2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * @package core_grades
20 * @copyright nicolas@moodle.com
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 defined('MOODLE_INTERNAL') || die();
26 require_once(__DIR__.'/fixtures/lib.php');
28 class 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_set_locked();
54 $this->sub_test_grade_item_is_locked();
55 $this->sub_test_grade_item_set_hidden();
56 $this->sub_test_grade_item_is_hidden();
57 $this->sub_test_grade_item_is_category_item();
58 $this->sub_test_grade_item_is_course_item();
59 $this->sub_test_grade_item_fetch_course_item();
60 $this->sub_test_grade_item_depends_on();
61 $this->sub_test_grade_item_is_calculated();
62 $this->sub_test_grade_item_set_calculation();
63 $this->sub_test_grade_item_get_calculation();
64 $this->sub_test_grade_item_compute();
67 protected function sub_test_grade_item_construct() {
68 $params = new stdClass();
70 $params->courseid = $this->courseid;
71 $params->categoryid = $this->grade_categories[1]->id;
72 $params->itemname = 'unittestgradeitem4';
73 $params->itemtype = 'mod';
74 $params->itemmodule = 'database';
75 $params->iteminfo = 'Grade item used for unit testing';
77 $grade_item = new grade_item($params, false);
79 $this->assertEquals($params->courseid, $grade_item->courseid);
80 $this->assertEquals($params->categoryid, $grade_item->categoryid);
81 $this->assertEquals($params->itemmodule, $grade_item->itemmodule);
84 protected function sub_test_grade_item_insert() {
85 $grade_item = new grade_item();
86 $this->assertTrue(method_exists($grade_item, 'insert'));
88 $grade_item->courseid = $this->courseid;
89 $grade_item->categoryid = $this->grade_categories[1]->id;
90 $grade_item->itemname = 'unittestgradeitem4';
91 $grade_item->itemtype = 'mod';
92 $grade_item->itemmodule = 'quiz';
93 $grade_item->iteminfo = 'Grade item used for unit testing';
95 $grade_item->insert();
97 $last_grade_item = end($this->grade_items);
99 $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
100 $this->assertEquals(11, $grade_item->sortorder);
102 //keep our reference collection the same as what is in the database
103 $this->grade_items[] = $grade_item;
106 protected function sub_test_grade_item_delete() {
108 $grade_item = new grade_item($this->grade_items[7],false);//use a grade item not touched by previous (or future) tests
109 $this->assertTrue(method_exists($grade_item, 'delete'));
111 $this->assertTrue($grade_item->delete());
113 $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
115 //keep our reference collection the same as the database
116 unset($this->grade_items[7]);
119 protected function sub_test_grade_item_update() {
121 $grade_item = new grade_item($this->grade_items[0], false);
122 $this->assertTrue(method_exists($grade_item, 'update'));
124 $grade_item->iteminfo = 'Updated info for this unittest grade_item';
126 $this->assertTrue($grade_item->update());
128 $grade_item->grademin = 14;
129 $this->assertTrue($grade_item->qualifies_for_regrading());
130 $this->assertTrue($grade_item->update());
132 $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
133 $this->assertEquals($grade_item->iteminfo, $iteminfo);
136 protected function sub_test_grade_item_load_scale() {
137 $grade_item = new grade_item($this->grade_items[2], false);
138 $this->assertTrue(method_exists($grade_item, 'load_scale'));
139 $scale = $grade_item->load_scale();
140 $this->assertFalse(empty($grade_item->scale));
141 $this->assertEquals($scale->id, $this->grade_items[2]->scaleid);
144 protected function sub_test_grade_item_load_outcome() {
145 $grade_item = new grade_item($this->grade_items[0], false);
146 $this->assertTrue(method_exists($grade_item, 'load_outcome'));
150 protected function sub_test_grade_item_qualifies_for_regrading() {
151 $grade_item = new grade_item($this->grade_items[3], false);//use a grade item not touched by previous tests
152 $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
154 $this->assertFalse($grade_item->qualifies_for_regrading());
156 $grade_item->iteminfo = 'Updated info for this unittest grade_item';
158 $this->assertFalse($grade_item->qualifies_for_regrading());
160 $grade_item->grademin = 14;
162 $this->assertTrue($grade_item->qualifies_for_regrading());
165 protected function sub_test_grade_item_force_regrading() {
166 $grade_item = new grade_item($this->grade_items[3], false);//use a grade item not touched by previous tests
167 $this->assertTrue(method_exists($grade_item, 'force_regrading'));
169 $this->assertEquals(0, $grade_item->needsupdate);
171 $grade_item->force_regrading();
172 $this->assertEquals(1, $grade_item->needsupdate);
173 $grade_item->update_from_db();
174 $this->assertEquals(1, $grade_item->needsupdate);
177 protected function sub_test_grade_item_fetch() {
178 $grade_item = new grade_item();
179 $this->assertTrue(method_exists($grade_item, 'fetch'));
181 //not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading()
182 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
183 $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
184 $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
186 $grade_item = grade_item::fetch(array('itemtype'=>$this->grade_items[1]->itemtype, 'itemmodule'=>$this->grade_items[1]->itemmodule));
187 $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
188 $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
191 protected function sub_test_grade_item_fetch_all() {
192 $grade_item = new grade_item();
193 $this->assertTrue(method_exists($grade_item, 'fetch_all'));
195 $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
196 $this->assertEquals(count($this->grade_items), count($grade_items)-1);//-1 to account for the course grade item
199 // Retrieve all final scores for a given grade_item.
200 protected function sub_test_grade_item_get_all_finals() {
201 $grade_item = new grade_item($this->grade_items[0], false);
202 $this->assertTrue(method_exists($grade_item, 'get_final'));
204 $final_grades = $grade_item->get_final();
205 $this->assertEquals(3, count($final_grades));
209 // Retrieve all final scores for a specific userid.
210 protected function sub_test_grade_item_get_final() {
211 $grade_item = new grade_item($this->grade_items[0], false);
212 $this->assertTrue(method_exists($grade_item, 'get_final'));
213 $final_grade = $grade_item->get_final($this->user[1]->id);
214 $this->assertEquals($this->grade_grades[0]->finalgrade, $final_grade->finalgrade);
217 protected function sub_test_grade_item_get_sortorder() {
218 $grade_item = new grade_item($this->grade_items[0], false);
219 $this->assertTrue(method_exists($grade_item, 'get_sortorder'));
220 $sortorder = $grade_item->get_sortorder();
221 $this->assertEquals($this->grade_items[0]->sortorder, $sortorder);
224 protected function sub_test_grade_item_set_sortorder() {
225 $grade_item = new grade_item($this->grade_items[0], false);
226 $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
227 $grade_item->set_sortorder(999);
228 $this->assertEquals($grade_item->sortorder, 999);
231 protected function sub_test_grade_item_move_after_sortorder() {
232 $grade_item = new grade_item($this->grade_items[0], false);
233 $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
234 $grade_item->move_after_sortorder(5);
235 $this->assertEquals($grade_item->sortorder, 6);
237 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
238 $this->assertEquals($grade_item->sortorder, 6);
240 $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
241 $this->assertEquals($after->sortorder, 8);
244 protected function sub_test_grade_item_get_name() {
245 $grade_item = new grade_item($this->grade_items[0], false);
246 $this->assertTrue(method_exists($grade_item, 'get_name'));
248 $name = $grade_item->get_name();
249 $this->assertEquals($this->grade_items[0]->itemname, $name);
252 protected function sub_test_grade_item_set_parent() {
253 $grade_item = new grade_item($this->grade_items[0], false);
254 $this->assertTrue(method_exists($grade_item, 'set_parent'));
256 $old = $grade_item->get_parent_category();
257 $new = new grade_category($this->grade_categories[3], false);
258 $new_item = $new->get_grade_item();
260 $this->assertTrue($grade_item->set_parent($new->id));
262 $new_item->update_from_db();
263 $grade_item->update_from_db();
265 $this->assertEquals($grade_item->categoryid, $new->id);
268 protected function sub_test_grade_item_get_parent_category() {
269 $grade_item = new grade_item($this->grade_items[0], false);
270 $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
272 $category = $grade_item->get_parent_category();
273 $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
276 protected function sub_test_grade_item_load_parent_category() {
277 $grade_item = new grade_item($this->grade_items[0], false);
278 $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
280 $category = $grade_item->load_parent_category();
281 $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
282 $this->assertEquals($this->grade_categories[1]->fullname, $grade_item->parent_category->fullname);
285 protected function sub_test_grade_item_get_item_category() {
286 $grade_item = new grade_item($this->grade_items[3], false);
287 $this->assertTrue(method_exists($grade_item, 'get_item_category'));
289 $category = $grade_item->get_item_category();
290 $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
293 protected function sub_test_grade_item_load_item_category() {
294 $grade_item = new grade_item($this->grade_items[3], false);
295 $this->assertTrue(method_exists($grade_item, 'load_item_category'));
297 $category = $grade_item->load_item_category();
298 $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
299 $this->assertEquals($this->grade_categories[0]->fullname, $grade_item->item_category->fullname);
302 // Test update of all final grades
303 protected function sub_test_grade_item_regrade_final_grades() {
304 $grade_item = new grade_item($this->grade_items[0], false);
305 $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
306 $this->assertEquals(true, $grade_item->regrade_final_grades());
307 //TODO: add more tests
310 // Test the adjust_raw_grade method
311 protected function sub_test_grade_item_adjust_raw_grade() {
312 $grade_item = new grade_item($this->grade_items[2], false); // anything but assignment module!
313 $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
315 $grade_raw = new stdClass();
316 $grade_raw->rawgrade = 40;
317 $grade_raw->grademax = 100;
318 $grade_raw->grademin = 0;
320 $grade_item->gradetype = GRADE_TYPE_VALUE;
321 $grade_item->multfactor = 1;
322 $grade_item->plusfactor = 0;
323 $grade_item->grademax = 50;
324 $grade_item->grademin = 0;
326 $original_grade_raw = clone($grade_raw);
327 $original_grade_item = clone($grade_item);
329 $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
331 // Try a larger maximum grade
332 $grade_item->grademax = 150;
333 $grade_item->grademin = 0;
334 $this->assertEquals(60, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
336 // Try larger minimum grade
337 $grade_item->grademin = 50;
339 $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
341 // Rescaling from a small scale (0-50) to a larger scale (0-100)
342 $grade_raw->grademax = 50;
343 $grade_raw->grademin = 0;
344 $grade_item->grademax = 100;
345 $grade_item->grademin = 0;
347 $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
349 // Rescaling from a small scale (0-50) to a larger scale with offset (40-100)
350 $grade_item->grademax = 100;
351 $grade_item->grademin = 40;
353 $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
355 // Try multfactor and plusfactor
356 $grade_raw = clone($original_grade_raw);
357 $grade_item = clone($original_grade_item);
358 $grade_item->multfactor = 1.23;
359 $grade_item->plusfactor = 3;
361 $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
363 // Try multfactor below 0 and a negative plusfactor
364 $grade_raw = clone($original_grade_raw);
365 $grade_item = clone($original_grade_item);
366 $grade_item->multfactor = 0.23;
367 $grade_item->plusfactor = -3;
369 $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
372 // Test locking of grade items
373 protected function sub_test_grade_item_set_locked() {
374 //getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
375 //also needs to have at least one grade_grade or $grade_item->get_final(1) returns null
376 //$grade_item = new grade_item($this->grade_items[8]);
377 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
379 $this->assertTrue(method_exists($grade_item, 'set_locked'));
381 $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
382 $this->assertTrue(empty($grade_item->locked));//not locked
383 $this->assertTrue(empty($grade_grade->locked));//not locked
385 $this->assertTrue($grade_item->set_locked(true, true, false));
386 $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
388 $this->assertFalse(empty($grade_item->locked));//locked
389 $this->assertFalse(empty($grade_grade->locked)); // individual grades should be locked too
391 $this->assertTrue($grade_item->set_locked(false, true, false));
392 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
394 $this->assertTrue(empty($grade_item->locked));
395 $this->assertTrue(empty($grade->locked)); // individual grades should be unlocked too
398 protected function sub_test_grade_item_is_locked() {
399 $grade_item = new grade_item($this->grade_items[10], false);
400 $this->assertTrue(method_exists($grade_item, 'is_locked'));
402 $this->assertFalse($grade_item->is_locked());
403 $this->assertFalse($grade_item->is_locked($this->user[1]->id));
404 $this->assertTrue($grade_item->set_locked(true, true, false));
405 $this->assertTrue($grade_item->is_locked());
406 $this->assertTrue($grade_item->is_locked($this->user[1]->id));
409 // Test hiding of grade items
410 protected function sub_test_grade_item_set_hidden() {
411 $grade_item = new grade_item($this->grade_items[0], false);
412 $this->assertTrue(method_exists($grade_item, 'set_hidden'));
414 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
415 $this->assertEquals(0, $grade_item->hidden);
416 $this->assertEquals(0, $grade->hidden);
418 $grade_item->set_hidden(666, true);
419 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
421 $this->assertEquals(666, $grade_item->hidden);
422 $this->assertEquals(666, $grade->hidden);
425 protected function sub_test_grade_item_is_hidden() {
426 $grade_item = new grade_item($this->grade_items[0], false);
427 $this->assertTrue(method_exists($grade_item, 'is_hidden'));
429 $this->assertFalse($grade_item->is_hidden());
430 $this->assertFalse($grade_item->is_hidden(1));
432 $grade_item->set_hidden(1);
433 $this->assertTrue($grade_item->is_hidden());
434 $this->assertTrue($grade_item->is_hidden(1));
436 $grade_item->set_hidden(666);
437 $this->assertFalse($grade_item->is_hidden());
438 $this->assertFalse($grade_item->is_hidden(1));
440 $grade_item->set_hidden(time()+666);
441 $this->assertTrue($grade_item->is_hidden());
442 $this->assertTrue($grade_item->is_hidden(1));
445 protected function sub_test_grade_item_is_category_item() {
446 $grade_item = new grade_item($this->grade_items[3], false);
447 $this->assertTrue(method_exists($grade_item, 'is_category_item'));
448 $this->assertTrue($grade_item->is_category_item());
451 protected function sub_test_grade_item_is_course_item() {
452 $grade_item = grade_item::fetch_course_item($this->courseid);
453 $this->assertTrue(method_exists($grade_item, 'is_course_item'));
454 $this->assertTrue($grade_item->is_course_item());
457 protected function sub_test_grade_item_fetch_course_item() {
458 $grade_item = grade_item::fetch_course_item($this->courseid);
459 $this->assertTrue(method_exists($grade_item, 'fetch_course_item'));
460 $this->assertEquals($grade_item->itemtype, 'course');
463 protected function sub_test_grade_item_depends_on() {
464 $grade_item = new grade_item($this->grade_items[1], false);
466 // calculated grade dependency
467 $deps = $grade_item->depends_on();
468 sort($deps, SORT_NUMERIC); // for comparison
469 $this->assertEquals(array($this->grade_items[0]->id), $deps);
471 // simulate depends on returns none when locked
472 $grade_item->locked = time();
473 $grade_item->update();
474 $deps = $grade_item->depends_on();
475 sort($deps, SORT_NUMERIC); // for comparison
476 $this->assertEquals(array(), $deps);
478 // category dependency
479 $grade_item = new grade_item($this->grade_items[3], false);
480 $deps = $grade_item->depends_on();
481 sort($deps, SORT_NUMERIC); // for comparison
482 $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
483 $this->assertEquals($res, $deps);
486 protected function sub_test_grade_item_is_calculated() {
487 $grade_item = new grade_item($this->grade_items[1], false);
488 $this->assertTrue(method_exists($grade_item, 'is_calculated'));
489 $this->assertTrue($grade_item->is_calculated());
491 $grade_item = new grade_item($this->grade_items[0], false);
492 $this->assertFalse($grade_item->is_calculated());
495 protected function sub_test_grade_item_set_calculation() {
496 $grade_item = new grade_item($this->grade_items[1], false);
497 $this->assertTrue(method_exists($grade_item, 'set_calculation'));
498 $grade_itemsource = new grade_item($this->grade_items[0], false);
500 $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
502 $this->assertTrue(!empty($grade_item->needsupdate));
503 $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
506 protected function sub_test_grade_item_get_calculation() {
507 $grade_item = new grade_item($this->grade_items[1], false);
508 $this->assertTrue(method_exists($grade_item, 'get_calculation'));
509 $grade_itemsource = new grade_item($this->grade_items[0], false);
511 $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
513 $formula = $grade_item->get_calculation();
514 $this->assertTrue(!empty($grade_item->needsupdate));
515 $this->assertEquals($denormalizedformula, $formula);
518 public function sub_test_grade_item_compute() {
519 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
520 $this->assertTrue(method_exists($grade_item, 'compute'));
522 //check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades
523 $this->grade_grades[3] = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
524 $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
525 $grade_grade->delete();
527 $this->grade_grades[4] = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
528 $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
529 $grade_grade->delete();
531 $this->grade_grades[5] = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
532 $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
533 $grade_grade->delete();
535 //recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them
536 $grade_item->compute();
538 $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[3]->userid, 'itemid'=>$this->grade_grades[3]->itemid));
539 $this->assertEquals($this->grade_grades[3]->finalgrade, $grade_grade->finalgrade);
541 $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[4]->userid, 'itemid'=>$this->grade_grades[4]->itemid));
542 $this->assertEquals($this->grade_grades[4]->finalgrade, $grade_grade->finalgrade);
544 $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[5]->userid, 'itemid'=>$this->grade_grades[5]->itemid));
545 $this->assertEquals($this->grade_grades[5]->finalgrade, $grade_grade->finalgrade);