Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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  /**
  18   * @package    core_grades
  19   * @category   phpunit
  20   * @copyright  nicolas@moodle.com
  21   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22   */
  23  
  24  defined('MOODLE_INTERNAL') || die();
  25  
  26  require_once (__DIR__.'/fixtures/lib.php');
  27  
  28  class core_grade_item_testcase extends grade_base_testcase {
  29      public function test_grade_item() {
  30          $this->sub_test_grade_item_construct();
  31          $this->sub_test_grade_item_insert();
  32          $this->sub_test_grade_item_delete();
  33          $this->sub_test_grade_item_update();
  34          $this->sub_test_grade_item_load_scale();
  35          $this->sub_test_grade_item_load_outcome();
  36          $this->sub_test_grade_item_qualifies_for_regrading();
  37          $this->sub_test_grade_item_force_regrading();
  38          $this->sub_test_grade_item_fetch();
  39          $this->sub_test_grade_item_fetch_all();
  40          $this->sub_test_grade_item_get_all_finals();
  41          $this->sub_test_grade_item_get_final();
  42          $this->sub_test_grade_item_get_sortorder();
  43          $this->sub_test_grade_item_set_sortorder();
  44          $this->sub_test_grade_item_move_after_sortorder();
  45          $this->sub_test_grade_item_get_name_escaped();
  46          $this->sub_test_grade_item_get_name_unescaped();
  47          $this->sub_test_grade_item_set_parent();
  48          $this->sub_test_grade_item_get_parent_category();
  49          $this->sub_test_grade_item_load_parent_category();
  50          $this->sub_test_grade_item_get_item_category();
  51          $this->sub_test_grade_item_load_item_category();
  52          $this->sub_test_grade_item_regrade_final_grades();
  53          $this->sub_test_grade_item_adjust_raw_grade();
  54          $this->sub_test_grade_item_rescale_grades_keep_percentage();
  55          $this->sub_test_grade_item_set_locked();
  56          $this->sub_test_grade_item_is_locked();
  57          $this->sub_test_grade_item_set_hidden();
  58          $this->sub_test_grade_item_is_hidden();
  59          $this->sub_test_grade_item_is_category_item();
  60          $this->sub_test_grade_item_is_course_item();
  61          $this->sub_test_grade_item_fetch_course_item();
  62          $this->sub_test_grade_item_depends_on();
  63          $this->sub_test_refresh_grades();
  64          $this->sub_test_grade_item_is_calculated();
  65          $this->sub_test_grade_item_set_calculation();
  66          $this->sub_test_grade_item_get_calculation();
  67          $this->sub_test_grade_item_compute();
  68          $this->sub_test_update_final_grade();
  69          $this->sub_test_grade_item_can_control_visibility();
  70          $this->sub_test_grade_item_fix_sortorder();
  71          $this->sub_test_grade_item_created_event();
  72          $this->sub_test_grade_item_updated_event();
  73      }
  74  
  75      protected function sub_test_grade_item_construct() {
  76          $params = new stdClass();
  77  
  78          $params->courseid = $this->courseid;
  79          $params->categoryid = $this->grade_categories[1]->id;
  80          $params->itemname = 'unittestgradeitem4';
  81          $params->itemtype = 'mod';
  82          $params->itemmodule = 'database';
  83          $params->iteminfo = 'Grade item used for unit testing';
  84  
  85          $grade_item = new grade_item($params, false);
  86  
  87          $this->assertEquals($params->courseid, $grade_item->courseid);
  88          $this->assertEquals($params->categoryid, $grade_item->categoryid);
  89          $this->assertEquals($params->itemmodule, $grade_item->itemmodule);
  90      }
  91  
  92      protected function sub_test_grade_item_insert() {
  93          $grade_item = new grade_item();
  94          $this->assertTrue(method_exists($grade_item, 'insert'));
  95  
  96          $grade_item->courseid = $this->courseid;
  97          $grade_item->categoryid = $this->grade_categories[1]->id;
  98          $grade_item->itemname = 'unittestgradeitem4';
  99          $grade_item->itemtype = 'mod';
 100          $grade_item->itemmodule = 'quiz';
 101          $grade_item->iteminfo = 'Grade item used for unit testing';
 102  
 103          $grade_item->insert();
 104  
 105          $last_grade_item = end($this->grade_items);
 106  
 107          $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
 108          $this->assertEquals(18, $grade_item->sortorder);
 109  
 110          // Keep our reference collection the same as what is in the database.
 111          $this->grade_items[] = $grade_item;
 112      }
 113  
 114      protected function sub_test_grade_item_delete() {
 115          global $DB;
 116          $grade_item = new grade_item($this->grade_items[7], false); // Use a grade item not touched by previous (or future) tests.
 117          $this->assertTrue(method_exists($grade_item, 'delete'));
 118  
 119          // Add two files.
 120          $dummy = array(
 121              'contextid' => $grade_item->get_context()->id,
 122              'component' => GRADE_FILE_COMPONENT,
 123              'filearea' => GRADE_HISTORY_FEEDBACK_FILEAREA,
 124              'itemid' => 1,
 125              'filepath' => '/',
 126              'filename' => 'feedback1.txt'
 127          );
 128  
 129          $fs = get_file_storage();
 130          $fs->create_file_from_string($dummy, '');
 131  
 132          $dummy['itemid'] = 2;
 133          $fs->create_file_from_string($dummy, '');
 134  
 135          $files = $fs->get_area_files($grade_item->get_context()->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 136          // Includes directories.
 137          $this->assertCount(4, $files);
 138  
 139          $this->assertTrue($grade_item->delete());
 140  
 141          $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
 142  
 143          $files = $fs->get_area_files($grade_item->get_context()->id, GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA);
 144          $this->assertEmpty($files);
 145  
 146          // Keep our reference collection the same as the database.
 147          unset($this->grade_items[7]);
 148      }
 149  
 150      protected function sub_test_grade_item_update() {
 151          global $DB;
 152          $grade_item = new grade_item($this->grade_items[0], false);
 153          $this->assertTrue(method_exists($grade_item, 'update'));
 154  
 155          $grade_item->iteminfo = 'Updated info for this unittest grade_item';
 156  
 157          $this->assertTrue($grade_item->update());
 158  
 159          $grade_item->grademin = 14;
 160          $this->assertTrue($grade_item->qualifies_for_regrading());
 161          $this->assertTrue($grade_item->update());
 162  
 163          $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
 164          $this->assertEquals($grade_item->iteminfo, $iteminfo);
 165      }
 166  
 167      protected function sub_test_grade_item_load_scale() {
 168          $grade_item = new grade_item($this->grade_items[2], false);
 169          $this->assertTrue(method_exists($grade_item, 'load_scale'));
 170          $scale = $grade_item->load_scale();
 171          $this->assertFalse(empty($grade_item->scale));
 172          $this->assertEquals($scale->id, $this->grade_items[2]->scaleid);
 173      }
 174  
 175      protected function sub_test_grade_item_load_outcome() {
 176          $grade_item = new grade_item($this->grade_items[0], false);
 177          $this->assertTrue(method_exists($grade_item, 'load_outcome'));
 178          // TODO: add tests.
 179      }
 180  
 181      protected function sub_test_grade_item_qualifies_for_regrading() {
 182          $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
 183          $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
 184  
 185          $this->assertFalse($grade_item->qualifies_for_regrading());
 186  
 187          $grade_item->iteminfo = 'Updated info for this unittest grade_item';
 188  
 189          $this->assertFalse($grade_item->qualifies_for_regrading());
 190  
 191          $grade_item->grademin = 14;
 192  
 193          $this->assertTrue($grade_item->qualifies_for_regrading());
 194      }
 195  
 196      protected function sub_test_grade_item_force_regrading() {
 197          $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
 198          $this->assertTrue(method_exists($grade_item, 'force_regrading'));
 199  
 200          $this->assertEquals(0, $grade_item->needsupdate);
 201  
 202          $grade_item->force_regrading();
 203          $this->assertEquals(1, $grade_item->needsupdate);
 204          $grade_item->update_from_db();
 205          $this->assertEquals(1, $grade_item->needsupdate);
 206      }
 207  
 208      protected function sub_test_grade_item_fetch() {
 209          $grade_item = new grade_item();
 210          $this->assertTrue(method_exists($grade_item, 'fetch'));
 211  
 212          // Not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading().
 213          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
 214          $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
 215          $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
 216  
 217          $grade_item = grade_item::fetch(array('itemtype'=>$this->grade_items[1]->itemtype, 'itemmodule'=>$this->grade_items[1]->itemmodule));
 218          $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
 219          $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
 220      }
 221  
 222      protected function sub_test_grade_item_fetch_all() {
 223          $grade_item = new grade_item();
 224          $this->assertTrue(method_exists($grade_item, 'fetch_all'));
 225  
 226          $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
 227          $this->assertEquals(count($this->grade_items), count($grade_items)-1); // -1 to account for the course grade item.
 228      }
 229  
 230      // Retrieve all final scores for a given grade_item.
 231      protected function sub_test_grade_item_get_all_finals() {
 232          $grade_item = new grade_item($this->grade_items[0], false);
 233          $this->assertTrue(method_exists($grade_item, 'get_final'));
 234  
 235          $final_grades = $grade_item->get_final();
 236          $this->assertEquals(3, count($final_grades));
 237      }
 238  
 239  
 240      // Retrieve all final scores for a specific userid.
 241      protected function sub_test_grade_item_get_final() {
 242          $grade_item = new grade_item($this->grade_items[0], false);
 243          $this->assertTrue(method_exists($grade_item, 'get_final'));
 244          $final_grade = $grade_item->get_final($this->user[1]->id);
 245          $this->assertEquals($this->grade_grades[0]->finalgrade, $final_grade->finalgrade);
 246      }
 247  
 248      protected function sub_test_grade_item_get_sortorder() {
 249          $grade_item = new grade_item($this->grade_items[0], false);
 250          $this->assertTrue(method_exists($grade_item, 'get_sortorder'));
 251          $sortorder = $grade_item->get_sortorder();
 252          $this->assertEquals($this->grade_items[0]->sortorder, $sortorder);
 253      }
 254  
 255      protected function sub_test_grade_item_set_sortorder() {
 256          $grade_item = new grade_item($this->grade_items[0], false);
 257          $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
 258          $grade_item->set_sortorder(999);
 259          $this->assertEquals($grade_item->sortorder, 999);
 260      }
 261  
 262      protected function sub_test_grade_item_move_after_sortorder() {
 263          $grade_item = new grade_item($this->grade_items[0], false);
 264          $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
 265          $grade_item->move_after_sortorder(5);
 266          $this->assertEquals($grade_item->sortorder, 6);
 267  
 268          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
 269          $this->assertEquals($grade_item->sortorder, 6);
 270  
 271          $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
 272          $this->assertEquals($after->sortorder, 8);
 273      }
 274  
 275      /**
 276       * Tests the getter of the item name with escaped HTML.
 277       */
 278      protected function sub_test_grade_item_get_name_escaped() {
 279          $gradeitem = new grade_item($this->grade_items[0], false);
 280          $this->assertTrue(method_exists($gradeitem, 'get_name'));
 281          $this->assertEquals(format_string($this->grade_items[0]->itemname, true, ['escape' => true]),
 282              $gradeitem->get_name(false, true));
 283      }
 284  
 285      /**
 286       * Tests the getter of the item name with unescaped HTML.
 287       */
 288      protected function sub_test_grade_item_get_name_unescaped() {
 289          $gradeitem = new grade_item($this->grade_items[0], false);
 290          $this->assertTrue(method_exists($gradeitem, 'get_name'));
 291          $this->assertEquals(format_string($this->grade_items[0]->itemname, true, ['escape' => false]),
 292              $gradeitem->get_name(false, false));
 293      }
 294  
 295      protected function sub_test_grade_item_set_parent() {
 296          $grade_item = new grade_item($this->grade_items[0], false);
 297          $this->assertTrue(method_exists($grade_item, 'set_parent'));
 298  
 299          $old = $grade_item->get_parent_category();
 300          $new = new grade_category($this->grade_categories[3], false);
 301          $new_item = $new->get_grade_item();
 302  
 303          $this->assertTrue($grade_item->set_parent($new->id));
 304  
 305          $new_item->update_from_db();
 306          $grade_item->update_from_db();
 307  
 308          $this->assertEquals($grade_item->categoryid, $new->id);
 309      }
 310  
 311      protected function sub_test_grade_item_get_parent_category() {
 312          $grade_item = new grade_item($this->grade_items[0], false);
 313          $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
 314  
 315          $category = $grade_item->get_parent_category();
 316          $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
 317      }
 318  
 319      protected function sub_test_grade_item_load_parent_category() {
 320          $grade_item = new grade_item($this->grade_items[0], false);
 321          $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
 322  
 323          $category = $grade_item->load_parent_category();
 324          $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
 325          $this->assertEquals($this->grade_categories[1]->fullname, $grade_item->parent_category->fullname);
 326      }
 327  
 328      protected function sub_test_grade_item_get_item_category() {
 329          $grade_item = new grade_item($this->grade_items[3], false);
 330          $this->assertTrue(method_exists($grade_item, 'get_item_category'));
 331  
 332          $category = $grade_item->get_item_category();
 333          $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
 334      }
 335  
 336      protected function sub_test_grade_item_load_item_category() {
 337          $grade_item = new grade_item($this->grade_items[3], false);
 338          $this->assertTrue(method_exists($grade_item, 'load_item_category'));
 339  
 340          $category = $grade_item->load_item_category();
 341          $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
 342          $this->assertEquals($this->grade_categories[0]->fullname, $grade_item->item_category->fullname);
 343      }
 344  
 345      protected function sub_test_grade_item_regrade_final_grades() {
 346          $grade_item = new grade_item($this->grade_items[0], false);
 347          $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
 348          $this->assertEquals(true, $grade_item->regrade_final_grades());
 349          // TODO: add more tests.
 350      }
 351  
 352      protected function sub_test_grade_item_adjust_raw_grade() {
 353          $grade_item = new grade_item($this->grade_items[2], false); // Anything but assignment module!
 354          $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
 355  
 356          $grade_raw = new stdClass();
 357          $grade_raw->rawgrade = 40;
 358          $grade_raw->grademax = 100;
 359          $grade_raw->grademin = 0;
 360  
 361          $grade_item->gradetype = GRADE_TYPE_VALUE;
 362          $grade_item->multfactor = 1;
 363          $grade_item->plusfactor = 0;
 364          $grade_item->grademax = 50;
 365          $grade_item->grademin = 0;
 366  
 367          $original_grade_raw  = clone($grade_raw);
 368          $original_grade_item = clone($grade_item);
 369  
 370          $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 371  
 372          // Try a larger maximum grade.
 373          $grade_item->grademax = 150;
 374          $grade_item->grademin = 0;
 375          $this->assertEquals(60, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 376  
 377          // Try larger minimum grade.
 378          $grade_item->grademin = 50;
 379  
 380          $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 381  
 382          // Rescaling from a small scale (0-50) to a larger scale (0-100).
 383          $grade_raw->grademax = 50;
 384          $grade_raw->grademin = 0;
 385          $grade_item->grademax = 100;
 386          $grade_item->grademin = 0;
 387  
 388          $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 389  
 390          // Rescaling from a small scale (0-50) to a larger scale with offset (40-100).
 391          $grade_item->grademax = 100;
 392          $grade_item->grademin = 40;
 393  
 394          $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 395  
 396          // Try multfactor and plusfactor.
 397          $grade_raw = clone($original_grade_raw);
 398          $grade_item = clone($original_grade_item);
 399          $grade_item->multfactor = 1.23;
 400          $grade_item->plusfactor = 3;
 401  
 402          $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
 403  
 404          // Try multfactor below 0 and a negative plusfactor.
 405          $grade_raw = clone($original_grade_raw);
 406          $grade_item = clone($original_grade_item);
 407          $grade_item->multfactor = 0.23;
 408          $grade_item->plusfactor = -3;
 409  
 410          $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
 411      }
 412  
 413      protected function sub_test_grade_item_rescale_grades_keep_percentage() {
 414          global $DB;
 415          $gradeitem = new grade_item($this->grade_items[10], false); // 10 is the manual grade item.
 416  
 417          // Create some grades to go with the grade item.
 418          $gradeids = array();
 419          $grade = new stdClass();
 420          $grade->itemid = $gradeitem->id;
 421          $grade->userid = $this->user[2]->id;
 422          $grade->finalgrade = 10;
 423          $grade->rawgrademax = $gradeitem->grademax;
 424          $grade->rawgrademin = $gradeitem->grademin;
 425          $grade->timecreated = time();
 426          $grade->timemodified = time();
 427          $gradeids[] = $DB->insert_record('grade_grades', $grade);
 428  
 429          $grade->userid = $this->user[3]->id;
 430          $grade->finalgrade = 50;
 431          $grade->rawgrademax = $gradeitem->grademax;
 432          $grade->rawgrademin = $gradeitem->grademin;
 433          $gradeids[] = $DB->insert_record('grade_grades', $grade);
 434  
 435          // Run the function.
 436          $gradeitem->grademax = 33;
 437          $gradeitem->grademin = 3;
 438          $gradeitem->update();
 439          $gradeitem->rescale_grades_keep_percentage(0, 100, 3, 33, 'test');
 440  
 441          // Check that the grades were updated to match the grade item.
 442          $grade = $DB->get_record('grade_grades', array('id' => $gradeids[0]));
 443          $this->assertEqualsWithDelta($gradeitem->grademax, $grade->rawgrademax, 0.0001, 'Max grade mismatch');
 444          $this->assertEqualsWithDelta($gradeitem->grademin, $grade->rawgrademin, 0.0001, 'Min grade mismatch');
 445          $this->assertEqualsWithDelta(6, $grade->finalgrade, 0.0001, 'Min grade mismatch');
 446  
 447          $grade = $DB->get_record('grade_grades', array('id' => $gradeids[1]));
 448          $this->assertEqualsWithDelta($gradeitem->grademax, $grade->rawgrademax, 0.0001, 'Max grade mismatch');
 449          $this->assertEqualsWithDelta($gradeitem->grademin, $grade->rawgrademin, 0.0001, 'Min grade mismatch');
 450          $this->assertEqualsWithDelta(18, $grade->finalgrade, 0.0001, 'Min grade mismatch');
 451      }
 452  
 453      protected function sub_test_grade_item_set_locked() {
 454          // Getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
 455          // also needs to have at least one grade_grade or $grade_item->get_final(1) returns null.
 456          // $grade_item = new grade_item($this->grade_items[8]);
 457          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
 458  
 459          $this->assertTrue(method_exists($grade_item, 'set_locked'));
 460  
 461          $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 462          $this->assertTrue(empty($grade_item->locked));// Not locked.
 463          $this->assertTrue(empty($grade_grade->locked));// Not locked.
 464  
 465          $this->assertTrue($grade_item->set_locked(true, true, false));
 466          $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 467  
 468          $this->assertFalse(empty($grade_item->locked));// Locked.
 469          $this->assertFalse(empty($grade_grade->locked)); // Individual grades should be locked too.
 470  
 471          $this->assertTrue($grade_item->set_locked(false, true, false));
 472          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 473  
 474          $this->assertTrue(empty($grade_item->locked));
 475          $this->assertTrue(empty($grade->locked)); // Individual grades should be unlocked too.
 476      }
 477  
 478      protected function sub_test_grade_item_is_locked() {
 479          $grade_item = new grade_item($this->grade_items[10], false);
 480          $this->assertTrue(method_exists($grade_item, 'is_locked'));
 481  
 482          $this->assertFalse($grade_item->is_locked());
 483          $this->assertFalse($grade_item->is_locked($this->user[1]->id));
 484          $this->assertTrue($grade_item->set_locked(true, true, false));
 485          $this->assertTrue($grade_item->is_locked());
 486          $this->assertTrue($grade_item->is_locked($this->user[1]->id));
 487      }
 488  
 489      protected function sub_test_grade_item_set_hidden() {
 490          $grade_item = new grade_item($this->grade_items[0], false);
 491          $this->assertTrue(method_exists($grade_item, 'set_hidden'));
 492  
 493          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 494          $this->assertEquals(0, $grade_item->hidden);
 495          $this->assertEquals(0, $grade->hidden);
 496  
 497          $grade_item->set_hidden(666, true);
 498          $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
 499  
 500          $this->assertEquals(666, $grade_item->hidden);
 501          $this->assertEquals(666, $grade->hidden);
 502      }
 503  
 504      protected function sub_test_grade_item_is_hidden() {
 505          $grade_item = new grade_item($this->grade_items[0], false);
 506          $this->assertTrue(method_exists($grade_item, 'is_hidden'));
 507  
 508          $this->assertFalse($grade_item->is_hidden());
 509          $this->assertFalse($grade_item->is_hidden(1));
 510  
 511          $grade_item->set_hidden(1);
 512          $this->assertTrue($grade_item->is_hidden());
 513          $this->assertTrue($grade_item->is_hidden(1));
 514  
 515          $grade_item->set_hidden(666);
 516          $this->assertFalse($grade_item->is_hidden());
 517          $this->assertFalse($grade_item->is_hidden(1));
 518  
 519          $grade_item->set_hidden(time()+666);
 520          $this->assertTrue($grade_item->is_hidden());
 521          $this->assertTrue($grade_item->is_hidden(1));
 522      }
 523  
 524      protected function sub_test_grade_item_is_category_item() {
 525          $grade_item = new grade_item($this->grade_items[3], false);
 526          $this->assertTrue(method_exists($grade_item, 'is_category_item'));
 527          $this->assertTrue($grade_item->is_category_item());
 528      }
 529  
 530      protected function sub_test_grade_item_is_course_item() {
 531          $grade_item = grade_item::fetch_course_item($this->courseid);
 532          $this->assertTrue(method_exists($grade_item, 'is_course_item'));
 533          $this->assertTrue($grade_item->is_course_item());
 534      }
 535  
 536      protected function sub_test_grade_item_fetch_course_item() {
 537          $grade_item = grade_item::fetch_course_item($this->courseid);
 538          $this->assertTrue(method_exists($grade_item, 'fetch_course_item'));
 539          $this->assertEquals($grade_item->itemtype, 'course');
 540      }
 541  
 542      protected function sub_test_grade_item_depends_on() {
 543          global $CFG;
 544  
 545          $origenableoutcomes = $CFG->enableoutcomes;
 546          $CFG->enableoutcomes = 0;
 547          $grade_item = new grade_item($this->grade_items[1], false);
 548  
 549          // Calculated grade dependency.
 550          $deps = $grade_item->depends_on();
 551          sort($deps, SORT_NUMERIC); // For comparison.
 552          $this->assertEquals(array($this->grade_items[0]->id), $deps);
 553  
 554          // Simulate depends on returns none when locked.
 555          $grade_item->locked = time();
 556          $grade_item->update();
 557          $deps = $grade_item->depends_on();
 558          sort($deps, SORT_NUMERIC); // For comparison.
 559          $this->assertEquals(array(), $deps);
 560  
 561          // Category dependency.
 562          $grade_item = new grade_item($this->grade_items[3], false);
 563          $deps = $grade_item->depends_on();
 564          sort($deps, SORT_NUMERIC); // For comparison.
 565          $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
 566          $this->assertEquals($res, $deps);
 567      }
 568  
 569      protected function scales_outcomes_test_grade_item_depends_on() {
 570          $CFG->enableoutcomes = 1;
 571          $origgradeincludescalesinaggregation = $CFG->grade_includescalesinaggregation;
 572          $CFG->grade_includescalesinaggregation = 1;
 573  
 574          // Scale item in category with $CFG->grade_includescalesinaggregation = 1.
 575          $grade_item = new grade_item($this->grade_items[14], false);
 576          $deps = $grade_item->depends_on();
 577          sort($deps, SORT_NUMERIC);
 578          $res = array($this->grade_items[16]->id);
 579          $this->assertEquals($res, $deps);
 580  
 581          // Scale item in category with $CFG->grade_includescalesinaggregation = 0.
 582          $CFG->grade_includescalesinaggregation = 0;
 583          $grade_item = new grade_item($this->grade_items[14], false);
 584          $deps = $grade_item->depends_on();
 585          $res = array();
 586          $this->assertEquals($res, $deps);
 587          $CFG->grade_includescalesinaggregation = 1;
 588  
 589          // Outcome item in category with outcomes disabled.
 590          $CFG->enableoutcomes = 0;
 591          $grade_item = new grade_item($this->grade_items[14], false);
 592          $deps = $grade_item->depends_on();
 593          sort($deps, SORT_NUMERIC);
 594          $res = array($this->grade_items[16]->id, $this->grade_items[17]->id);
 595          $this->assertEquals($res, $deps);
 596  
 597          $CFG->enableoutcomes = $origenableoutcomes;
 598          $CFG->grade_includescalesinaggregation = $origgradeincludescalesinaggregation;
 599      }
 600  
 601      protected function sub_test_refresh_grades() {
 602          // Testing with the grade item for a mod_assignment instance.
 603          $grade_item = new grade_item($this->grade_items[0], false);
 604          $this->assertTrue(method_exists($grade_item, 'refresh_grades'));
 605          $this->assertTrue($grade_item->refresh_grades());
 606  
 607          // Break the grade item and check error handling.
 608          $grade_item->iteminstance = 123456789;
 609          $this->assertFalse($grade_item->refresh_grades());
 610          $this->assertDebuggingCalled();
 611      }
 612  
 613      protected function sub_test_grade_item_is_calculated() {
 614          $grade_item = new grade_item($this->grade_items[1], false);
 615          $this->assertTrue(method_exists($grade_item, 'is_calculated'));
 616          $this->assertTrue($grade_item->is_calculated());
 617  
 618          $grade_item = new grade_item($this->grade_items[0], false);
 619          $this->assertFalse($grade_item->is_calculated());
 620      }
 621  
 622      protected function sub_test_grade_item_set_calculation() {
 623          $grade_item = new grade_item($this->grade_items[1], false);
 624          $this->assertTrue(method_exists($grade_item, 'set_calculation'));
 625          $grade_itemsource = new grade_item($this->grade_items[0], false);
 626  
 627          $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
 628  
 629          $this->assertTrue(!empty($grade_item->needsupdate));
 630          $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
 631      }
 632  
 633      protected function sub_test_grade_item_get_calculation() {
 634          $grade_item = new grade_item($this->grade_items[1], false);
 635          $this->assertTrue(method_exists($grade_item, 'get_calculation'));
 636          $grade_itemsource = new grade_item($this->grade_items[0], false);
 637  
 638          $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
 639  
 640          $formula = $grade_item->get_calculation();
 641          $this->assertTrue(!empty($grade_item->needsupdate));
 642          $this->assertEquals($denormalizedformula, $formula);
 643      }
 644  
 645      public function sub_test_grade_item_compute() {
 646          $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
 647          $this->assertTrue(method_exists($grade_item, 'compute'));
 648  
 649          // Check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades.
 650          $this->grade_grades[3] = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
 651          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
 652          $grade_grade->delete();
 653  
 654          $this->grade_grades[4] = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
 655          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
 656          $grade_grade->delete();
 657  
 658          $this->grade_grades[5] = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
 659          $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
 660          $grade_grade->delete();
 661  
 662          // Recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them.
 663          $grade_item->compute();
 664  
 665          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[3]->userid, 'itemid'=>$this->grade_grades[3]->itemid));
 666          $this->assertEquals($this->grade_grades[3]->finalgrade, $grade_grade->finalgrade);
 667  
 668          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[4]->userid, 'itemid'=>$this->grade_grades[4]->itemid));
 669          $this->assertEquals($this->grade_grades[4]->finalgrade, $grade_grade->finalgrade);
 670  
 671          $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[5]->userid, 'itemid'=>$this->grade_grades[5]->itemid));
 672          $this->assertEquals($this->grade_grades[5]->finalgrade, $grade_grade->finalgrade);
 673      }
 674  
 675      protected function sub_test_update_final_grade() {
 676  
 677          // MDL-31713 Check that min and max are set on the grade_grade instance
 678          // if the grade is overridden before the activity has supplied a grade.
 679          $min = 2;
 680          $max = 8;
 681  
 682          // Create a brand new grade item.
 683          $grade_item = new grade_item();
 684          $this->assertTrue(method_exists($grade_item, 'insert'));
 685  
 686          $grade_item->courseid = $this->courseid;
 687          $grade_item->categoryid = $this->grade_categories[1]->id;
 688          $grade_item->itemname = 'brand new unit test grade item';
 689          $grade_item->itemtype = 'mod';
 690          $grade_item->itemmodule = 'quiz';
 691          $grade_item->iteminfo = 'Grade item used for unit testing';
 692          $grade_item->iteminstance = $this->activities[7]->id;
 693          $grade_item->grademin = $min;
 694          $grade_item->grademax = $max;
 695          $grade_item->insert();
 696  
 697          // Override the student grade.
 698          $grade_item->update_final_grade($this->user[1]->id, 7, 'gradebook', '', FORMAT_MOODLE);
 699  
 700          // Check the student's grade has the correct min and max grade.
 701          $grade_grade = grade_grade::fetch(array('userid'=>$this->user[1]->id, 'itemid'=>$grade_item->id));
 702          $this->assertEquals($min, $grade_grade->rawgrademin);
 703          $this->assertEquals($max, $grade_grade->rawgrademax);
 704      }
 705  
 706      protected function sub_test_grade_item_can_control_visibility() {
 707          // Grade item 0 == Course module 0 == Assignment.
 708          $grade_item = new grade_item($this->grade_items[0], false);
 709          $this->assertTrue($grade_item->can_control_visibility());
 710  
 711          // Grade item  == Course module 7 == Quiz.
 712          $grade_item = new grade_item($this->grade_items[11], false);
 713          $this->assertFalse($grade_item->can_control_visibility());
 714      }
 715  
 716      /**
 717       * Test the {@link grade_item::fix_duplicate_sortorder() function with
 718       * faked duplicate sortorder data.
 719       */
 720      public function sub_test_grade_item_fix_sortorder() {
 721          global $DB;
 722  
 723          $this->resetAfterTest(true);
 724  
 725          // Each set is used for filling the db with fake data and will be representing the result of query:
 726          // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
 727          $testsets = array(
 728              // Items that need no action.
 729              array(1,2,3),
 730              array(5,6,7),
 731              array(7,6,1,3,2,5),
 732              // Items with sortorder duplicates
 733              array(1,2,2,3,3,4,5),
 734              // Only one sortorder duplicate.
 735              array(1,1),
 736              array(3,3),
 737              // Non-sequential sortorders with one or multiple duplicates.
 738              array(3,3,7,5,6,6,9,10,8,3),
 739              array(7,7,3),
 740              array(3,4,5,3,5,4,7,1)
 741          );
 742          $origsequence = array();
 743  
 744          // Generate the data and remember the initial sequence or items.
 745          foreach ($testsets as $testset) {
 746              $course = $this->getDataGenerator()->create_course();
 747              foreach ($testset as $sortorder) {
 748                  $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
 749              }
 750              $DB->get_records('grade_items');
 751              $origsequence[$course->id] = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
 752                  "WHERE courseid = ? ORDER BY sortorder, id", array($course->id));
 753          }
 754  
 755          $duplicatedetectionsql = "SELECT courseid, sortorder
 756                                      FROM {grade_items}
 757                                  WHERE courseid = :courseid
 758                                  GROUP BY courseid, sortorder
 759                                    HAVING COUNT(id) > 1";
 760  
 761          // Do the work.
 762          foreach ($origsequence as $courseid => $ignore) {
 763              grade_item::fix_duplicate_sortorder($courseid);
 764              // Verify that no duplicates are left in the database.
 765              $dupes = $DB->record_exists_sql($duplicatedetectionsql, array('courseid' => $courseid));
 766              $this->assertFalse($dupes);
 767          }
 768  
 769          // Verify that sequences are exactly the same as they were before upgrade script.
 770          $idx = 0;
 771          foreach ($origsequence as $courseid => $sequence) {
 772              if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
 773                  // If there were no duplicates for this course verify that sortorders are not modified.
 774                  $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
 775                  $this->assertEquals($testsets[$idx], $newsortorders);
 776              }
 777              $newsequence = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
 778                  "WHERE courseid = ? ORDER BY sortorder, id", array($courseid));
 779              $this->assertEquals($sequence, $newsequence,
 780                      "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
 781              $idx++;
 782          }
 783      }
 784  
 785      /**
 786       * Populate some fake grade items into the database with specified
 787       * sortorder and course id.
 788       *
 789       * NOTE: This function doesn't make much attempt to respect the
 790       * gradebook internals, its simply used to fake some data for
 791       * testing the upgradelib function. Please don't use it for other
 792       * purposes.
 793       *
 794       * @param int $courseid id of course
 795       * @param int $sortorder numeric sorting order of item
 796       * @return stdClass grade item object from the database.
 797       */
 798      private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
 799          global $DB, $CFG;
 800          require_once($CFG->libdir.'/gradelib.php');
 801  
 802          $item = new stdClass();
 803          $item->courseid = $courseid;
 804          $item->sortorder = $sortorder;
 805          $item->gradetype = GRADE_TYPE_VALUE;
 806          $item->grademin = 30;
 807          $item->grademax = 110;
 808          $item->itemnumber = 1;
 809          $item->iteminfo = '';
 810          $item->timecreated = time();
 811          $item->timemodified = time();
 812  
 813          $item->id = $DB->insert_record('grade_items', $item);
 814  
 815          return $DB->get_record('grade_items', array('id' => $item->id));
 816      }
 817  
 818      public function test_set_aggregation_fields_for_aggregation() {
 819          $course = $this->getDataGenerator()->create_course();
 820          $gi = new grade_item(array('courseid' => $course->id, 'itemtype' => 'manual'), false);
 821  
 822          $methods = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN, GRADE_AGGREGATE_MAX,
 823              GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
 824              GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_SUM);
 825  
 826          // Switching from and to the same aggregation using the defaults.
 827          foreach ($methods as $method) {
 828              $defaults = grade_category::get_default_aggregation_coefficient_values($method);
 829              $gi->aggregationcoef = $defaults['aggregationcoef'];
 830              $gi->aggregationcoef2 = $defaults['aggregationcoef2'];
 831              $gi->weightoverride = $defaults['weightoverride'];
 832              $this->assertFalse($gi->set_aggregation_fields_for_aggregation($method, $method));
 833              $this->assertEquals($defaults['aggregationcoef'], $gi->aggregationcoef);
 834              $this->assertEquals($defaults['aggregationcoef2'], $gi->aggregationcoef2);
 835              $this->assertEquals($defaults['weightoverride'], $gi->weightoverride);
 836          }
 837  
 838          // Extra credit is kept across aggregation methods that support it.
 839          foreach ($methods as $from) {
 840              $fromsupportsec = grade_category::aggregation_uses_extracredit($from);
 841              $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 842  
 843              foreach ($methods as $to) {
 844                  $tosupportsec = grade_category::aggregation_uses_extracredit($to);
 845                  $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 846  
 847                  // Set the item to be extra credit, if supported.
 848                  if ($fromsupportsec) {
 849                      $gi->aggregationcoef = 1;
 850                  } else {
 851                      $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 852                  }
 853  
 854                  // We ignore those fields, we know it is never used for extra credit.
 855                  $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
 856                  $gi->weightoverride = $todefaults['weightoverride'];
 857  
 858                  if ($fromsupportsec && $tosupportsec) {
 859                      $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 860                      $this->assertEquals(1, $gi->aggregationcoef);
 861  
 862                  } else if ($fromsupportsec && !$tosupportsec) {
 863                      if ($to == GRADE_AGGREGATE_WEIGHTED_MEAN) {
 864                          // Special case, aggregationcoef is used but for weights.
 865                          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 866                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 867                      } else {
 868                          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 869                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 870                      }
 871                  } else {
 872                      // The source does not support extra credit, everything will be reset.
 873                      if (($from == GRADE_AGGREGATE_WEIGHTED_MEAN || $to == GRADE_AGGREGATE_WEIGHTED_MEAN) && $from != $to) {
 874                          // Special case, aggregationcoef is used but for weights.
 875                          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 876                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 877                      } else {
 878                          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 879                          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 880                      }
 881                  }
 882              }
 883          }
 884  
 885          // Extra credit can be higher than one for GRADE_AGGREGATE_EXTRACREDIT_MEAN, but will be normalised for others.
 886          $from = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
 887          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 888  
 889          foreach ($methods as $to) {
 890              if (!grade_category::aggregation_uses_extracredit($to)) {
 891                  continue;
 892              }
 893  
 894              $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 895              $gi->aggregationcoef = 8;
 896  
 897              // Ignore those fields, they are not used for extra credit.
 898              $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
 899              $gi->weightoverride = $todefaults['weightoverride'];
 900  
 901              if ($to == $from) {
 902                  $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 903                  $this->assertEquals(8, $gi->aggregationcoef);
 904              } else {
 905                  $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 906                  $this->assertEquals(1, $gi->aggregationcoef);
 907              }
 908          }
 909  
 910          // Weights are reset.
 911          $from = GRADE_AGGREGATE_SUM;
 912          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 913  
 914          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 915          $gi->aggregationcoef2 = 0.321;
 916          $gi->weightoverride = $fromdefaults['weightoverride'];
 917  
 918          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 919          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 920  
 921          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 922          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 923          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 924          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 925  
 926          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 927          $gi->aggregationcoef2 = 0.321;
 928          $gi->weightoverride = $fromdefaults['weightoverride'];
 929  
 930          $to = GRADE_AGGREGATE_SUM;
 931          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 932  
 933          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 934          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 935          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 936          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 937  
 938          // Weight is kept when using SUM with weight override.
 939          $from = GRADE_AGGREGATE_SUM;
 940          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 941  
 942          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 943          $gi->aggregationcoef2 = 0.321;
 944          $gi->weightoverride = 1;
 945  
 946          $to = GRADE_AGGREGATE_SUM;
 947          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 948  
 949          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 950          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 951          $this->assertEquals(0.321, $gi->aggregationcoef2);
 952          $this->assertEquals(1, $gi->weightoverride);
 953  
 954          $gi->aggregationcoef2 = 0.321;
 955          $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
 956          $gi->weightoverride = 1;
 957  
 958          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 959          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 960  
 961          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 962          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 963          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 964          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 965  
 966          // Weight is kept when staying in weighted mean.
 967          $from = GRADE_AGGREGATE_WEIGHTED_MEAN;
 968          $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
 969  
 970          $gi->aggregationcoef = 18;
 971          $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
 972          $gi->weightoverride = $fromdefaults['weightoverride'];
 973  
 974          $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
 975          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 976  
 977          $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 978          $this->assertEquals(18, $gi->aggregationcoef);
 979          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 980          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 981  
 982          $gi->aggregationcoef = 18;
 983          $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
 984          $gi->weightoverride = $fromdefaults['weightoverride'];
 985  
 986          $to = GRADE_AGGREGATE_SUM;
 987          $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
 988  
 989          $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
 990          $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
 991          $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
 992          $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
 993      }
 994  
 995      /**
 996       * Test that grade item event triggered when a grade item is created.
 997       */
 998      protected function sub_test_grade_item_created_event() {
 999          $sink = $this->redirectEvents();
1000  
1001          $gradeitem = new grade_item();
1002  
1003          $gradeitem->courseid = $this->courseid;
1004          $gradeitem->categoryid = $this->grade_categories[1]->id;
1005          $gradeitem->itemname = 'unittestgradeitem4';
1006          $gradeitem->itemtype = 'mod';
1007          $gradeitem->itemmodule = 'quiz';
1008          $gradeitem->iteminfo = 'Grade item used for unit testing';
1009  
1010          $gradeitem->insert();
1011  
1012          $result = $sink->get_events();
1013          $sink->close();
1014  
1015          $this->assertCount(1, $result);
1016  
1017          $event = reset($result);
1018          $this->assertEventContextNotUsed($event);
1019          $this->assertInstanceOf('core\event\grade_item_created', $event);
1020  
1021          $eventgradeitem = $event->get_grade_item();
1022  
1023          $this->assertInstanceOf('grade_item', $eventgradeitem);
1024          $this->assertEquals($gradeitem->id, $eventgradeitem->id);
1025          $this->assertEquals($gradeitem->itemname, $event->other['itemname']);
1026          $this->assertEquals($gradeitem->itemtype, $event->other['itemtype']);
1027          $this->assertEquals($gradeitem->itemmodule, $event->other['itemmodule']);
1028      }
1029  
1030      /**
1031       * Test that grade item event triggered when a grade item is updated.
1032       */
1033      protected function sub_test_grade_item_updated_event() {
1034          $gradeitem = new grade_item();
1035  
1036          $gradeitem->courseid = $this->courseid;
1037          $gradeitem->categoryid = $this->grade_categories[1]->id;
1038          $gradeitem->itemname = 'unittestgradeitem4';
1039          $gradeitem->itemtype = 'mod';
1040          $gradeitem->itemmodule = 'quiz';
1041          $gradeitem->iteminfo = 'Grade item used for unit testing';
1042          $gradeitem->insert();
1043  
1044          $sink = $this->redirectEvents();
1045  
1046          $gradeitem->itemname = 'updatedname';
1047          $gradeitem->update();
1048  
1049          $result = $sink->get_events();
1050          $sink->close();
1051  
1052          $this->assertCount(1, $result);
1053  
1054          $event = reset($result);
1055          $this->assertInstanceOf('core\event\grade_item_updated', $event);
1056          $this->assertEventContextNotUsed($event);
1057  
1058          $eventgradeitem = $event->get_grade_item();
1059  
1060          $this->assertInstanceOf('grade_item', $eventgradeitem);
1061          $this->assertEquals($gradeitem->id, $eventgradeitem->id);
1062          $this->assertEquals($gradeitem->itemtype, $event->other['itemtype']);
1063          $this->assertEquals($gradeitem->itemmodule, $event->other['itemmodule']);
1064          $this->assertEquals('updatedname', $event->other['itemname']);
1065      }
1066  
1067  
1068      /**
1069       * Test grade item duplication expecting success.
1070       */
1071      public function test_grade_duplicate_grade_item_success() {
1072          $cat = new grade_category();
1073          $cat->courseid = $this->courseid;
1074          $cat->fullname = 'Grade category';
1075          $cat->insert();
1076  
1077          // Method exists.
1078          $gi = new grade_item();
1079          $this->assertTrue(method_exists($gi, 'duplicate'));
1080  
1081          // Grade item is inserted and valid for duplication.
1082          $gi->courseid = $this->courseid;
1083          $gi->categoryid = $cat->id;
1084          $gi->itemtype = 'manual';
1085          $gi->itemname = 'Grade Item 1';
1086          $gi->idnumber = '1000';
1087          $gi->insert();
1088          $gi2 = $gi->duplicate();
1089  
1090          $this->assertEquals($gi->courseid, $gi2->courseid);
1091          $this->assertEquals($gi->categoryid, $gi2->categoryid);
1092          $this->assertEquals($gi->itemtype, $gi2->itemtype);
1093          $this->assertEquals($gi->gradetype, $gi2->gradetype);
1094          $this->assertEquals($gi->grademax, $gi2->grademax);
1095          $this->assertEquals($gi->grademin, $gi2->grademin);
1096          $this->assertEquals($gi->gradepass, $gi2->gradepass);
1097          $this->assertEquals($gi->display, $gi2->display);
1098          $this->assertEquals($gi->decimals, $gi2->decimals);
1099          $this->assertEquals($gi->hidden, $gi2->hidden);
1100          $this->assertEquals($gi->weightoverride, $gi2->weightoverride);
1101  
1102          $this->assertNotEquals($gi->id, $gi2->id);
1103          $this->assertNotEquals($gi->idnumber, $gi2->idnumber);
1104          $this->assertNotEquals($gi->sortorder, $gi2->sortorder);
1105          $this->assertNotEquals($gi->itemname, $gi2->itemname);
1106      }
1107  
1108      /**
1109       * Test grade item duplication exception expected with incomplete grade item.
1110       */
1111      public function test_grade_duplicate_grade_item_incomplete() {
1112          // Grade item is not valid because it is empty.
1113          $gi = new grade_item();
1114          $gi->courseid = $this->courseid;
1115          $this->expectException("moodle_exception");
1116          $gi2 = $gi->duplicate();
1117      }
1118  
1119      /**
1120       * Test grade item duplication exception expected because item must be in db.
1121       */
1122      public function test_grade_duplicate_grade_item_not_in_db() {
1123          $cat = new grade_category();
1124          $cat->courseid = $this->courseid;
1125          $cat->fullname = 'Grade category';
1126          $cat->insert();
1127  
1128          // Grade item is valid for insertion but is not inserted into db.
1129          // Duplicate method throws an exception.
1130          $gi = new grade_item();
1131          $gi->courseid = $this->courseid;
1132          $gi->categoryid = $cat->id;
1133          $gi->itemtype = 'manual';
1134          $gi->itemname = 'Grade Item 1';
1135          $gi->idnumber = '1000';
1136  
1137          $this->expectException("moodle_exception");
1138          $gi2 = $gi->duplicate();
1139      }
1140  }