Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

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