ade1d890f82511a6f79b0530ffdb20eee19d36b9
[moodle.git] / lib / grade / tests / grade_item_test.php
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/>.
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  */
24 defined('MOODLE_INTERNAL') || die();
26 require_once(__DIR__.'/fixtures/lib.php');
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();
46         $this->sub_test_grade_item_set_parent();
47         $this->sub_test_grade_item_get_parent_category();
48         $this->sub_test_grade_item_load_parent_category();
49         $this->sub_test_grade_item_get_item_category();
50         $this->sub_test_grade_item_load_item_category();
51         $this->sub_test_grade_item_regrade_final_grades();
52         $this->sub_test_grade_item_adjust_raw_grade();
53         $this->sub_test_grade_item_rescale_grades_keep_percentage();
54         $this->sub_test_grade_item_set_locked();
55         $this->sub_test_grade_item_is_locked();
56         $this->sub_test_grade_item_set_hidden();
57         $this->sub_test_grade_item_is_hidden();
58         $this->sub_test_grade_item_is_category_item();
59         $this->sub_test_grade_item_is_course_item();
60         $this->sub_test_grade_item_fetch_course_item();
61         $this->sub_test_grade_item_depends_on();
62         $this->sub_test_refresh_grades();
63         $this->sub_test_grade_item_is_calculated();
64         $this->sub_test_grade_item_set_calculation();
65         $this->sub_test_grade_item_get_calculation();
66         $this->sub_test_grade_item_compute();
67         $this->sub_test_update_final_grade();
68         $this->sub_test_grade_item_can_control_visibility();
69         $this->sub_test_grade_item_fix_sortorder();
70     }
72     protected function sub_test_grade_item_construct() {
73         $params = new stdClass();
75         $params->courseid = $this->courseid;
76         $params->categoryid = $this->grade_categories[1]->id;
77         $params->itemname = 'unittestgradeitem4';
78         $params->itemtype = 'mod';
79         $params->itemmodule = 'database';
80         $params->iteminfo = 'Grade item used for unit testing';
82         $grade_item = new grade_item($params, false);
84         $this->assertEquals($params->courseid, $grade_item->courseid);
85         $this->assertEquals($params->categoryid, $grade_item->categoryid);
86         $this->assertEquals($params->itemmodule, $grade_item->itemmodule);
87     }
89     protected function sub_test_grade_item_insert() {
90         $grade_item = new grade_item();
91         $this->assertTrue(method_exists($grade_item, 'insert'));
93         $grade_item->courseid = $this->courseid;
94         $grade_item->categoryid = $this->grade_categories[1]->id;
95         $grade_item->itemname = 'unittestgradeitem4';
96         $grade_item->itemtype = 'mod';
97         $grade_item->itemmodule = 'quiz';
98         $grade_item->iteminfo = 'Grade item used for unit testing';
100         $grade_item->insert();
102         $last_grade_item = end($this->grade_items);
104         $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
105         $this->assertEquals(18, $grade_item->sortorder);
107         // Keep our reference collection the same as what is in the database.
108         $this->grade_items[] = $grade_item;
109     }
111     protected function sub_test_grade_item_delete() {
112         global $DB;
113         $grade_item = new grade_item($this->grade_items[7], false); // Use a grade item not touched by previous (or future) tests.
114         $this->assertTrue(method_exists($grade_item, 'delete'));
116         $this->assertTrue($grade_item->delete());
118         $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
120         // Keep our reference collection the same as the database.
121         unset($this->grade_items[7]);
122     }
124     protected function sub_test_grade_item_update() {
125         global $DB;
126         $grade_item = new grade_item($this->grade_items[0], false);
127         $this->assertTrue(method_exists($grade_item, 'update'));
129         $grade_item->iteminfo = 'Updated info for this unittest grade_item';
131         $this->assertTrue($grade_item->update());
133         $grade_item->grademin = 14;
134         $this->assertTrue($grade_item->qualifies_for_regrading());
135         $this->assertTrue($grade_item->update());
137         $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
138         $this->assertEquals($grade_item->iteminfo, $iteminfo);
139     }
141     protected function sub_test_grade_item_load_scale() {
142         $grade_item = new grade_item($this->grade_items[2], false);
143         $this->assertTrue(method_exists($grade_item, 'load_scale'));
144         $scale = $grade_item->load_scale();
145         $this->assertFalse(empty($grade_item->scale));
146         $this->assertEquals($scale->id, $this->grade_items[2]->scaleid);
147     }
149     protected function sub_test_grade_item_load_outcome() {
150         $grade_item = new grade_item($this->grade_items[0], false);
151         $this->assertTrue(method_exists($grade_item, 'load_outcome'));
152         // TODO: add tests.
153     }
155     protected function sub_test_grade_item_qualifies_for_regrading() {
156         $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
157         $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
159         $this->assertFalse($grade_item->qualifies_for_regrading());
161         $grade_item->iteminfo = 'Updated info for this unittest grade_item';
163         $this->assertFalse($grade_item->qualifies_for_regrading());
165         $grade_item->grademin = 14;
167         $this->assertTrue($grade_item->qualifies_for_regrading());
168     }
170     protected function sub_test_grade_item_force_regrading() {
171         $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
172         $this->assertTrue(method_exists($grade_item, 'force_regrading'));
174         $this->assertEquals(0, $grade_item->needsupdate);
176         $grade_item->force_regrading();
177         $this->assertEquals(1, $grade_item->needsupdate);
178         $grade_item->update_from_db();
179         $this->assertEquals(1, $grade_item->needsupdate);
180     }
182     protected function sub_test_grade_item_fetch() {
183         $grade_item = new grade_item();
184         $this->assertTrue(method_exists($grade_item, 'fetch'));
186         // Not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading().
187         $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
188         $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
189         $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
191         $grade_item = grade_item::fetch(array('itemtype'=>$this->grade_items[1]->itemtype, 'itemmodule'=>$this->grade_items[1]->itemmodule));
192         $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
193         $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
194     }
196     protected function sub_test_grade_item_fetch_all() {
197         $grade_item = new grade_item();
198         $this->assertTrue(method_exists($grade_item, 'fetch_all'));
200         $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
201         $this->assertEquals(count($this->grade_items), count($grade_items)-1); // -1 to account for the course grade item.
202     }
204     // Retrieve all final scores for a given grade_item.
205     protected function sub_test_grade_item_get_all_finals() {
206         $grade_item = new grade_item($this->grade_items[0], false);
207         $this->assertTrue(method_exists($grade_item, 'get_final'));
209         $final_grades = $grade_item->get_final();
210         $this->assertEquals(3, count($final_grades));
211     }
214     // Retrieve all final scores for a specific userid.
215     protected function sub_test_grade_item_get_final() {
216         $grade_item = new grade_item($this->grade_items[0], false);
217         $this->assertTrue(method_exists($grade_item, 'get_final'));
218         $final_grade = $grade_item->get_final($this->user[1]->id);
219         $this->assertEquals($this->grade_grades[0]->finalgrade, $final_grade->finalgrade);
220     }
222     protected function sub_test_grade_item_get_sortorder() {
223         $grade_item = new grade_item($this->grade_items[0], false);
224         $this->assertTrue(method_exists($grade_item, 'get_sortorder'));
225         $sortorder = $grade_item->get_sortorder();
226         $this->assertEquals($this->grade_items[0]->sortorder, $sortorder);
227     }
229     protected function sub_test_grade_item_set_sortorder() {
230         $grade_item = new grade_item($this->grade_items[0], false);
231         $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
232         $grade_item->set_sortorder(999);
233         $this->assertEquals($grade_item->sortorder, 999);
234     }
236     protected function sub_test_grade_item_move_after_sortorder() {
237         $grade_item = new grade_item($this->grade_items[0], false);
238         $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
239         $grade_item->move_after_sortorder(5);
240         $this->assertEquals($grade_item->sortorder, 6);
242         $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
243         $this->assertEquals($grade_item->sortorder, 6);
245         $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
246         $this->assertEquals($after->sortorder, 8);
247     }
249     protected function sub_test_grade_item_get_name() {
250         $grade_item = new grade_item($this->grade_items[0], false);
251         $this->assertTrue(method_exists($grade_item, 'get_name'));
253         $name = $grade_item->get_name();
254         $this->assertEquals($this->grade_items[0]->itemname, $name);
255     }
257     protected function sub_test_grade_item_set_parent() {
258         $grade_item = new grade_item($this->grade_items[0], false);
259         $this->assertTrue(method_exists($grade_item, 'set_parent'));
261         $old = $grade_item->get_parent_category();
262         $new = new grade_category($this->grade_categories[3], false);
263         $new_item = $new->get_grade_item();
265         $this->assertTrue($grade_item->set_parent($new->id));
267         $new_item->update_from_db();
268         $grade_item->update_from_db();
270         $this->assertEquals($grade_item->categoryid, $new->id);
271     }
273     protected function sub_test_grade_item_get_parent_category() {
274         $grade_item = new grade_item($this->grade_items[0], false);
275         $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
277         $category = $grade_item->get_parent_category();
278         $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
279     }
281     protected function sub_test_grade_item_load_parent_category() {
282         $grade_item = new grade_item($this->grade_items[0], false);
283         $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
285         $category = $grade_item->load_parent_category();
286         $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
287         $this->assertEquals($this->grade_categories[1]->fullname, $grade_item->parent_category->fullname);
288     }
290     protected function sub_test_grade_item_get_item_category() {
291         $grade_item = new grade_item($this->grade_items[3], false);
292         $this->assertTrue(method_exists($grade_item, 'get_item_category'));
294         $category = $grade_item->get_item_category();
295         $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
296     }
298     protected function sub_test_grade_item_load_item_category() {
299         $grade_item = new grade_item($this->grade_items[3], false);
300         $this->assertTrue(method_exists($grade_item, 'load_item_category'));
302         $category = $grade_item->load_item_category();
303         $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
304         $this->assertEquals($this->grade_categories[0]->fullname, $grade_item->item_category->fullname);
305     }
307     protected function sub_test_grade_item_regrade_final_grades() {
308         $grade_item = new grade_item($this->grade_items[0], false);
309         $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
310         $this->assertEquals(true, $grade_item->regrade_final_grades());
311         // TODO: add more tests.
312     }
314     protected function sub_test_grade_item_adjust_raw_grade() {
315         $grade_item = new grade_item($this->grade_items[2], false); // Anything but assignment module!
316         $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
318         $grade_raw = new stdClass();
319         $grade_raw->rawgrade = 40;
320         $grade_raw->grademax = 100;
321         $grade_raw->grademin = 0;
323         $grade_item->gradetype = GRADE_TYPE_VALUE;
324         $grade_item->multfactor = 1;
325         $grade_item->plusfactor = 0;
326         $grade_item->grademax = 50;
327         $grade_item->grademin = 0;
329         $original_grade_raw  = clone($grade_raw);
330         $original_grade_item = clone($grade_item);
332         $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
334         // Try a larger maximum grade.
335         $grade_item->grademax = 150;
336         $grade_item->grademin = 0;
337         $this->assertEquals(60, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
339         // Try larger minimum grade.
340         $grade_item->grademin = 50;
342         $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
344         // Rescaling from a small scale (0-50) to a larger scale (0-100).
345         $grade_raw->grademax = 50;
346         $grade_raw->grademin = 0;
347         $grade_item->grademax = 100;
348         $grade_item->grademin = 0;
350         $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
352         // Rescaling from a small scale (0-50) to a larger scale with offset (40-100).
353         $grade_item->grademax = 100;
354         $grade_item->grademin = 40;
356         $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
358         // Try multfactor and plusfactor.
359         $grade_raw = clone($original_grade_raw);
360         $grade_item = clone($original_grade_item);
361         $grade_item->multfactor = 1.23;
362         $grade_item->plusfactor = 3;
364         $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
366         // Try multfactor below 0 and a negative plusfactor.
367         $grade_raw = clone($original_grade_raw);
368         $grade_item = clone($original_grade_item);
369         $grade_item->multfactor = 0.23;
370         $grade_item->plusfactor = -3;
372         $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
373     }
375     protected function sub_test_grade_item_rescale_grades_keep_percentage() {
376         global $DB;
377         $gradeitem = new grade_item($this->grade_items[10], false); // 10 is the manual grade item.
379         // Create some grades to go with the grade item.
380         $gradeids = array();
381         $grade = new stdClass();
382         $grade->itemid = $gradeitem->id;
383         $grade->userid = $this->user[2]->id;
384         $grade->finalgrade = 10;
385         $grade->rawgrademax = $gradeitem->grademax;
386         $grade->rawgrademin = $gradeitem->grademin;
387         $grade->timecreated = time();
388         $grade->timemodified = time();
389         $gradeids[] = $DB->insert_record('grade_grades', $grade);
391         $grade->userid = $this->user[3]->id;
392         $grade->finalgrade = 50;
393         $grade->rawgrademax = $gradeitem->grademax;
394         $grade->rawgrademin = $gradeitem->grademin;
395         $gradeids[] = $DB->insert_record('grade_grades', $grade);
397         // Run the function.
398         $gradeitem->grademax = 33;
399         $gradeitem->grademin = 3;
400         $gradeitem->update();
401         $gradeitem->rescale_grades_keep_percentage(0, 100, 3, 33, 'test');
403         // Check that the grades were updated to match the grade item.
404         $grade = $DB->get_record('grade_grades', array('id' => $gradeids[0]));
405         $this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
406         $this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
407         $this->assertEquals(6, $grade->finalgrade, 'Min grade mismatch', 0.0001);
409         $grade = $DB->get_record('grade_grades', array('id' => $gradeids[1]));
410         $this->assertEquals($gradeitem->grademax, $grade->rawgrademax, 'Max grade mismatch', 0.0001);
411         $this->assertEquals($gradeitem->grademin, $grade->rawgrademin, 'Min grade mismatch', 0.0001);
412         $this->assertEquals(18, $grade->finalgrade, 'Min grade mismatch', 0.0001);
413     }
415     protected function sub_test_grade_item_set_locked() {
416         // Getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
417         // also needs to have at least one grade_grade or $grade_item->get_final(1) returns null.
418         // $grade_item = new grade_item($this->grade_items[8]);
419         $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
421         $this->assertTrue(method_exists($grade_item, 'set_locked'));
423         $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
424         $this->assertTrue(empty($grade_item->locked));// Not locked.
425         $this->assertTrue(empty($grade_grade->locked));// Not locked.
427         $this->assertTrue($grade_item->set_locked(true, true, false));
428         $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
430         $this->assertFalse(empty($grade_item->locked));// Locked.
431         $this->assertFalse(empty($grade_grade->locked)); // Individual grades should be locked too.
433         $this->assertTrue($grade_item->set_locked(false, true, false));
434         $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
436         $this->assertTrue(empty($grade_item->locked));
437         $this->assertTrue(empty($grade->locked)); // Individual grades should be unlocked too.
438     }
440     protected function sub_test_grade_item_is_locked() {
441         $grade_item = new grade_item($this->grade_items[10], false);
442         $this->assertTrue(method_exists($grade_item, 'is_locked'));
444         $this->assertFalse($grade_item->is_locked());
445         $this->assertFalse($grade_item->is_locked($this->user[1]->id));
446         $this->assertTrue($grade_item->set_locked(true, true, false));
447         $this->assertTrue($grade_item->is_locked());
448         $this->assertTrue($grade_item->is_locked($this->user[1]->id));
449     }
451     protected function sub_test_grade_item_set_hidden() {
452         $grade_item = new grade_item($this->grade_items[0], false);
453         $this->assertTrue(method_exists($grade_item, 'set_hidden'));
455         $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
456         $this->assertEquals(0, $grade_item->hidden);
457         $this->assertEquals(0, $grade->hidden);
459         $grade_item->set_hidden(666, true);
460         $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
462         $this->assertEquals(666, $grade_item->hidden);
463         $this->assertEquals(666, $grade->hidden);
464     }
466     protected function sub_test_grade_item_is_hidden() {
467         $grade_item = new grade_item($this->grade_items[0], false);
468         $this->assertTrue(method_exists($grade_item, 'is_hidden'));
470         $this->assertFalse($grade_item->is_hidden());
471         $this->assertFalse($grade_item->is_hidden(1));
473         $grade_item->set_hidden(1);
474         $this->assertTrue($grade_item->is_hidden());
475         $this->assertTrue($grade_item->is_hidden(1));
477         $grade_item->set_hidden(666);
478         $this->assertFalse($grade_item->is_hidden());
479         $this->assertFalse($grade_item->is_hidden(1));
481         $grade_item->set_hidden(time()+666);
482         $this->assertTrue($grade_item->is_hidden());
483         $this->assertTrue($grade_item->is_hidden(1));
484     }
486     protected function sub_test_grade_item_is_category_item() {
487         $grade_item = new grade_item($this->grade_items[3], false);
488         $this->assertTrue(method_exists($grade_item, 'is_category_item'));
489         $this->assertTrue($grade_item->is_category_item());
490     }
492     protected function sub_test_grade_item_is_course_item() {
493         $grade_item = grade_item::fetch_course_item($this->courseid);
494         $this->assertTrue(method_exists($grade_item, 'is_course_item'));
495         $this->assertTrue($grade_item->is_course_item());
496     }
498     protected function sub_test_grade_item_fetch_course_item() {
499         $grade_item = grade_item::fetch_course_item($this->courseid);
500         $this->assertTrue(method_exists($grade_item, 'fetch_course_item'));
501         $this->assertEquals($grade_item->itemtype, 'course');
502     }
504     protected function sub_test_grade_item_depends_on() {
505         global $CFG;
507         $origenableoutcomes = $CFG->enableoutcomes;
508         $CFG->enableoutcomes = 0;
509         $grade_item = new grade_item($this->grade_items[1], false);
511         // Calculated grade dependency.
512         $deps = $grade_item->depends_on();
513         sort($deps, SORT_NUMERIC); // For comparison.
514         $this->assertEquals(array($this->grade_items[0]->id), $deps);
516         // Simulate depends on returns none when locked.
517         $grade_item->locked = time();
518         $grade_item->update();
519         $deps = $grade_item->depends_on();
520         sort($deps, SORT_NUMERIC); // For comparison.
521         $this->assertEquals(array(), $deps);
523         // Category dependency.
524         $grade_item = new grade_item($this->grade_items[3], false);
525         $deps = $grade_item->depends_on();
526         sort($deps, SORT_NUMERIC); // For comparison.
527         $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
528         $this->assertEquals($res, $deps);
529     }
531     protected function scales_outcomes_test_grade_item_depends_on() {
532         $CFG->enableoutcomes = 1;
533         $origgradeincludescalesinaggregation = $CFG->grade_includescalesinaggregation;
534         $CFG->grade_includescalesinaggregation = 1;
536         // Scale item in category with $CFG->grade_includescalesinaggregation = 1.
537         $grade_item = new grade_item($this->grade_items[14], false);
538         $deps = $grade_item->depends_on();
539         sort($deps, SORT_NUMERIC);
540         $res = array($this->grade_items[16]->id);
541         $this->assertEquals($res, $deps);
543         // Scale item in category with $CFG->grade_includescalesinaggregation = 0.
544         $CFG->grade_includescalesinaggregation = 0;
545         $grade_item = new grade_item($this->grade_items[14], false);
546         $deps = $grade_item->depends_on();
547         $res = array();
548         $this->assertEquals($res, $deps);
549         $CFG->grade_includescalesinaggregation = 1;
551         // Outcome item in category with outcomes disabled.
552         $CFG->enableoutcomes = 0;
553         $grade_item = new grade_item($this->grade_items[14], false);
554         $deps = $grade_item->depends_on();
555         sort($deps, SORT_NUMERIC);
556         $res = array($this->grade_items[16]->id, $this->grade_items[17]->id);
557         $this->assertEquals($res, $deps);
559         $CFG->enableoutcomes = $origenableoutcomes;
560         $CFG->grade_includescalesinaggregation = $origgradeincludescalesinaggregation;
561     }
563     protected function sub_test_refresh_grades() {
564         // Testing with the grade item for a mod_assignment instance.
565         $grade_item = new grade_item($this->grade_items[0], false);
566         $this->assertTrue(method_exists($grade_item, 'refresh_grades'));
567         $this->assertTrue($grade_item->refresh_grades());
569         // Break the grade item and check error handling.
570         $grade_item->iteminstance = 123456789;
571         $this->assertFalse($grade_item->refresh_grades());
572         $this->assertDebuggingCalled();
573     }
575     protected function sub_test_grade_item_is_calculated() {
576         $grade_item = new grade_item($this->grade_items[1], false);
577         $this->assertTrue(method_exists($grade_item, 'is_calculated'));
578         $this->assertTrue($grade_item->is_calculated());
580         $grade_item = new grade_item($this->grade_items[0], false);
581         $this->assertFalse($grade_item->is_calculated());
582     }
584     protected function sub_test_grade_item_set_calculation() {
585         $grade_item = new grade_item($this->grade_items[1], false);
586         $this->assertTrue(method_exists($grade_item, 'set_calculation'));
587         $grade_itemsource = new grade_item($this->grade_items[0], false);
589         $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
591         $this->assertTrue(!empty($grade_item->needsupdate));
592         $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
593     }
595     protected function sub_test_grade_item_get_calculation() {
596         $grade_item = new grade_item($this->grade_items[1], false);
597         $this->assertTrue(method_exists($grade_item, 'get_calculation'));
598         $grade_itemsource = new grade_item($this->grade_items[0], false);
600         $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
602         $formula = $grade_item->get_calculation();
603         $this->assertTrue(!empty($grade_item->needsupdate));
604         $this->assertEquals($denormalizedformula, $formula);
605     }
607     public function sub_test_grade_item_compute() {
608         $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
609         $this->assertTrue(method_exists($grade_item, 'compute'));
611         // Check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades.
612         $this->grade_grades[3] = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
613         $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
614         $grade_grade->delete();
616         $this->grade_grades[4] = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
617         $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
618         $grade_grade->delete();
620         $this->grade_grades[5] = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
621         $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
622         $grade_grade->delete();
624         // Recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them.
625         $grade_item->compute();
627         $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[3]->userid, 'itemid'=>$this->grade_grades[3]->itemid));
628         $this->assertEquals($this->grade_grades[3]->finalgrade, $grade_grade->finalgrade);
630         $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[4]->userid, 'itemid'=>$this->grade_grades[4]->itemid));
631         $this->assertEquals($this->grade_grades[4]->finalgrade, $grade_grade->finalgrade);
633         $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[5]->userid, 'itemid'=>$this->grade_grades[5]->itemid));
634         $this->assertEquals($this->grade_grades[5]->finalgrade, $grade_grade->finalgrade);
635     }
637     protected function sub_test_update_final_grade() {
639         // MDL-31713 Check that min and max are set on the grade_grade instance
640         // if the grade is overridden before the activity has supplied a grade.
641         $min = 2;
642         $max = 8;
644         // Create a brand new grade item.
645         $grade_item = new grade_item();
646         $this->assertTrue(method_exists($grade_item, 'insert'));
648         $grade_item->courseid = $this->courseid;
649         $grade_item->categoryid = $this->grade_categories[1]->id;
650         $grade_item->itemname = 'brand new unit test grade item';
651         $grade_item->itemtype = 'mod';
652         $grade_item->itemmodule = 'quiz';
653         $grade_item->iteminfo = 'Grade item used for unit testing';
654         $grade_item->iteminstance = $this->activities[7]->id;
655         $grade_item->grademin = $min;
656         $grade_item->grademax = $max;
657         $grade_item->insert();
659         // Override the student grade.
660         $grade_item->update_final_grade($this->user[1]->id, 7, 'gradebook', '', FORMAT_MOODLE);
662         // Check the student's grade has the correct min and max grade.
663         $grade_grade = grade_grade::fetch(array('userid'=>$this->user[1]->id, 'itemid'=>$grade_item->id));
664         $this->assertEquals($min, $grade_grade->rawgrademin);
665         $this->assertEquals($max, $grade_grade->rawgrademax);
666     }
668     protected function sub_test_grade_item_can_control_visibility() {
669         // Grade item 0 == Course module 0 == Assignment.
670         $grade_item = new grade_item($this->grade_items[0], false);
671         $this->assertTrue($grade_item->can_control_visibility());
673         // Grade item  == Course module 7 == Quiz.
674         $grade_item = new grade_item($this->grade_items[11], false);
675         $this->assertFalse($grade_item->can_control_visibility());
676     }
678     /**
679      * Test the {@link grade_item::fix_duplicate_sortorder() function with
680      * faked duplicate sortorder data.
681      */
682     public function sub_test_grade_item_fix_sortorder() {
683         global $DB;
685         $this->resetAfterTest(true);
687         // Each set is used for filling the db with fake data and will be representing the result of query:
688         // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
689         $testsets = array(
690             // Items that need no action.
691             array(1,2,3),
692             array(5,6,7),
693             array(7,6,1,3,2,5),
694             // Items with sortorder duplicates
695             array(1,2,2,3,3,4,5),
696             // Only one sortorder duplicate.
697             array(1,1),
698             array(3,3),
699             // Non-sequential sortorders with one or multiple duplicates.
700             array(3,3,7,5,6,6,9,10,8,3),
701             array(7,7,3),
702             array(3,4,5,3,5,4,7,1)
703         );
704         $origsequence = array();
706         // Generate the data and remember the initial sequence or items.
707         foreach ($testsets as $testset) {
708             $course = $this->getDataGenerator()->create_course();
709             foreach ($testset as $sortorder) {
710                 $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
711             }
712             $DB->get_records('grade_items');
713             $origsequence[$course->id] = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
714                 "WHERE courseid = ? ORDER BY sortorder, id", array($course->id));
715         }
717         $duplicatedetectionsql = "SELECT courseid, sortorder
718                                     FROM {grade_items}
719                                 WHERE courseid = :courseid
720                                 GROUP BY courseid, sortorder
721                                   HAVING COUNT(id) > 1";
723         // Do the work.
724         foreach ($origsequence as $courseid => $ignore) {
725             grade_item::fix_duplicate_sortorder($courseid);
726             // Verify that no duplicates are left in the database.
727             $dupes = $DB->record_exists_sql($duplicatedetectionsql, array('courseid' => $courseid));
728             $this->assertFalse($dupes);
729         }
731         // Verify that sequences are exactly the same as they were before upgrade script.
732         $idx = 0;
733         foreach ($origsequence as $courseid => $sequence) {
734             if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
735                 // If there were no duplicates for this course verify that sortorders are not modified.
736                 $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
737                 $this->assertEquals($testsets[$idx], $newsortorders);
738             }
739             $newsequence = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
740                 "WHERE courseid = ? ORDER BY sortorder, id", array($courseid));
741             $this->assertEquals($sequence, $newsequence,
742                     "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
743             $idx++;
744         }
745     }
747     /**
748      * Populate some fake grade items into the database with specified
749      * sortorder and course id.
750      *
751      * NOTE: This function doesn't make much attempt to respect the
752      * gradebook internals, its simply used to fake some data for
753      * testing the upgradelib function. Please don't use it for other
754      * purposes.
755      *
756      * @param int $courseid id of course
757      * @param int $sortorder numeric sorting order of item
758      * @return stdClass grade item object from the database.
759      */
760     private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
761         global $DB, $CFG;
762         require_once($CFG->libdir.'/gradelib.php');
764         $item = new stdClass();
765         $item->courseid = $courseid;
766         $item->sortorder = $sortorder;
767         $item->gradetype = GRADE_TYPE_VALUE;
768         $item->grademin = 30;
769         $item->grademax = 110;
770         $item->itemnumber = 1;
771         $item->iteminfo = '';
772         $item->timecreated = time();
773         $item->timemodified = time();
775         $item->id = $DB->insert_record('grade_items', $item);
777         return $DB->get_record('grade_items', array('id' => $item->id));
778     }
780     public function test_set_aggregation_fields_for_aggregation() {
781         $course = $this->getDataGenerator()->create_course();
782         $gi = new grade_item(array('courseid' => $course->id, 'itemtype' => 'manual'), false);
784         $methods = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN, GRADE_AGGREGATE_MAX,
785             GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
786             GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_SUM);
788         // Switching from and to the same aggregation using the defaults.
789         foreach ($methods as $method) {
790             $defaults = grade_category::get_default_aggregation_coefficient_values($method);
791             $gi->aggregationcoef = $defaults['aggregationcoef'];
792             $gi->aggregationcoef2 = $defaults['aggregationcoef2'];
793             $gi->weightoverride = $defaults['weightoverride'];
794             $this->assertFalse($gi->set_aggregation_fields_for_aggregation($method, $method));
795             $this->assertEquals($defaults['aggregationcoef'], $gi->aggregationcoef);
796             $this->assertEquals($defaults['aggregationcoef2'], $gi->aggregationcoef2);
797             $this->assertEquals($defaults['weightoverride'], $gi->weightoverride);
798         }
800         // Extra credit is kept across aggregation methods that support it.
801         foreach ($methods as $from) {
802             $fromsupportsec = grade_category::aggregation_uses_extracredit($from);
803             $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
805             foreach ($methods as $to) {
806                 $tosupportsec = grade_category::aggregation_uses_extracredit($to);
807                 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
809                 // Set the item to be extra credit, if supported.
810                 if ($fromsupportsec) {
811                     $gi->aggregationcoef = 1;
812                 } else {
813                     $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
814                 }
816                 // We ignore those fields, we know it is never used for extra credit.
817                 $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
818                 $gi->weightoverride = $todefaults['weightoverride'];
820                 if ($fromsupportsec && $tosupportsec) {
821                     $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
822                     $this->assertEquals(1, $gi->aggregationcoef);
824                 } else if ($fromsupportsec && !$tosupportsec) {
825                     if ($to == GRADE_AGGREGATE_WEIGHTED_MEAN) {
826                         // Special case, aggregationcoef is used but for weights.
827                         $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
828                         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
829                     } else {
830                         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
831                         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
832                     }
833                 } else {
834                     // The source does not support extra credit, everything will be reset.
835                     if (($from == GRADE_AGGREGATE_WEIGHTED_MEAN || $to == GRADE_AGGREGATE_WEIGHTED_MEAN) && $from != $to) {
836                         // Special case, aggregationcoef is used but for weights.
837                         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
838                         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
839                     } else {
840                         $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
841                         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
842                     }
843                 }
844             }
845         }
847         // Extra credit can be higher than one for GRADE_AGGREGATE_EXTRACREDIT_MEAN, but will be normalised for others.
848         $from = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
849         $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
851         foreach ($methods as $to) {
852             if (!grade_category::aggregation_uses_extracredit($to)) {
853                 continue;
854             }
856             $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
857             $gi->aggregationcoef = 8;
859             // Ignore those fields, they are not used for extra credit.
860             $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
861             $gi->weightoverride = $todefaults['weightoverride'];
863             if ($to == $from) {
864                 $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
865                 $this->assertEquals(8, $gi->aggregationcoef);
866             } else {
867                 $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
868                 $this->assertEquals(1, $gi->aggregationcoef);
869             }
870         }
872         // Weights are reset.
873         $from = GRADE_AGGREGATE_SUM;
874         $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
876         $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
877         $gi->aggregationcoef2 = 0.321;
878         $gi->weightoverride = $fromdefaults['weightoverride'];
880         $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
881         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
883         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
884         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
885         $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
886         $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
888         $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
889         $gi->aggregationcoef2 = 0.321;
890         $gi->weightoverride = $fromdefaults['weightoverride'];
892         $to = GRADE_AGGREGATE_SUM;
893         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
895         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
896         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
897         $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
898         $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
900         // Weight is kept when using SUM with weight override.
901         $from = GRADE_AGGREGATE_SUM;
902         $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
904         $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
905         $gi->aggregationcoef2 = 0.321;
906         $gi->weightoverride = 1;
908         $to = GRADE_AGGREGATE_SUM;
909         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
911         $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
912         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
913         $this->assertEquals(0.321, $gi->aggregationcoef2);
914         $this->assertEquals(1, $gi->weightoverride);
916         $gi->aggregationcoef2 = 0.321;
917         $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
918         $gi->weightoverride = 1;
920         $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
921         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
923         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
924         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
925         $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
926         $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
928         // Weight is kept when staying in weighted mean.
929         $from = GRADE_AGGREGATE_WEIGHTED_MEAN;
930         $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
932         $gi->aggregationcoef = 18;
933         $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
934         $gi->weightoverride = $fromdefaults['weightoverride'];
936         $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
937         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
939         $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
940         $this->assertEquals(18, $gi->aggregationcoef);
941         $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
942         $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
944         $gi->aggregationcoef = 18;
945         $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
946         $gi->weightoverride = $fromdefaults['weightoverride'];
948         $to = GRADE_AGGREGATE_SUM;
949         $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
951         $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
952         $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
953         $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
954         $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
955     }