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 core_grades\grades\grader\gradingpanel\scale\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 use grade_grade; 28 use grade_item; 29 30 /** 31 * Unit tests for core_grades\component_gradeitems; 32 * 33 * @package core_grades 34 * @category test 35 * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class store_test extends advanced_testcase { 39 40 /** 41 * Ensure that an execute with an invalid component is rejected. 42 */ 43 public function test_execute_invalid_component(): void { 44 $this->resetAfterTest(); 45 $user = $this->getDataGenerator()->create_user(); 46 $this->setUser($user); 47 48 $this->expectException(coding_exception::class); 49 $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component"); 50 store::execute('mod_invalid', 1, 'foo', 2, false, 'formdata'); 51 } 52 53 /** 54 * Ensure that an execute with an invalid itemname on a valid component is rejected. 55 */ 56 public function test_execute_invalid_itemname(): void { 57 $this->resetAfterTest(); 58 $user = $this->getDataGenerator()->create_user(); 59 $this->setUser($user); 60 61 $this->expectException(coding_exception::class); 62 $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component"); 63 store::execute('mod_forum', 1, 'foo', 2, false, 'formdata'); 64 } 65 66 /** 67 * Ensure that an execute against a different grading method is rejected. 68 */ 69 public function test_execute_incorrect_type(): void { 70 $this->resetAfterTest(); 71 72 $forum = $this->get_forum_instance([ 73 // Negative numbers mean a scale. 74 'grade_forum' => 5, 75 ]); 76 $course = $forum->get_course_record(); 77 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 78 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 79 $this->setUser($teacher); 80 81 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 82 83 $this->expectException(moodle_exception::class); 84 $this->expectExceptionMessage("not configured for grading with scales"); 85 store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); 86 } 87 88 /** 89 * Ensure that an execute against a different grading method is rejected. 90 */ 91 public function test_execute_disabled(): void { 92 $this->resetAfterTest(); 93 94 $forum = $this->get_forum_instance(); 95 $course = $forum->get_course_record(); 96 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 97 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 98 $this->setUser($teacher); 99 100 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 101 102 $this->expectException(moodle_exception::class); 103 $this->expectExceptionMessage("Grading is not enabled"); 104 store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata'); 105 } 106 107 /** 108 * Ensure that an execute against the correct grading method returns the current state of the user. 109 */ 110 public function test_execute_store_empty(): void { 111 [ 112 'forum' => $forum, 113 'options' => $options, 114 'student' => $student, 115 'teacher' => $teacher, 116 ] = $this->get_test_data(); 117 118 $this->setUser($teacher); 119 120 $formdata = [ 121 'grade' => null, 122 ]; 123 124 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 125 126 $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', 127 (int) $student->id, false, http_build_query($formdata)); 128 $result = external_api::clean_returnvalue(store::execute_returns(), $result); 129 130 // The result should still be empty. 131 $this->assertIsArray($result); 132 $this->assertArrayHasKey('templatename', $result); 133 134 $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); 135 136 $this->assertArrayHasKey('warnings', $result); 137 $this->assertIsArray($result['warnings']); 138 $this->assertEmpty($result['warnings']); 139 140 // Test the grade array items. 141 $this->assertArrayHasKey('grade', $result); 142 $this->assertIsArray($result['grade']); 143 144 $this->assertIsInt($result['grade']['timecreated']); 145 $this->assertArrayHasKey('timemodified', $result['grade']); 146 $this->assertIsInt($result['grade']['timemodified']); 147 148 $this->assertArrayHasKey('usergrade', $result['grade']); 149 $this->assertEquals('-', $result['grade']['usergrade']); 150 151 $this->assertArrayHasKey('maxgrade', $result['grade']); 152 $this->assertIsInt($result['grade']['maxgrade']); 153 $this->assertEquals(3, $result['grade']['maxgrade']); 154 155 $this->assertArrayHasKey('gradedby', $result['grade']); 156 $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); 157 158 $this->assertArrayHasKey('options', $result['grade']); 159 $this->assertCount(count($options), $result['grade']['options']); 160 rsort($options); 161 foreach ($options as $index => $option) { 162 $this->assertArrayHasKey($index, $result['grade']['options']); 163 164 $returnedoption = $result['grade']['options'][$index]; 165 $this->assertArrayHasKey('value', $returnedoption); 166 $this->assertEquals(3 - $index, $returnedoption['value']); 167 168 $this->assertArrayHasKey('title', $returnedoption); 169 $this->assertEquals($option, $returnedoption['title']); 170 171 $this->assertArrayHasKey('selected', $returnedoption); 172 $this->assertFalse($returnedoption['selected']); 173 } 174 175 // Compare against the grade stored in the database. 176 $storedgradeitem = grade_item::fetch([ 177 'courseid' => $forum->get_course_id(), 178 'itemtype' => 'mod', 179 'itemmodule' => 'forum', 180 'iteminstance' => $forum->get_id(), 181 'itemnumber' => $gradeitem->get_grade_itemid(), 182 ]); 183 $storedgrade = grade_grade::fetch([ 184 'userid' => $student->id, 185 'itemid' => $storedgradeitem->id, 186 ]); 187 188 $this->assertEmpty($storedgrade->rawgrade); 189 } 190 191 /** 192 * Ensure that an execute against the correct grading method returns the current state of the user. 193 */ 194 public function test_execute_store_not_selected(): void { 195 [ 196 'forum' => $forum, 197 'options' => $options, 198 'student' => $student, 199 'teacher' => $teacher, 200 ] = $this->get_test_data(); 201 202 $this->setUser($teacher); 203 204 $formdata = [ 205 'grade' => -1, 206 ]; 207 208 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 209 210 $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', 211 (int) $student->id, false, http_build_query($formdata)); 212 $result = external_api::clean_returnvalue(store::execute_returns(), $result); 213 214 // The result should still be empty. 215 $this->assertIsArray($result); 216 $this->assertArrayHasKey('templatename', $result); 217 218 $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); 219 220 $this->assertArrayHasKey('warnings', $result); 221 $this->assertIsArray($result['warnings']); 222 $this->assertEmpty($result['warnings']); 223 224 // Test the grade array items. 225 $this->assertArrayHasKey('grade', $result); 226 $this->assertIsArray($result['grade']); 227 228 $this->assertIsInt($result['grade']['timecreated']); 229 $this->assertArrayHasKey('timemodified', $result['grade']); 230 $this->assertIsInt($result['grade']['timemodified']); 231 232 $this->assertArrayHasKey('usergrade', $result['grade']); 233 $this->assertEquals('-', $result['grade']['usergrade']); 234 235 $this->assertArrayHasKey('maxgrade', $result['grade']); 236 $this->assertIsInt($result['grade']['maxgrade']); 237 $this->assertEquals(3, $result['grade']['maxgrade']); 238 239 $this->assertArrayHasKey('gradedby', $result['grade']); 240 $this->assertEquals(null, $result['grade']['gradedby']); 241 242 $this->assertArrayHasKey('options', $result['grade']); 243 $this->assertCount(count($options), $result['grade']['options']); 244 rsort($options); 245 foreach ($options as $index => $option) { 246 $this->assertArrayHasKey($index, $result['grade']['options']); 247 248 $returnedoption = $result['grade']['options'][$index]; 249 $this->assertArrayHasKey('value', $returnedoption); 250 $this->assertEquals(3 - $index, $returnedoption['value']); 251 252 $this->assertArrayHasKey('title', $returnedoption); 253 $this->assertEquals($option, $returnedoption['title']); 254 255 $this->assertArrayHasKey('selected', $returnedoption); 256 $this->assertFalse($returnedoption['selected']); 257 } 258 259 // Compare against the grade stored in the database. 260 $storedgradeitem = grade_item::fetch([ 261 'courseid' => $forum->get_course_id(), 262 'itemtype' => 'mod', 263 'itemmodule' => 'forum', 264 'iteminstance' => $forum->get_id(), 265 'itemnumber' => $gradeitem->get_grade_itemid(), 266 ]); 267 $storedgrade = grade_grade::fetch([ 268 'userid' => $student->id, 269 'itemid' => $storedgradeitem->id, 270 ]); 271 272 // No grade will have been saved. 273 $this->assertFalse($storedgrade); 274 } 275 276 /** 277 * Ensure that an execute against the correct grading method returns the current state of the user. 278 */ 279 public function test_execute_store_graded(): void { 280 [ 281 'scale' => $scale, 282 'forum' => $forum, 283 'options' => $options, 284 'student' => $student, 285 'teacher' => $teacher, 286 ] = $this->get_test_data(); 287 288 $this->setUser($teacher); 289 290 $formdata = [ 291 'grade' => 2, 292 ]; 293 $formattedvalue = grade_floatval(unformat_float($formdata['grade'])); 294 295 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 296 297 $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', 298 (int) $student->id, false, http_build_query($formdata)); 299 $result = external_api::clean_returnvalue(store::execute_returns(), $result); 300 301 // The result should still be empty. 302 $this->assertIsArray($result); 303 $this->assertArrayHasKey('templatename', $result); 304 305 $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']); 306 307 $this->assertArrayHasKey('warnings', $result); 308 $this->assertIsArray($result['warnings']); 309 $this->assertEmpty($result['warnings']); 310 311 // Test the grade array items. 312 $this->assertArrayHasKey('grade', $result); 313 $this->assertIsArray($result['grade']); 314 315 $this->assertIsInt($result['grade']['timecreated']); 316 $this->assertArrayHasKey('timemodified', $result['grade']); 317 $this->assertIsInt($result['grade']['timemodified']); 318 319 $this->assertArrayHasKey('usergrade', $result['grade']); 320 $this->assertEquals('B', $result['grade']['usergrade']); 321 322 $this->assertArrayHasKey('maxgrade', $result['grade']); 323 $this->assertIsInt($result['grade']['maxgrade']); 324 $this->assertEquals(3, $result['grade']['maxgrade']); 325 326 $this->assertArrayHasKey('gradedby', $result['grade']); 327 $this->assertEquals(fullname($teacher), $result['grade']['gradedby']); 328 329 $this->assertArrayHasKey('options', $result['grade']); 330 $this->assertCount(count($options), $result['grade']['options']); 331 rsort($options); 332 foreach ($options as $index => $option) { 333 $this->assertArrayHasKey($index, $result['grade']['options']); 334 335 $returnedoption = $result['grade']['options'][$index]; 336 $this->assertArrayHasKey('value', $returnedoption); 337 $this->assertEquals(3 - $index, $returnedoption['value']); 338 339 $this->assertArrayHasKey('title', $returnedoption); 340 $this->assertEquals($option, $returnedoption['title']); 341 342 $this->assertArrayHasKey('selected', $returnedoption); 343 } 344 345 // Compare against the grade stored in the database. 346 $storedgradeitem = grade_item::fetch([ 347 'courseid' => $forum->get_course_id(), 348 'itemtype' => 'mod', 349 'itemmodule' => 'forum', 350 'iteminstance' => $forum->get_id(), 351 'itemnumber' => $gradeitem->get_grade_itemid(), 352 ]); 353 $storedgrade = grade_grade::fetch([ 354 'userid' => $student->id, 355 'itemid' => $storedgradeitem->id, 356 ]); 357 358 $this->assertEquals($formattedvalue, $storedgrade->rawgrade); 359 $this->assertEquals($scale->id, $storedgrade->rawscaleid); 360 361 // The grade was 2, which relates to the middle option. 362 $this->assertFalse($result['grade']['options'][0]['selected']); 363 $this->assertTrue($result['grade']['options'][1]['selected']); 364 $this->assertFalse($result['grade']['options'][2]['selected']); 365 } 366 367 /** 368 * Ensure that an out-of-range value is rejected. 369 * 370 * @dataProvider execute_out_of_range_provider 371 * @param int $suppliedvalue The value that was submitted 372 */ 373 public function test_execute_store_out_of_range(int $suppliedvalue): void { 374 [ 375 'scale' => $scale, 376 'forum' => $forum, 377 'options' => $options, 378 'student' => $student, 379 'teacher' => $teacher, 380 ] = $this->get_test_data(); 381 382 $this->setUser($teacher); 383 384 $formdata = [ 385 'grade' => $suppliedvalue, 386 ]; 387 388 $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum'); 389 390 $this->expectException(moodle_exception::class); 391 $this->expectExceptionMessage("Invalid grade '{$suppliedvalue}' provided. Grades must be between 0 and 3."); 392 store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', 393 (int) $student->id, false, http_build_query($formdata)); 394 } 395 396 /** 397 * Data provider for out of range tests. 398 * 399 * @return array 400 */ 401 public function execute_out_of_range_provider(): array { 402 return [ 403 'above' => [ 404 'supplied' => 500, 405 ], 406 'above just' => [ 407 'supplied' => 4, 408 ], 409 'below' => [ 410 'supplied' => -100, 411 ], 412 '-10' => [ 413 'supplied' => -10, 414 ], 415 ]; 416 } 417 418 419 /** 420 * Get a forum instance. 421 * 422 * @param array $config 423 * @return forum_entity 424 */ 425 protected function get_forum_instance(array $config = []): forum_entity { 426 $this->resetAfterTest(); 427 428 $datagenerator = $this->getDataGenerator(); 429 $course = $datagenerator->create_course(); 430 $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id])); 431 432 $vaultfactory = \mod_forum\local\container::get_vault_factory(); 433 $vault = $vaultfactory->get_forum_vault(); 434 435 return $vault->get_from_id((int) $forum->id); 436 } 437 438 /** 439 * Get test data for scaled forums. 440 * 441 * @return array 442 */ 443 protected function get_test_data(): array { 444 $this->resetAfterTest(); 445 446 $options = [ 447 'A', 448 'B', 449 'C' 450 ]; 451 $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]); 452 453 $forum = $this->get_forum_instance([ 454 // Negative numbers mean a scale. 455 'grade_forum' => -1 * $scale->id 456 ]); 457 $course = $forum->get_course_record(); 458 $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher'); 459 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 460 461 return [ 462 'forum' => $forum, 463 'scale' => $scale, 464 'options' => $options, 465 'student' => $student, 466 'teacher' => $teacher, 467 ]; 468 } 469 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body