MDL-54613 unit tests: Add iteminstance to test grade_item
[moodle.git] / lib / grade / tests / grade_item_test.php
CommitLineData
948d46da
PS
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
24defined('MOODLE_INTERNAL') || die();
25
26require_once(__DIR__.'/fixtures/lib.php');
27
8252b7c2 28class core_grade_item_testcase extends grade_base_testcase {
948d46da
PS
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();
d629c601 53 $this->sub_test_grade_item_rescale_grades_keep_percentage();
948d46da
PS
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();
69bcca5e 62 $this->sub_test_refresh_grades();
948d46da
PS
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();
59772c06 67 $this->sub_test_update_final_grade();
39873128 68 $this->sub_test_grade_item_can_control_visibility();
8acec2a6 69 $this->sub_test_grade_item_fix_sortorder();
948d46da
PS
70 }
71
72 protected function sub_test_grade_item_construct() {
73 $params = new stdClass();
74
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';
81
82 $grade_item = new grade_item($params, false);
83
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 }
88
89 protected function sub_test_grade_item_insert() {
90 $grade_item = new grade_item();
91 $this->assertTrue(method_exists($grade_item, 'insert'));
92
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';
99
100 $grade_item->insert();
101
102 $last_grade_item = end($this->grade_items);
103
104 $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
87a26cc4 105 $this->assertEquals(18, $grade_item->sortorder);
948d46da 106
c952e091 107 // Keep our reference collection the same as what is in the database.
948d46da
PS
108 $this->grade_items[] = $grade_item;
109 }
110
111 protected function sub_test_grade_item_delete() {
112 global $DB;
c952e091 113 $grade_item = new grade_item($this->grade_items[7], false); // Use a grade item not touched by previous (or future) tests.
948d46da
PS
114 $this->assertTrue(method_exists($grade_item, 'delete'));
115
116 $this->assertTrue($grade_item->delete());
117
118 $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
119
c952e091 120 // Keep our reference collection the same as the database.
948d46da
PS
121 unset($this->grade_items[7]);
122 }
123
124 protected function sub_test_grade_item_update() {
125 global $DB;
bd42a265 126 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
127 $this->assertTrue(method_exists($grade_item, 'update'));
128
129 $grade_item->iteminfo = 'Updated info for this unittest grade_item';
130
131 $this->assertTrue($grade_item->update());
132
133 $grade_item->grademin = 14;
134 $this->assertTrue($grade_item->qualifies_for_regrading());
135 $this->assertTrue($grade_item->update());
136
137 $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
138 $this->assertEquals($grade_item->iteminfo, $iteminfo);
139 }
140
141 protected function sub_test_grade_item_load_scale() {
bd42a265 142 $grade_item = new grade_item($this->grade_items[2], false);
948d46da
PS
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 }
148
149 protected function sub_test_grade_item_load_outcome() {
bd42a265 150 $grade_item = new grade_item($this->grade_items[0], false);
948d46da 151 $this->assertTrue(method_exists($grade_item, 'load_outcome'));
c952e091 152 // TODO: add tests.
948d46da
PS
153 }
154
155 protected function sub_test_grade_item_qualifies_for_regrading() {
c952e091 156 $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
948d46da
PS
157 $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
158
159 $this->assertFalse($grade_item->qualifies_for_regrading());
160
161 $grade_item->iteminfo = 'Updated info for this unittest grade_item';
162
163 $this->assertFalse($grade_item->qualifies_for_regrading());
164
165 $grade_item->grademin = 14;
166
167 $this->assertTrue($grade_item->qualifies_for_regrading());
168 }
169
170 protected function sub_test_grade_item_force_regrading() {
c952e091 171 $grade_item = new grade_item($this->grade_items[3], false); // Use a grade item not touched by previous tests.
948d46da
PS
172 $this->assertTrue(method_exists($grade_item, 'force_regrading'));
173
174 $this->assertEquals(0, $grade_item->needsupdate);
175
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 }
181
182 protected function sub_test_grade_item_fetch() {
183 $grade_item = new grade_item();
184 $this->assertTrue(method_exists($grade_item, 'fetch'));
185
c952e091 186 // Not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading().
948d46da
PS
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);
190
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 }
195
196 protected function sub_test_grade_item_fetch_all() {
197 $grade_item = new grade_item();
198 $this->assertTrue(method_exists($grade_item, 'fetch_all'));
199
200 $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
c952e091 201 $this->assertEquals(count($this->grade_items), count($grade_items)-1); // -1 to account for the course grade item.
948d46da
PS
202 }
203
204 // Retrieve all final scores for a given grade_item.
205 protected function sub_test_grade_item_get_all_finals() {
bd42a265 206 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
207 $this->assertTrue(method_exists($grade_item, 'get_final'));
208
209 $final_grades = $grade_item->get_final();
210 $this->assertEquals(3, count($final_grades));
211 }
212
213
214 // Retrieve all final scores for a specific userid.
215 protected function sub_test_grade_item_get_final() {
bd42a265 216 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
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 }
221
222 protected function sub_test_grade_item_get_sortorder() {
bd42a265 223 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
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 }
228
229 protected function sub_test_grade_item_set_sortorder() {
bd42a265 230 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
231 $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
232 $grade_item->set_sortorder(999);
233 $this->assertEquals($grade_item->sortorder, 999);
234 }
235
236 protected function sub_test_grade_item_move_after_sortorder() {
bd42a265 237 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
238 $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
239 $grade_item->move_after_sortorder(5);
240 $this->assertEquals($grade_item->sortorder, 6);
241
242 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
243 $this->assertEquals($grade_item->sortorder, 6);
244
245 $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
246 $this->assertEquals($after->sortorder, 8);
247 }
248
249 protected function sub_test_grade_item_get_name() {
bd42a265 250 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
251 $this->assertTrue(method_exists($grade_item, 'get_name'));
252
253 $name = $grade_item->get_name();
254 $this->assertEquals($this->grade_items[0]->itemname, $name);
255 }
256
257 protected function sub_test_grade_item_set_parent() {
bd42a265 258 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
259 $this->assertTrue(method_exists($grade_item, 'set_parent'));
260
261 $old = $grade_item->get_parent_category();
bd42a265 262 $new = new grade_category($this->grade_categories[3], false);
948d46da
PS
263 $new_item = $new->get_grade_item();
264
265 $this->assertTrue($grade_item->set_parent($new->id));
266
267 $new_item->update_from_db();
268 $grade_item->update_from_db();
269
270 $this->assertEquals($grade_item->categoryid, $new->id);
271 }
272
273 protected function sub_test_grade_item_get_parent_category() {
bd42a265 274 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
275 $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
276
277 $category = $grade_item->get_parent_category();
278 $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
279 }
280
281 protected function sub_test_grade_item_load_parent_category() {
bd42a265 282 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
283 $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
284
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 }
289
290 protected function sub_test_grade_item_get_item_category() {
bd42a265 291 $grade_item = new grade_item($this->grade_items[3], false);
948d46da
PS
292 $this->assertTrue(method_exists($grade_item, 'get_item_category'));
293
294 $category = $grade_item->get_item_category();
295 $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
296 }
297
298 protected function sub_test_grade_item_load_item_category() {
bd42a265 299 $grade_item = new grade_item($this->grade_items[3], false);
948d46da
PS
300 $this->assertTrue(method_exists($grade_item, 'load_item_category'));
301
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 }
306
948d46da 307 protected function sub_test_grade_item_regrade_final_grades() {
bd42a265 308 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
309 $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
310 $this->assertEquals(true, $grade_item->regrade_final_grades());
c952e091 311 // TODO: add more tests.
948d46da
PS
312 }
313
948d46da 314 protected function sub_test_grade_item_adjust_raw_grade() {
c952e091 315 $grade_item = new grade_item($this->grade_items[2], false); // Anything but assignment module!
948d46da
PS
316 $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
317
318 $grade_raw = new stdClass();
319 $grade_raw->rawgrade = 40;
320 $grade_raw->grademax = 100;
321 $grade_raw->grademin = 0;
322
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;
328
329 $original_grade_raw = clone($grade_raw);
330 $original_grade_item = clone($grade_item);
331
332 $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
333
c952e091 334 // Try a larger maximum grade.
948d46da
PS
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));
338
c952e091 339 // Try larger minimum grade.
948d46da
PS
340 $grade_item->grademin = 50;
341
342 $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
343
c952e091 344 // Rescaling from a small scale (0-50) to a larger scale (0-100).
948d46da
PS
345 $grade_raw->grademax = 50;
346 $grade_raw->grademin = 0;
347 $grade_item->grademax = 100;
348 $grade_item->grademin = 0;
349
350 $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
351
c952e091 352 // Rescaling from a small scale (0-50) to a larger scale with offset (40-100).
948d46da
PS
353 $grade_item->grademax = 100;
354 $grade_item->grademin = 40;
355
356 $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
357
c952e091 358 // Try multfactor and plusfactor.
948d46da
PS
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;
363
364 $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
365
c952e091 366 // Try multfactor below 0 and a negative plusfactor.
948d46da
PS
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;
371
372 $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
373 }
374
d629c601
DW
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.
378
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);
390
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);
396
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');
402
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);
408
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 }
414
948d46da 415 protected function sub_test_grade_item_set_locked() {
c952e091
PS
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]);
948d46da
PS
419 $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
420
421 $this->assertTrue(method_exists($grade_item, 'set_locked'));
422
bd42a265 423 $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
c952e091
PS
424 $this->assertTrue(empty($grade_item->locked));// Not locked.
425 $this->assertTrue(empty($grade_grade->locked));// Not locked.
948d46da
PS
426
427 $this->assertTrue($grade_item->set_locked(true, true, false));
bd42a265 428 $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
948d46da 429
c952e091
PS
430 $this->assertFalse(empty($grade_item->locked));// Locked.
431 $this->assertFalse(empty($grade_grade->locked)); // Individual grades should be locked too.
948d46da
PS
432
433 $this->assertTrue($grade_item->set_locked(false, true, false));
bd42a265 434 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
948d46da
PS
435
436 $this->assertTrue(empty($grade_item->locked));
c952e091 437 $this->assertTrue(empty($grade->locked)); // Individual grades should be unlocked too.
948d46da
PS
438 }
439
440 protected function sub_test_grade_item_is_locked() {
bd42a265 441 $grade_item = new grade_item($this->grade_items[10], false);
948d46da
PS
442 $this->assertTrue(method_exists($grade_item, 'is_locked'));
443
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 }
450
948d46da 451 protected function sub_test_grade_item_set_hidden() {
bd42a265 452 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
453 $this->assertTrue(method_exists($grade_item, 'set_hidden'));
454
bd42a265 455 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
948d46da
PS
456 $this->assertEquals(0, $grade_item->hidden);
457 $this->assertEquals(0, $grade->hidden);
458
459 $grade_item->set_hidden(666, true);
bd42a265 460 $grade = new grade_grade($grade_item->get_final($this->user[1]->id), false);
948d46da
PS
461
462 $this->assertEquals(666, $grade_item->hidden);
463 $this->assertEquals(666, $grade->hidden);
464 }
465
466 protected function sub_test_grade_item_is_hidden() {
bd42a265 467 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
468 $this->assertTrue(method_exists($grade_item, 'is_hidden'));
469
470 $this->assertFalse($grade_item->is_hidden());
471 $this->assertFalse($grade_item->is_hidden(1));
472
473 $grade_item->set_hidden(1);
474 $this->assertTrue($grade_item->is_hidden());
475 $this->assertTrue($grade_item->is_hidden(1));
476
477 $grade_item->set_hidden(666);
478 $this->assertFalse($grade_item->is_hidden());
479 $this->assertFalse($grade_item->is_hidden(1));
480
481 $grade_item->set_hidden(time()+666);
482 $this->assertTrue($grade_item->is_hidden());
483 $this->assertTrue($grade_item->is_hidden(1));
484 }
485
486 protected function sub_test_grade_item_is_category_item() {
bd42a265 487 $grade_item = new grade_item($this->grade_items[3], false);
948d46da
PS
488 $this->assertTrue(method_exists($grade_item, 'is_category_item'));
489 $this->assertTrue($grade_item->is_category_item());
490 }
491
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 }
497
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 }
503
504 protected function sub_test_grade_item_depends_on() {
87a26cc4
SC
505 global $CFG;
506
507 $origenableoutcomes = $CFG->enableoutcomes;
508 $CFG->enableoutcomes = 0;
bd42a265 509 $grade_item = new grade_item($this->grade_items[1], false);
948d46da 510
c952e091 511 // Calculated grade dependency.
948d46da 512 $deps = $grade_item->depends_on();
c952e091 513 sort($deps, SORT_NUMERIC); // For comparison.
948d46da
PS
514 $this->assertEquals(array($this->grade_items[0]->id), $deps);
515
c952e091 516 // Simulate depends on returns none when locked.
948d46da
PS
517 $grade_item->locked = time();
518 $grade_item->update();
519 $deps = $grade_item->depends_on();
c952e091 520 sort($deps, SORT_NUMERIC); // For comparison.
948d46da
PS
521 $this->assertEquals(array(), $deps);
522
c952e091 523 // Category dependency.
bd42a265 524 $grade_item = new grade_item($this->grade_items[3], false);
948d46da 525 $deps = $grade_item->depends_on();
c952e091 526 sort($deps, SORT_NUMERIC); // For comparison.
948d46da
PS
527 $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
528 $this->assertEquals($res, $deps);
47d6e6a7 529 }
87a26cc4 530
47d6e6a7 531 protected function scales_outcomes_test_grade_item_depends_on() {
87a26cc4
SC
532 $CFG->enableoutcomes = 1;
533 $origgradeincludescalesinaggregation = $CFG->grade_includescalesinaggregation;
534 $CFG->grade_includescalesinaggregation = 1;
535
47d6e6a7
DW
536 // Scale item in category with $CFG->grade_includescalesinaggregation = 1.
537 $grade_item = new grade_item($this->grade_items[14], false);
87a26cc4
SC
538 $deps = $grade_item->depends_on();
539 sort($deps, SORT_NUMERIC);
47d6e6a7 540 $res = array($this->grade_items[16]->id);
87a26cc4
SC
541 $this->assertEquals($res, $deps);
542
47d6e6a7 543 // Scale item in category with $CFG->grade_includescalesinaggregation = 0.
87a26cc4 544 $CFG->grade_includescalesinaggregation = 0;
47d6e6a7 545 $grade_item = new grade_item($this->grade_items[14], false);
87a26cc4 546 $deps = $grade_item->depends_on();
47d6e6a7 547 $res = array();
87a26cc4
SC
548 $this->assertEquals($res, $deps);
549 $CFG->grade_includescalesinaggregation = 1;
550
47d6e6a7 551 // Outcome item in category with outcomes disabled.
87a26cc4 552 $CFG->enableoutcomes = 0;
47d6e6a7 553 $grade_item = new grade_item($this->grade_items[14], false);
87a26cc4
SC
554 $deps = $grade_item->depends_on();
555 sort($deps, SORT_NUMERIC);
47d6e6a7 556 $res = array($this->grade_items[16]->id, $this->grade_items[17]->id);
87a26cc4
SC
557 $this->assertEquals($res, $deps);
558
559 $CFG->enableoutcomes = $origenableoutcomes;
560 $CFG->grade_includescalesinaggregation = $origgradeincludescalesinaggregation;
948d46da
PS
561 }
562
69bcca5e
AD
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());
568
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 }
574
948d46da 575 protected function sub_test_grade_item_is_calculated() {
bd42a265 576 $grade_item = new grade_item($this->grade_items[1], false);
948d46da
PS
577 $this->assertTrue(method_exists($grade_item, 'is_calculated'));
578 $this->assertTrue($grade_item->is_calculated());
579
bd42a265 580 $grade_item = new grade_item($this->grade_items[0], false);
948d46da
PS
581 $this->assertFalse($grade_item->is_calculated());
582 }
583
584 protected function sub_test_grade_item_set_calculation() {
bd42a265 585 $grade_item = new grade_item($this->grade_items[1], false);
948d46da 586 $this->assertTrue(method_exists($grade_item, 'set_calculation'));
bd42a265 587 $grade_itemsource = new grade_item($this->grade_items[0], false);
948d46da
PS
588
589 $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
590
591 $this->assertTrue(!empty($grade_item->needsupdate));
592 $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
593 }
594
595 protected function sub_test_grade_item_get_calculation() {
bd42a265 596 $grade_item = new grade_item($this->grade_items[1], false);
948d46da 597 $this->assertTrue(method_exists($grade_item, 'get_calculation'));
bd42a265 598 $grade_itemsource = new grade_item($this->grade_items[0], false);
948d46da
PS
599
600 $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
601
602 $formula = $grade_item->get_calculation();
603 $this->assertTrue(!empty($grade_item->needsupdate));
604 $this->assertEquals($denormalizedformula, $formula);
605 }
606
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'));
610
c952e091 611 // Check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades.
948d46da
PS
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();
615
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();
619
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();
623
c952e091 624 // Recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them.
948d46da
PS
625 $grade_item->compute();
626
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);
629
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);
632
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 }
59772c06
AD
636
637 protected function sub_test_update_final_grade() {
638
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;
643
644 // Create a brand new grade item.
645 $grade_item = new grade_item();
646 $this->assertTrue(method_exists($grade_item, 'insert'));
647
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';
0c0f01aa 654 $grade_item->iteminstance = $this->activities[7]->id;
59772c06
AD
655 $grade_item->grademin = $min;
656 $grade_item->grademax = $max;
657 $grade_item->insert();
658
659 // Override the student grade.
660 $grade_item->update_final_grade($this->user[1]->id, 7, 'gradebook', '', FORMAT_MOODLE);
661
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 }
39873128
TH
667
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());
672
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 }
8acec2a6
RT
677
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;
684
685 $this->resetAfterTest(true);
686
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();
705
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 }
716
717 $duplicatedetectionsql = "SELECT courseid, sortorder
718 FROM {grade_items}
719 WHERE courseid = :courseid
720 GROUP BY courseid, sortorder
721 HAVING COUNT(id) > 1";
722
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 }
730
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 }
746
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');
763
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();
774
775 $item->id = $DB->insert_record('grade_items', $item);
776
777 return $DB->get_record('grade_items', array('id' => $item->id));
778 }
01a088e0
FM
779
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);
783
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);
787
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 }
799
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);
804
805 foreach ($methods as $to) {
806 $tosupportsec = grade_category::aggregation_uses_extracredit($to);
807 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
808
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 }
815
816 // We ignore those fields, we know it is never used for extra credit.
817 $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
818 $gi->weightoverride = $todefaults['weightoverride'];
819
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);
823
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 }
846
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);
850
851 foreach ($methods as $to) {
852 if (!grade_category::aggregation_uses_extracredit($to)) {
853 continue;
854 }
855
856 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
857 $gi->aggregationcoef = 8;
858
859 // Ignore those fields, they are not used for extra credit.
860 $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
861 $gi->weightoverride = $todefaults['weightoverride'];
862
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 }
871
872 // Weights are reset.
873 $from = GRADE_AGGREGATE_SUM;
874 $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
875
876 $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
877 $gi->aggregationcoef2 = 0.321;
878 $gi->weightoverride = $fromdefaults['weightoverride'];
879
880 $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
881 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
882
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);
887
888 $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
889 $gi->aggregationcoef2 = 0.321;
890 $gi->weightoverride = $fromdefaults['weightoverride'];
891
892 $to = GRADE_AGGREGATE_SUM;
893 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
894
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);
899
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);
903
904 $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
905 $gi->aggregationcoef2 = 0.321;
906 $gi->weightoverride = 1;
907
908 $to = GRADE_AGGREGATE_SUM;
909 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
910
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);
915
916 $gi->aggregationcoef2 = 0.321;
917 $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
918 $gi->weightoverride = 1;
919
920 $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
921 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
922
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);
927
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);
931
932 $gi->aggregationcoef = 18;
933 $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
934 $gi->weightoverride = $fromdefaults['weightoverride'];
935
936 $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
937 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
938
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);
943
944 $gi->aggregationcoef = 18;
945 $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
946 $gi->weightoverride = $fromdefaults['weightoverride'];
947
948 $to = GRADE_AGGREGATE_SUM;
949 $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
950
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 }
956
948d46da 957}