Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 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 namespace core; 18 19 use core_grades_external; 20 21 defined('MOODLE_INTERNAL') || die(); 22 23 global $CFG; 24 25 require_once($CFG->dirroot . '/webservice/tests/helpers.php'); 26 27 /** 28 * Grades functions unit tests 29 * 30 * Unit tests for the grade API at /lib/classes/grades_external.php 31 * 32 * @package core 33 * @category test 34 * @copyright 2012 Andrew Davis 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class grades_external_test extends \externallib_advanced_testcase { 38 39 /** 40 * Load initial test information 41 * 42 * @param string $assignmentname Assignment name 43 * @param int $student1rawgrade Student 1 grade 44 * @param int $student2rawgrade Student 2 grade 45 * @return array Array of vars with test information 46 */ 47 protected function load_test_data($assignmentname, $student1rawgrade, $student2rawgrade) { 48 global $DB; 49 50 // Adds a course, a teacher, 2 students, an assignment and grades for the students. 51 $course = $this->getDataGenerator()->create_course(); 52 $coursecontext = \context_course::instance($course->id); 53 54 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 55 56 $student1 = $this->getDataGenerator()->create_user(); 57 $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id); 58 59 $student2 = $this->getDataGenerator()->create_user(); 60 $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id); 61 62 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher')); 63 $teacher = $this->getDataGenerator()->create_user(); 64 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id); 65 66 $parent = $this->getDataGenerator()->create_user(); 67 $this->setUser($parent); 68 $student1context = \context_user::instance($student1->id); 69 // Creates a new role, gives it the capability and gives $USER that role. 70 $parentroleid = $this->assignUserCapability('moodle/grade:viewall', $student1context->id); 71 // Enrol the user in the course using the new role. 72 $this->getDataGenerator()->enrol_user($parent->id, $course->id, $parentroleid); 73 74 $assignment = $this->getDataGenerator()->create_module('assign', array('name' => $assignmentname, 'course' => $course->id)); 75 $modcontext = get_coursemodule_from_instance('assign', $assignment->id, $course->id); 76 $assignment->cmidnumber = $modcontext->id; 77 78 $student1grade = array('userid' => $student1->id, 'rawgrade' => $student1rawgrade); 79 $student2grade = array('userid' => $student2->id, 'rawgrade' => $student2rawgrade); 80 $studentgrades = array($student1->id => $student1grade, $student2->id => $student2grade); 81 assign_grade_item_update($assignment, $studentgrades); 82 83 // Insert a custom grade scale to be used by an outcome. 84 $gradescale = new \grade_scale(); 85 $gradescale->name = 'unittestscale3'; 86 $gradescale->courseid = $course->id; 87 $gradescale->userid = 0; 88 $gradescale->scale = 'Distinction, Very Good, Good, Pass, Fail'; 89 $gradescale->description = 'This scale is used to mark standard assignments.'; 90 $gradescale->insert(); 91 92 // Insert an outcome. 93 $data = new \stdClass(); 94 $data->courseid = $course->id; 95 $data->fullname = 'Team work'; 96 $data->shortname = 'Team work'; 97 $data->scaleid = $gradescale->id; 98 $outcome = new \grade_outcome($data, false); 99 $outcome->insert(); 100 101 $outcomegradeitem = new \grade_item(); 102 $outcomegradeitem->itemname = $outcome->shortname; 103 $outcomegradeitem->itemtype = 'mod'; 104 $outcomegradeitem->itemmodule = 'assign'; 105 $outcomegradeitem->iteminstance = $assignment->id; 106 $outcomegradeitem->outcomeid = $outcome->id; 107 $outcomegradeitem->cmid = 0; 108 $outcomegradeitem->courseid = $course->id; 109 $outcomegradeitem->aggregationcoef = 0; 110 $outcomegradeitem->itemnumber = 1; // The activity's original grade item will be 0. 111 $outcomegradeitem->gradetype = GRADE_TYPE_SCALE; 112 $outcomegradeitem->scaleid = $outcome->scaleid; 113 // This next two values for testing that returns parameters are correcly formatted. 114 $outcomegradeitem->set_locked(true); 115 $outcomegradeitem->hidden = ''; 116 $outcomegradeitem->insert(); 117 118 $assignmentgradeitem = \grade_item::fetch( 119 array( 120 'itemtype' => 'mod', 121 'itemmodule' => 'assign', 122 'iteminstance' => $assignment->id, 123 'itemnumber' => 0, 124 'courseid' => $course->id 125 ) 126 ); 127 $outcomegradeitem->set_parent($assignmentgradeitem->categoryid); 128 $outcomegradeitem->move_after_sortorder($assignmentgradeitem->sortorder); 129 130 return array($course, $assignment, $student1, $student2, $teacher, $parent); 131 } 132 133 /** 134 * Test get_grades() 135 */ 136 public function test_get_grades() { 137 global $CFG; 138 139 $this->resetAfterTest(true); 140 $CFG->enableoutcomes = 1; 141 142 $assignmentname = 'The assignment'; 143 $student1rawgrade = 10; 144 $student2rawgrade = 20; 145 list($course, $assignment, $student1, $student2, $teacher, $parent) = 146 $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); 147 $assigmentcm = get_coursemodule_from_id('assign', $assignment->cmid, 0, false, MUST_EXIST); 148 149 // Teacher requesting a student grade for the assignment. 150 $this->setUser($teacher); 151 $grades = core_grades_external::get_grades( 152 $course->id, 153 'mod_assign', 154 $assigmentcm->id, 155 array($student1->id) 156 ); 157 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 158 $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); 159 160 // Teacher requesting all the grades of student1 in a course. 161 $grades = core_grades_external::get_grades( 162 $course->id, 163 null, 164 null, 165 array($student1->id) 166 ); 167 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 168 $this->assertTrue(count($grades['items']) == 2); 169 $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); 170 $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, 'course', $student1->id)); 171 172 $outcome = $this->get_outcome($grades, $assigmentcm->id); 173 $this->assertEquals($outcome['name'], 'Team work'); 174 $this->assertEquals(0, $this->get_outcome_student_grade($grades, $assigmentcm->id, $student1->id)); 175 176 // Teacher requesting all the grades of all the students in a course. 177 $grades = core_grades_external::get_grades( 178 $course->id, 179 null, 180 null, 181 array($student1->id, $student2->id) 182 ); 183 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 184 $this->assertTrue(count($grades['items']) == 2); 185 $this->assertTrue(count($grades['items'][0]['grades']) == 2); 186 $this->assertTrue(count($grades['items'][1]['grades']) == 2); 187 188 // Student requesting another student's grade for the assignment (should fail). 189 $this->setUser($student1); 190 try { 191 $grades = core_grades_external::get_grades( 192 $course->id, 193 'mod_assign', 194 $assigmentcm->id, 195 array($student2->id) 196 ); 197 $this->fail('moodle_exception expected'); 198 } catch (\moodle_exception $ex) { 199 $this->assertTrue(true); 200 } 201 202 // Parent requesting their child's grade for the assignment (should fail). 203 $this->setUser($parent); 204 try { 205 $grades = core_grades_external::get_grades( 206 $course->id, 207 'mod_assign', 208 $assigmentcm->id, 209 array($student1->id) 210 ); 211 $this->fail('moodle_exception expected'); 212 } catch (\moodle_exception $ex) { 213 $this->assertTrue(true); 214 } 215 216 // Parent requesting another student's grade for the assignment(should fail). 217 try { 218 $grades = core_grades_external::get_grades( 219 $course->id, 220 'mod_assign', 221 $assigmentcm->id, 222 array($student2->id) 223 ); 224 $this->fail('moodle_exception expected'); 225 } catch (\moodle_exception $ex) { 226 $this->assertTrue(true); 227 } 228 229 // Student requesting all other student grades for the assignment (should fail). 230 try { 231 $grades = core_grades_external::get_grades( 232 $course->id, 233 'mod_assign', 234 $assigmentcm->id, 235 array($student1->id, $student2->id) 236 ); 237 $this->fail('moodle_exception expected'); 238 } catch (\moodle_exception $ex) { 239 $this->assertTrue(true); 240 } 241 242 // Student requesting only grade item information (should fail). 243 try { 244 $grades = core_grades_external::get_grades( 245 $course->id, 246 'mod_assign', 247 $assigmentcm->id, 248 array() 249 ); 250 $this->fail('moodle_exception expected'); 251 } catch (\moodle_exception $ex) { 252 $this->assertTrue(true); 253 } 254 255 // Teacher requesting student grades for a course. 256 $this->setUser($teacher); 257 $grades = core_grades_external::get_grades( 258 $course->id, 259 'mod_assign', 260 $assigmentcm->id, 261 array($student1->id, $student2->id) 262 ); 263 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 264 $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); 265 $this->assertEquals($student2rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student2->id)); 266 267 // Teacher requesting grade item information. 268 $grades = core_grades_external::get_grades( 269 $course->id, 270 'mod_assign', 271 $assigmentcm->id 272 ); 273 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 274 $activity = $this->get_activity($grades, $assigmentcm->id); 275 $this->assertEquals($activity['name'], $assignmentname); 276 $this->assertEquals(count($activity['grades']), 0); 277 278 // Teacher requesting all grade items in a course. 279 $grades = core_grades_external::get_grades( 280 $course->id 281 ); 282 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 283 $this->assertTrue(count($grades['items']) == 2); 284 285 $activity = $this->get_activity($grades, $assigmentcm->id); 286 $this->assertEquals($activity['name'], $assignmentname); 287 $this->assertEquals(count($activity['grades']), 0); 288 289 $outcome = $this->get_outcome($grades, $assigmentcm->id); 290 $this->assertEquals($outcome['name'], 'Team work'); 291 292 // Hide a grade item then have student request it. 293 $result = core_grades_external::update_grades( 294 'test', 295 $course->id, 296 'mod_assign', 297 $assigmentcm->id, 298 0, 299 array(), 300 array('hidden' => 1) 301 ); 302 $result = \external_api::clean_returnvalue(core_grades_external::update_grades_returns(), $result); 303 $this->assertTrue($result == GRADE_UPDATE_OK); 304 305 // Check it's definitely hidden. 306 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); 307 $this->assertEquals($grades->items[0]->hidden, 1); 308 309 // Teacher should still be able to see the hidden grades. 310 $this->setUser($teacher); 311 $grades = core_grades_external::get_grades( 312 $course->id, 313 'mod_assign', 314 $assigmentcm->id, 315 array($student1->id) 316 ); 317 $grades = \external_api::clean_returnvalue(core_grades_external::get_grades_returns(), $grades); 318 $this->assertEquals($student1rawgrade, $this->get_activity_student_grade($grades, $assigmentcm->id, $student1->id)); 319 } 320 321 /** 322 * Get an activity 323 * 324 * @param array $grades Array of grades 325 * @param int $cmid Activity course module id 326 * @return strdClass Activity object 327 */ 328 private function get_activity($grades, $cmid) { 329 foreach ($grades['items'] as $item) { 330 if ($item['activityid'] == $cmid) { 331 return $item; 332 } 333 } 334 return null; 335 } 336 337 /** 338 * Get a grade for an activity 339 * 340 * @param array $grades Array of grades 341 * @param int $cmid Activity course module id 342 * @param int $studentid Student it 343 * @return stdClass Activity Object 344 */ 345 private function get_activity_student_grade($grades, $cmid, $studentid) { 346 $item = $this->get_activity($grades, $cmid); 347 foreach ($item['grades'] as $grade) { 348 if ($grade['userid'] == $studentid) { 349 return $grade['grade']; 350 } 351 } 352 return null; 353 } 354 355 /** 356 * Get an ouctome 357 * 358 * @param array $grades Array of grades 359 * @param int $cmid Activity course module id 360 * @return stdClass Outcome object 361 */ 362 private function get_outcome($grades, $cmid) { 363 foreach ($grades['outcomes'] as $outcome) { 364 if ($outcome['activityid'] == $cmid) { 365 return $outcome; 366 } 367 } 368 return null; 369 } 370 371 /** 372 * Get a grade from an outcome 373 * 374 * @param array $grades Array of grades 375 * @param int $cmid Activity course module id 376 * @param int $studentid Student id 377 * @return stdClass Outcome object 378 */ 379 private function get_outcome_student_grade($grades, $cmid, $studentid) { 380 $outcome = $this->get_outcome($grades, $cmid); 381 foreach ($outcome['grades'] as $grade) { 382 if ($grade['userid'] == $studentid) { 383 return $grade['grade']; 384 } 385 } 386 return null; 387 } 388 389 /** 390 * Test update_grades() 391 */ 392 public function test_update_grades() { 393 global $DB; 394 395 $this->resetAfterTest(true); 396 397 $assignmentname = 'The assignment'; 398 $student1rawgrade = 10; 399 $student2rawgrade = 20; 400 list($course, $assignment, $student1, $student2, $teacher, $parent) = 401 $this->load_test_data($assignmentname, $student1rawgrade, $student2rawgrade); 402 $assigmentcm = get_coursemodule_from_id('assign', $assignment->cmid, 0, false, MUST_EXIST); 403 404 $this->setUser($teacher); 405 406 // Teacher updating grade item information. 407 $changedmax = 93; 408 $result = core_grades_external::update_grades( 409 'test', 410 $course->id, 411 'mod_assign', 412 $assigmentcm->id, 413 0, 414 array(), 415 array('grademax' => $changedmax) 416 ); 417 $result = \external_api::clean_returnvalue(core_grades_external::update_grades_returns(), $result); 418 $this->assertTrue($result == GRADE_UPDATE_OK); 419 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); 420 $this->assertTrue($grades->items[0]->grademax == $changedmax); 421 422 // Teacher updating 1 student grade. 423 $student1grade = 23; 424 $result = core_grades_external::update_grades( 425 'test', 426 $course->id, 427 'mod_assign', 428 $assigmentcm->id, 429 0, 430 array(array('studentid' => $student1->id, 'grade' => $student1grade)) 431 ); 432 $result = \external_api::clean_returnvalue(core_grades_external::update_grades_returns(), $result); 433 $this->assertTrue($result == GRADE_UPDATE_OK); 434 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id, array($student1->id)); 435 $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); 436 437 // Teacher updating multiple student grades. 438 $student1grade = 11; 439 $student2grade = 13; 440 $result = core_grades_external::update_grades( 441 'test', 442 $course->id, 443 'mod_assign', 444 $assigmentcm->id, 445 0, 446 array( 447 array('studentid' => $student1->id, 'grade' => $student1grade), 448 array('studentid' => $student2->id, 'grade' => $student2grade) 449 ) 450 ); 451 $result = \external_api::clean_returnvalue(core_grades_external::update_grades_returns(), $result); 452 $this->assertTrue($result == GRADE_UPDATE_OK); 453 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id, array($student1->id, $student2->id)); 454 $this->assertTrue($grades->items[0]->grades[$student1->id]->grade == $student1grade); 455 $this->assertTrue($grades->items[0]->grades[$student2->id]->grade == $student2grade); 456 457 // Student attempting to update their own grade (should fail). 458 $this->setUser($student1); 459 try { 460 $student1grade = 17; 461 $result = core_grades_external::update_grades( 462 'test', 463 $course->id, 464 'mod_assign', 465 $assigmentcm->id, 466 0, 467 array( array('studentid' => $student1->id, 'grade' => $student1grade)) 468 ); 469 $this->fail('moodle_exception expected'); 470 } catch (\moodle_exception $ex) { 471 $this->assertTrue(true); 472 } 473 474 // Parent attempting to update their child's grade (should fail). 475 $this->setUser($parent); 476 try { 477 $student1grade = 13; 478 $result = core_grades_external::update_grades( 479 'test', 480 $course->id, 481 'mod_assign', 482 $assigmentcm->id, 483 0, 484 array( array('studentid' => $student1->id, 'grade' => $student1grade)) 485 ); 486 $this->fail('moodle_exception expected'); 487 } catch (\moodle_exception $ex) { 488 $this->assertTrue(true); 489 } 490 491 // Student trying to hide a grade item (should fail). 492 $this->setUser($student1); 493 try { 494 $result = core_grades_external::update_grades( 495 'test', 496 $course->id, 497 'mod_assign', 498 $assigmentcm->id, 499 0, 500 array(), 501 array('hidden' => 1) 502 ); 503 $this->fail('moodle_exception expected'); 504 } catch (\moodle_exception $ex) { 505 $this->assertTrue(true); 506 } 507 508 // Give the student role 'moodle/grade:hide' and they should now be able to hide the grade item. 509 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 510 $coursecontext = \context_course::instance($course->id); 511 assign_capability('moodle/grade:hide', CAP_ALLOW, $studentrole->id, $coursecontext->id); 512 accesslib_clear_all_caches_for_unit_testing(); 513 514 // Check the activity isn't already hidden. 515 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); 516 $this->assertTrue($grades->items[0]->hidden == 0); 517 518 $result = core_grades_external::update_grades( 519 'test', 520 $course->id, 521 'mod_assign', 522 $assigmentcm->id, 523 0, 524 array(), 525 array('hidden' => 1) 526 ); 527 $result = \external_api::clean_returnvalue(core_grades_external::update_grades_returns(), $result); 528 $this->assertTrue($result == GRADE_UPDATE_OK); 529 $grades = grade_get_grades($course->id, 'mod', 'assign', $assignment->id); 530 $this->assertTrue($grades->items[0]->hidden == 1); 531 } 532 533 /** 534 * Test create_gradecategory. 535 * 536 * @return void 537 */ 538 public function test_create_gradecategory() { 539 global $DB; 540 $this->resetAfterTest(true); 541 $course = $this->getDataGenerator()->create_course(); 542 $this->setAdminUser(); 543 544 // Test the most basic gradecategory creation. 545 $status1 = core_grades_external::create_gradecategory($course->id, 'Test Category 1', []); 546 547 $courseparentcat = new \grade_category(['courseid' => $course->id, 'depth' => 1], true); 548 $record1 = $DB->get_record('grade_categories', ['id' => $status1['categoryid']]); 549 $this->assertEquals('Test Category 1', $record1->fullname); 550 // Confirm that the parent category for this category is the top level category for the course. 551 $this->assertEquals($courseparentcat->id, $record1->parent); 552 $this->assertEquals(2, $record1->depth); 553 554 // Now create a category as a child of the newly created category. 555 $status2 = core_grades_external::create_gradecategory($course->id, 'Test Category 2', ['parentcategoryid' => $record1->id]); 556 $record2 = $DB->get_record('grade_categories', ['id' => $status2['categoryid']]); 557 $this->assertEquals($record1->id, $record2->parent); 558 $this->assertEquals(3, $record2->depth); 559 // Check the path is correct. 560 $this->assertEquals('/' . implode('/', [$courseparentcat->id, $record1->id, $record2->id]) . '/', $record2->path); 561 562 // Now create a category with some customised data and check the returns. This customises every value. 563 $customopts = [ 564 'aggregation' => GRADE_AGGREGATE_MEAN, 565 'aggregateonlygraded' => 0, 566 'aggregateoutcomes' => 1, 567 'droplow' => 1, 568 'itemname' => 'item', 569 'iteminfo' => 'info', 570 'idnumber' => 'idnumber', 571 'gradetype' => GRADE_TYPE_TEXT, 572 'grademax' => 5, 573 'grademin' => 2, 574 'gradepass' => 3, 575 'display' => GRADE_DISPLAY_TYPE_LETTER, 576 // Hack. This must be -2 to use the default setting. 577 'decimals' => 3, 578 'hiddenuntil' => time(), 579 'locktime' => time(), 580 'weightoverride' => 1, 581 'aggregationcoef2' => 20, 582 'parentcategoryid' => $record2->id 583 ]; 584 585 $status3 = core_grades_external::create_gradecategory($course->id, 'Test Category 3', $customopts); 586 $cat3 = new \grade_category(['courseid' => $course->id, 'id' => $status3['categoryid']], true); 587 $cat3->load_grade_item(); 588 589 // Lets check all of the data is in the right shape. 590 $this->assertEquals(GRADE_AGGREGATE_MEAN, $cat3->aggregation); 591 $this->assertEquals(0, $cat3->aggregateonlygraded); 592 $this->assertEquals(1, $cat3->aggregateoutcomes); 593 $this->assertEquals(1, $cat3->droplow); 594 $this->assertEquals('item', $cat3->grade_item->itemname); 595 $this->assertEquals('info', $cat3->grade_item->iteminfo); 596 $this->assertEquals('idnumber', $cat3->grade_item->idnumber); 597 $this->assertEquals(GRADE_TYPE_TEXT, $cat3->grade_item->gradetype); 598 $this->assertEquals(5, $cat3->grade_item->grademax); 599 $this->assertEquals(2, $cat3->grade_item->grademin); 600 $this->assertEquals(3, $cat3->grade_item->gradepass); 601 $this->assertEquals(GRADE_DISPLAY_TYPE_LETTER, $cat3->grade_item->display); 602 $this->assertEquals(3, $cat3->grade_item->decimals); 603 $this->assertGreaterThanOrEqual($cat3->grade_item->hidden, time()); 604 $this->assertGreaterThanOrEqual($cat3->grade_item->locktime, time()); 605 $this->assertEquals(1, $cat3->grade_item->weightoverride); 606 // Coefficient is converted to percentage. 607 $this->assertEquals(0.2, $cat3->grade_item->aggregationcoef2); 608 $this->assertEquals($record2->id, $cat3->parent); 609 } 610 611 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body