Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 311 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  declare(strict_types = 1);
  18  
  19  namespace gradingform_guide\grades\grader\gradingpanel\external;
  20  
  21  use advanced_testcase;
  22  use coding_exception;
  23  use core_grades\component_gradeitem;
  24  use core_external\external_api;
  25  use mod_forum\local\entities\forum as forum_entity;
  26  use moodle_exception;
  27  
  28  /**
  29   * Unit tests for core_grades\component_gradeitems;
  30   *
  31   * @package   gradingform_guide
  32   * @category  test
  33   * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class fetch_test extends advanced_testcase {
  37      /**
  38       * Ensure that an execute with an invalid component is rejected.
  39       */
  40      public function test_execute_invalid_component(): void {
  41          $this->resetAfterTest();
  42          $user = $this->getDataGenerator()->create_user();
  43          $this->setUser($user);
  44  
  45          $this->expectException(coding_exception::class);
  46          $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component");
  47          fetch::execute('mod_invalid', 1, 'foo', 2);
  48      }
  49  
  50      /**
  51       * Ensure that an execute with an invalid itemname on a valid component is rejected.
  52       */
  53      public function test_execute_invalid_itemname(): void {
  54          $this->resetAfterTest();
  55          $user = $this->getDataGenerator()->create_user();
  56          $this->setUser($user);
  57  
  58          $this->expectException(coding_exception::class);
  59          $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component");
  60          fetch::execute('mod_forum', 1, 'foo', 2);
  61      }
  62  
  63      /**
  64       * Ensure that an execute against a different grading method is rejected.
  65       */
  66      public function test_execute_incorrect_type(): void {
  67          $this->resetAfterTest();
  68  
  69          $forum = $this->get_forum_instance([
  70              // Negative numbers mean a scale.
  71              'grade_forum' => 5,
  72          ]);
  73          $course = $forum->get_course_record();
  74          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
  75          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  76          $this->setUser($teacher);
  77  
  78          $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
  79  
  80          $this->expectException(moodle_exception::class);
  81          $this->expectExceptionMessage("not configured for advanced grading with a marking guide");
  82          fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
  83      }
  84  
  85      /**
  86       * Ensure that an execute against the correct grading method returns the current state of the user.
  87       */
  88      public function test_execute_fetch_empty(): void {
  89          $this->resetAfterTest();
  90  
  91          [
  92              'forum' => $forum,
  93              'controller' => $controller,
  94              'definition' => $definition,
  95              'student' => $student,
  96              'teacher' => $teacher,
  97          ] = $this->get_test_data();
  98  
  99          $this->setUser($teacher);
 100  
 101          $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
 102  
 103          $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
 104          $result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
 105  
 106          $this->assertIsArray($result);
 107          $this->assertArrayHasKey('templatename', $result);
 108  
 109          $this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']);
 110  
 111          $this->assertArrayHasKey('warnings', $result);
 112          $this->assertIsArray($result['warnings']);
 113          $this->assertEmpty($result['warnings']);
 114  
 115          // Test the grade array items.
 116          $this->assertArrayHasKey('grade', $result);
 117          $this->assertIsArray($result['grade']);
 118          $this->assertIsInt($result['grade']['timecreated']);
 119  
 120          $this->assertArrayHasKey('timemodified', $result['grade']);
 121          $this->assertIsInt($result['grade']['timemodified']);
 122  
 123          $this->assertArrayHasKey('usergrade', $result['grade']);
 124          $this->assertEquals('- / 100.00', $result['grade']['usergrade']);
 125  
 126          $this->assertArrayHasKey('maxgrade', $result['grade']);
 127          $this->assertIsInt($result['grade']['maxgrade']);
 128          $this->assertEquals(100, $result['grade']['maxgrade']);
 129  
 130          $this->assertArrayHasKey('gradedby', $result['grade']);
 131          $this->assertEquals(null, $result['grade']['gradedby']);
 132  
 133          $this->assertArrayHasKey('criterion', $result['grade']);
 134          $criteria = $result['grade']['criterion'];
 135          $this->assertCount(count($definition->guide_criteria), $criteria);
 136          foreach ($criteria as $criterion) {
 137              $this->assertArrayHasKey('id', $criterion);
 138              $criterionid = $criterion['id'];
 139              $sourcecriterion = $definition->guide_criteria[$criterionid];
 140  
 141              $this->assertArrayHasKey('name', $criterion);
 142              $this->assertEquals($sourcecriterion['shortname'], $criterion['name']);
 143  
 144              $this->assertArrayHasKey('maxscore', $criterion);
 145              $this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']);
 146  
 147              $this->assertArrayHasKey('description', $criterion);
 148              $this->assertEquals($sourcecriterion['description'], $criterion['description']);
 149  
 150              $this->assertArrayHasKey('descriptionmarkers', $criterion);
 151              $this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']);
 152  
 153              $this->assertArrayHasKey('score', $criterion);
 154              $this->assertEmpty($criterion['score']);
 155  
 156              $this->assertArrayHasKey('remark', $criterion);
 157              $this->assertEmpty($criterion['remark']);
 158          }
 159      }
 160  
 161      /**
 162       * Ensure that an execute against the correct grading method returns the current state of the user.
 163       */
 164      public function test_execute_fetch_graded(): void {
 165          $this->resetAfterTest();
 166  
 167          [
 168              'forum' => $forum,
 169              'controller' => $controller,
 170              'definition' => $definition,
 171              'student' => $student,
 172              'teacher' => $teacher,
 173          ] = $this->get_test_data();
 174  
 175          $this->execute_and_assert_fetch($forum, $controller, $definition, $teacher, $teacher, $student);
 176      }
 177  
 178      /**
 179       * Class mates should not get other's grades.
 180       */
 181      public function test_execute_fetch_does_not_return_data_to_other_students(): void {
 182          $this->resetAfterTest();
 183  
 184          [
 185              'forum' => $forum,
 186              'controller' => $controller,
 187              'definition' => $definition,
 188              'student' => $student,
 189              'teacher' => $teacher,
 190              'course' => $course,
 191          ] = $this->get_test_data();
 192  
 193          $evilstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
 194  
 195          $this->expectException(\required_capability_exception::class);
 196          $this->execute_and_assert_fetch($forum, $controller, $definition, $evilstudent, $teacher, $student);
 197      }
 198  
 199      /**
 200       * Grades can be returned to graded user.
 201       */
 202      public function test_execute_fetch_return_data_to_graded_user(): void {
 203          $this->resetAfterTest();
 204  
 205          [
 206              'forum' => $forum,
 207              'controller' => $controller,
 208              'definition' => $definition,
 209              'student' => $student,
 210              'teacher' => $teacher,
 211          ] = $this->get_test_data();
 212  
 213          $this->execute_and_assert_fetch($forum, $controller, $definition, $student, $teacher, $student);
 214      }
 215  
 216      /**
 217       * Executes and performs all the assertions of the fetch method with the given parameters.
 218       */
 219      private function execute_and_assert_fetch ($forum, $controller, $definition, $fetcheruser, $grader, $gradeduser) {
 220          $generator = \testing_util::get_data_generator();
 221          $guidegenerator = $generator->get_plugin_generator('gradingform_guide');
 222  
 223          $this->setUser($grader);
 224  
 225          $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
 226          $grade = $gradeitem->get_grade_for_user($gradeduser, $grader);
 227          $instance = $gradeitem->get_advanced_grading_instance($grader, $grade);
 228  
 229          $submissiondata = $guidegenerator->get_test_form_data($controller, (int) $gradeduser->id,
 230              10, 'Propper good speling',
 231              0, 'ASCII art is not a picture'
 232          );
 233  
 234          $gradeitem->store_grade_from_formdata($gradeduser, $grader, (object) [
 235              'instanceid' => $instance->get_id(),
 236              'advancedgrading' => $submissiondata,
 237          ]);
 238  
 239          $this->setUser($fetcheruser);
 240  
 241          // Set up some items we need to return on other interfaces.
 242          $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $gradeduser->id);
 243          $result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
 244  
 245          $this->assertIsArray($result);
 246          $this->assertArrayHasKey('templatename', $result);
 247  
 248          $this->assertEquals('gradingform_guide/grades/grader/gradingpanel', $result['templatename']);
 249  
 250          $this->assertArrayHasKey('warnings', $result);
 251          $this->assertIsArray($result['warnings']);
 252          $this->assertEmpty($result['warnings']);
 253  
 254          // Test the grade array items.
 255          $this->assertArrayHasKey('grade', $result);
 256          $this->assertIsArray($result['grade']);
 257          $this->assertIsInt($result['grade']['timecreated']);
 258  
 259          $this->assertArrayHasKey('timemodified', $result['grade']);
 260          $this->assertIsInt($result['grade']['timemodified']);
 261  
 262          $this->assertArrayHasKey('usergrade', $result['grade']);
 263          $this->assertEquals('25.00 / 100.00', $result['grade']['usergrade']);
 264  
 265          $this->assertArrayHasKey('maxgrade', $result['grade']);
 266          $this->assertIsInt($result['grade']['maxgrade']);
 267          $this->assertEquals(100, $result['grade']['maxgrade']);
 268  
 269          $this->assertArrayHasKey('gradedby', $result['grade']);
 270          $this->assertEquals(fullname($grader), $result['grade']['gradedby']);
 271  
 272          $this->assertArrayHasKey('criterion', $result['grade']);
 273          $criteria = $result['grade']['criterion'];
 274          $this->assertCount(count($definition->guide_criteria), $criteria);
 275          foreach ($criteria as $criterion) {
 276              $this->assertArrayHasKey('id', $criterion);
 277              $criterionid = $criterion['id'];
 278              $sourcecriterion = $definition->guide_criteria[$criterionid];
 279  
 280              $this->assertArrayHasKey('name', $criterion);
 281              $this->assertEquals($sourcecriterion['shortname'], $criterion['name']);
 282  
 283              $this->assertArrayHasKey('maxscore', $criterion);
 284              $this->assertEquals($sourcecriterion['maxscore'], $criterion['maxscore']);
 285  
 286              $this->assertArrayHasKey('description', $criterion);
 287              $this->assertEquals($sourcecriterion['description'], $criterion['description']);
 288  
 289              $this->assertArrayHasKey('descriptionmarkers', $criterion);
 290              $this->assertEquals($sourcecriterion['descriptionmarkers'], $criterion['descriptionmarkers']);
 291  
 292              $this->assertArrayHasKey('score', $criterion);
 293              $this->assertArrayHasKey('remark', $criterion);
 294          }
 295  
 296          $this->assertEquals(10, $criteria[0]['score']);
 297          $this->assertEquals('Propper good speling', $criteria[0]['remark']);
 298          $this->assertEquals(0, $criteria[1]['score']);
 299          $this->assertEquals('ASCII art is not a picture', $criteria[1]['remark']);
 300      }
 301  
 302      /**
 303       * Get a forum instance.
 304       *
 305       * @param array $config
 306       * @return forum_entity
 307       */
 308      protected function get_forum_instance(array $config = []): forum_entity {
 309          $this->resetAfterTest();
 310  
 311          $datagenerator = $this->getDataGenerator();
 312          $course = $datagenerator->create_course();
 313          $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id, 'grade_forum' => 100]));
 314  
 315          $vaultfactory = \mod_forum\local\container::get_vault_factory();
 316          $vault = $vaultfactory->get_forum_vault();
 317  
 318          return $vault->get_from_id((int) $forum->id);
 319      }
 320  
 321      /**
 322       * Get test data for forums graded using a marking guide.
 323       *
 324       * @return array
 325       */
 326      protected function get_test_data(): array {
 327          global $DB;
 328  
 329          $this->resetAfterTest();
 330  
 331          $generator = \testing_util::get_data_generator();
 332          $guidegenerator = $generator->get_plugin_generator('gradingform_guide');
 333  
 334          $forum = $this->get_forum_instance();
 335          $course = $forum->get_course_record();
 336          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 337          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
 338  
 339          $this->setUser($teacher);
 340          $controller = $guidegenerator->get_test_guide($forum->get_context(), 'forum', 'forum');
 341          $definition = $controller->get_definition();
 342  
 343          // In the situation of mod_forum this would be the id from forum_grades.
 344          $itemid = 1;
 345          $instance = $controller->create_instance($student->id, $itemid);
 346  
 347          $data = $this->get_test_form_data(
 348              $controller,
 349              $itemid,
 350              5, 'This user made several mistakes.',
 351              10, 'This user has two pictures.'
 352          );
 353  
 354          // Update this instance with data.
 355          $instance->update($data);
 356  
 357          return [
 358              'forum' => $forum,
 359              'controller' => $controller,
 360              'definition' => $definition,
 361              'student' => $student,
 362              'teacher' => $teacher,
 363              'course' => $course,
 364          ];
 365      }
 366  
 367      /**
 368       * Fetch a set of sample data.
 369       *
 370       * @param \gradingform_guide_controller $controller
 371       * @param int $itemid
 372       * @param float $spellingscore
 373       * @param string $spellingremark
 374       * @param float $picturescore
 375       * @param string $pictureremark
 376       * @return array
 377       */
 378      protected function get_test_form_data(
 379          \gradingform_guide_controller $controller,
 380          int $itemid,
 381          float $spellingscore,
 382          string $spellingremark,
 383          float $picturescore,
 384          string $pictureremark
 385      ): array {
 386          $generator = \testing_util::get_data_generator();
 387          $guidegenerator = $generator->get_plugin_generator('gradingform_guide');
 388  
 389          return $guidegenerator->get_test_form_data(
 390              $controller,
 391              $itemid,
 392              $spellingscore,
 393              $spellingremark,
 394              $picturescore,
 395              $pictureremark
 396          );
 397      }
 398  }