PageRenderTime 96ms CodeModel.GetById 26ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/grade/tests/grade_category_test.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 798 lines | 562 code | 128 blank | 108 comment | 3 complexity | 1a9f31a4f4c294227cc2bf0912d91817 MD5 | raw file
  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
 28
 29class core_grade_category_testcase extends grade_base_testcase {
 30
 31    public function test_grade_category() {
 32        $this->sub_test_grade_category_construct();
 33        $this->sub_test_grade_category_build_path();
 34        $this->sub_test_grade_category_fetch();
 35        $this->sub_test_grade_category_fetch_all();
 36        $this->sub_test_grade_category_update();
 37        $this->sub_test_grade_category_delete();
 38        $this->sub_test_grade_category_insert();
 39        $this->sub_test_grade_category_qualifies_for_regrading();
 40        $this->sub_test_grade_category_force_regrading();
 41        $this->sub_test_grade_category_aggregate_grades();
 42        $this->sub_test_grade_category_apply_limit_rules();
 43        $this->sub_test_grade_category_is_aggregationcoef_used();
 44        $this->sub_test_grade_category_aggregation_uses_aggregationcoef();
 45        $this->sub_test_grade_category_fetch_course_tree();
 46        $this->sub_test_grade_category_get_children();
 47        $this->sub_test_grade_category_load_grade_item();
 48        $this->sub_test_grade_category_get_grade_item();
 49        $this->sub_test_grade_category_load_parent_category();
 50        $this->sub_test_grade_category_get_parent_category();
 51        $this->sub_test_grade_category_get_name();
 52        $this->sub_test_grade_category_set_parent();
 53        $this->sub_test_grade_category_get_final();
 54        $this->sub_test_grade_category_get_sortorder();
 55        $this->sub_test_grade_category_set_sortorder();
 56        $this->sub_test_grade_category_is_editable();
 57        $this->sub_test_grade_category_move_after_sortorder();
 58        $this->sub_test_grade_category_is_course_category();
 59        $this->sub_test_grade_category_fetch_course_category();
 60        $this->sub_test_grade_category_is_locked();
 61        $this->sub_test_grade_category_set_locked();
 62        $this->sub_test_grade_category_is_hidden();
 63        $this->sub_test_grade_category_set_hidden();
 64        $this->sub_test_grade_category_can_control_visibility();
 65
 66        // This won't work until MDL-11837 is complete.
 67        // $this->sub_test_grade_category_generate_grades();
 68
 69        // Do this last as adding a second course category messes up the data.
 70        $this->sub_test_grade_category_insert_course_category();
 71        $this->sub_test_grade_category_is_extracredit_used();
 72        $this->sub_test_grade_category_aggregation_uses_extracredit();
 73    }
 74
 75    // Adds 3 new grade categories at various depths.
 76    protected function sub_test_grade_category_construct() {
 77        $course_category = grade_category::fetch_course_category($this->courseid);
 78
 79        $params = new stdClass();
 80
 81        $params->courseid = $this->courseid;
 82        $params->fullname = 'unittestcategory4';
 83
 84        $grade_category = new grade_category($params, false);
 85        $grade_category->insert();
 86        $this->grade_categories[] = $grade_category;
 87
 88        $this->assertEquals($params->courseid, $grade_category->courseid);
 89        $this->assertEquals($params->fullname, $grade_category->fullname);
 90        $this->assertEquals(2, $grade_category->depth);
 91        $this->assertEquals("/$course_category->id/$grade_category->id/", $grade_category->path);
 92        $parentpath = $grade_category->path;
 93
 94        // Test a child category.
 95        $params->parent = $grade_category->id;
 96        $params->fullname = 'unittestcategory5';
 97        $grade_category = new grade_category($params, false);
 98        $grade_category->insert();
 99        $this->grade_categories[] = $grade_category;
100
101        $this->assertEquals(3, $grade_category->depth);
102        $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
103        $parentpath = $grade_category->path;
104
105        // Test a third depth category.
106        $params->parent = $grade_category->id;
107        $params->fullname = 'unittestcategory6';
108        $grade_category = new grade_category($params, false);
109        $grade_category->insert();
110        $this->grade_categories[50] = $grade_category;// Going to delete this one later hence the special index.
111
112        $this->assertEquals(4, $grade_category->depth);
113        $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
114    }
115
116    protected function sub_test_grade_category_build_path() {
117        $grade_category = new grade_category($this->grade_categories[1]);
118        $this->assertTrue(method_exists($grade_category, 'build_path'));
119        $path = grade_category::build_path($grade_category);
120        $this->assertEquals($grade_category->path, $path);
121    }
122
123    protected function sub_test_grade_category_fetch() {
124        $grade_category = new grade_category();
125        $this->assertTrue(method_exists($grade_category, 'fetch'));
126
127        $grade_category = grade_category::fetch(array('id'=>$this->grade_categories[0]->id));
128        $this->assertEquals($this->grade_categories[0]->id, $grade_category->id);
129        $this->assertEquals($this->grade_categories[0]->fullname, $grade_category->fullname);
130    }
131
132    protected function sub_test_grade_category_fetch_all() {
133        $grade_category = new grade_category();
134        $this->assertTrue(method_exists($grade_category, 'fetch_all'));
135
136        $grade_categories = grade_category::fetch_all(array('courseid'=>$this->courseid));
137        $this->assertEquals(count($this->grade_categories), count($grade_categories)-1);
138    }
139
140    protected function sub_test_grade_category_update() {
141        global $DB;
142        $grade_category = new grade_category($this->grade_categories[0]);
143        $this->assertTrue(method_exists($grade_category, 'update'));
144
145        $grade_category->fullname = 'Updated info for this unittest grade_category';
146        $grade_category->path = null; // Path must be recalculated if missing.
147        $grade_category->depth = null;
148        $grade_category->aggregation = GRADE_AGGREGATE_MAX; // Should force regrading.
149
150        $grade_item = $grade_category->get_grade_item();
151        $this->assertEquals(0, $grade_item->needsupdate);
152
153        $this->assertTrue($grade_category->update());
154
155        $fullname = $DB->get_field('grade_categories', 'fullname', array('id' => $this->grade_categories[0]->id));
156        $this->assertEquals($grade_category->fullname, $fullname);
157
158        $path = $DB->get_field('grade_categories', 'path', array('id' => $this->grade_categories[0]->id));
159        $this->assertEquals($grade_category->path, $path);
160
161        $depth = $DB->get_field('grade_categories', 'depth', array('id' => $this->grade_categories[0]->id));
162        $this->assertEquals($grade_category->depth, $depth);
163
164        $grade_item = $grade_category->get_grade_item();
165        $this->assertEquals(1, $grade_item->needsupdate);
166    }
167
168    protected function sub_test_grade_category_delete() {
169        global $DB;
170
171        $grade_category = new grade_category($this->grade_categories[50]);
172        $this->assertTrue(method_exists($grade_category, 'delete'));
173
174        $this->assertTrue($grade_category->delete());
175        $this->assertFalse($DB->get_record('grade_categories', array('id' => $grade_category->id)));
176    }
177
178    protected function sub_test_grade_category_insert() {
179        $course_category = grade_category::fetch_course_category($this->courseid);
180
181        $grade_category = new grade_category();
182        $this->assertTrue(method_exists($grade_category, 'insert'));
183
184        $grade_category->fullname    = 'unittestcategory4';
185        $grade_category->courseid    = $this->courseid;
186        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
187        $grade_category->aggregateonlygraded = 1;
188        $grade_category->keephigh    = 100;
189        $grade_category->droplow     = 10;
190        $grade_category->hidden      = 0;
191        $grade_category->parent      = $this->grade_categories[1]->id; // sub_test_grade_category_delete() removed the category at 0.
192
193        $grade_category->insert();
194
195        $this->assertEquals('/'.$course_category->id.'/'.$this->grade_categories[1]->parent.'/'.$this->grade_categories[1]->id.'/'.$grade_category->id.'/', $grade_category->path);
196        $this->assertEquals(4, $grade_category->depth);
197
198        $last_grade_category = end($this->grade_categories);
199
200        $this->assertFalse(empty($grade_category->grade_item));
201        $this->assertEquals($grade_category->id, $grade_category->grade_item->iteminstance);
202        $this->assertEquals('category', $grade_category->grade_item->itemtype);
203
204        $this->assertEquals($grade_category->id, $last_grade_category->id + 1);
205        $this->assertFalse(empty($grade_category->timecreated));
206        $this->assertFalse(empty($grade_category->timemodified));
207    }
208
209    protected function sub_test_grade_category_qualifies_for_regrading() {
210        $grade_category = new grade_category($this->grade_categories[1]);
211        $this->assertTrue(method_exists($grade_category, 'qualifies_for_regrading'));
212        $this->assertFalse($grade_category->qualifies_for_regrading());
213
214        $grade_category->aggregation = GRADE_AGGREGATE_MAX;
215        $this->assertTrue($grade_category->qualifies_for_regrading());
216
217        $grade_category = new grade_category($this->grade_categories[1]);
218        $grade_category->droplow = 99;
219        $this->assertTrue($grade_category->qualifies_for_regrading());
220
221        $grade_category = new grade_category($this->grade_categories[1]);
222        $grade_category->keephigh = 99;
223        $this->assertTrue($grade_category->qualifies_for_regrading());
224    }
225
226    protected function sub_test_grade_category_force_regrading() {
227        $grade_category = new grade_category($this->grade_categories[1]);
228        $this->assertTrue(method_exists($grade_category, 'force_regrading'));
229
230        $grade_category->load_grade_item();
231        $this->assertEquals(0, $grade_category->grade_item->needsupdate);
232
233        $grade_category->force_regrading();
234
235        $grade_category->grade_item = null;
236        $grade_category->load_grade_item();
237
238        $this->assertEquals(1, $grade_category->grade_item->needsupdate);
239    }
240
241    /**
242     * Tests the calculation of grades using the various aggregation methods with and without hidden grades
243     * This will not work entirely until MDL-11837 is done
244     */
245    protected function sub_test_grade_category_generate_grades() {
246        global $DB;
247
248        // Inserting some special grade items to make testing the final grade calculation easier.
249        $params = new stdClass();
250        $params->courseid = $this->courseid;
251        $params->fullname = 'unittestgradecalccategory';
252        $params->aggregation = GRADE_AGGREGATE_MEAN;
253        $params->aggregateonlygraded = 0;
254        $grade_category = new grade_category($params, false);
255        $grade_category->insert();
256
257        $this->assertTrue(method_exists($grade_category, 'generate_grades'));
258
259        $grade_category->load_grade_item();
260        $cgi = $grade_category->get_grade_item();
261        $cgi->grademin = 0;
262        $cgi->grademax = 20; // 3 grade items out of 10 but category is out of 20 to force scaling to occur.
263        $cgi->update();
264
265        // 3 grade items each with a maximum grade of 10.
266        $grade_items = array();
267        for ($i=0; $i<3; $i++) {
268            $grade_items[$i] = new grade_item();
269            $grade_items[$i]->courseid = $this->courseid;
270            $grade_items[$i]->categoryid = $grade_category->id;
271            $grade_items[$i]->itemname = 'manual grade_item '.$i;
272            $grade_items[$i]->itemtype = 'manual';
273            $grade_items[$i]->itemnumber = 0;
274            $grade_items[$i]->needsupdate = false;
275            $grade_items[$i]->gradetype = GRADE_TYPE_VALUE;
276            $grade_items[$i]->grademin = 0;
277            $grade_items[$i]->grademax = 10;
278            $grade_items[$i]->iteminfo = 'Manual grade item used for unit testing';
279            $grade_items[$i]->timecreated = time();
280            $grade_items[$i]->timemodified = time();
281
282            // Used as the weight by weighted mean and as extra credit by mean with extra credit.
283            // Will be 0, 1 and 2.
284            $grade_items[$i]->aggregationcoef = $i;
285
286            $grade_items[$i]->insert();
287        }
288
289        // A grade for each grade item.
290        $grade_grades = array();
291        for ($i=0; $i<3; $i++) {
292            $grade_grades[$i] = new grade_grade();
293            $grade_grades[$i]->itemid = $grade_items[$i]->id;
294            $grade_grades[$i]->userid = $this->userid;
295            $grade_grades[$i]->rawgrade = ($i+1)*2; // Produce grade grades of 2, 4 and 6.
296            $grade_grades[$i]->finalgrade = ($i+1)*2;
297            $grade_grades[$i]->timecreated = time();
298            $grade_grades[$i]->timemodified = time();
299            $grade_grades[$i]->information = '1 of 2 grade_grades';
300            $grade_grades[$i]->informationformat = FORMAT_PLAIN;
301            $grade_grades[$i]->feedback = 'Good, but not good enough..';
302            $grade_grades[$i]->feedbackformat = FORMAT_PLAIN;
303
304            $grade_grades[$i]->insert();
305        }
306
307        // 3 grade items with 1 grade_grade each.
308        // grade grades have the values 2, 4 and 6.
309
310        // First correct answer is the aggregate with all 3 grades.
311        // Second correct answer is with the first grade (value 2) hidden.
312
313        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEDIAN, 'GRADE_AGGREGATE_MEDIAN', 8, 8);
314        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MAX, 'GRADE_AGGREGATE_MAX', 12, 12);
315        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MODE, 'GRADE_AGGREGATE_MODE', 12, 12);
316
317        // Weighted mean. note grade totals are rounded to an int to prevent rounding discrepancies. correct final grade isnt actually exactly 10
318        // 3 items with grades 2, 4 and 6 with weights 0, 1 and 2 and all out of 10. then doubled to be out of 20.
319        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN, 'GRADE_AGGREGATE_WEIGHTED_MEAN', 10, 10);
320
321        // Simple weighted mean.
322        // 3 items with grades 2, 4 and 6 equally weighted and all out of 10. then doubled to be out of 20.
323        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN2, 'GRADE_AGGREGATE_WEIGHTED_MEAN2', 8, 10);
324
325        // Mean of grades with extra credit.
326        // 3 items with grades 2, 4 and 6 with extra credit 0, 1 and 2 equally weighted and all out of 10. then doubled to be out of 20.
327        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_EXTRACREDIT_MEAN, 'GRADE_AGGREGATE_EXTRACREDIT_MEAN', 10, 13);
328
329        // Aggregation tests the are affected by a hidden grade currently dont work as we dont store the altered grade in the database
330        // instead an in memory recalculation is done. This should be remedied by MDL-11837.
331
332        // Fails with 1 grade hidden. still reports 8 as being correct.
333        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEAN, 'GRADE_AGGREGATE_MEAN', 8, 10);
334
335        // Fails with 1 grade hidden. still reports 4 as being correct.
336        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MIN, 'GRADE_AGGREGATE_MIN', 4, 8);
337
338        // Fails with 1 grade hidden. still reports 12 as being correct.
339        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_SUM, 'GRADE_AGGREGATE_SUM', 12, 10);
340    }
341
342    /**
343     * Test grade category aggregation using the supplied grade objects and aggregation method
344     * @param grade_category $grade_category the category to be tested
345     * @param array $grade_items array of instance of grade_item
346     * @param array $grade_grades array of instances of grade_grade
347     * @param int $aggmethod the aggregation method to apply ie GRADE_AGGREGATE_MEAN
348     * @param string $aggmethodname the name of the aggregation method to apply. Used to display any test failure messages
349     * @param int $correct1 the correct final grade for the category with NO items hidden
350     * @param int $correct2 the correct final grade for the category with the grade at $grade_grades[0] hidden
351     * @return void
352     */
353    protected function helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, $aggmethod, $aggmethodname, $correct1, $correct2) {
354        $grade_category->aggregation = $aggmethod;
355        $grade_category->update();
356
357        // Check grade_item isnt hidden from a previous test.
358        $grade_items[0]->set_hidden(0, true);
359        $this->helper_test_grade_aggregation_result($grade_category, $correct1, 'Testing aggregation method('.$aggmethodname.') with no items hidden %s');
360
361        // Hide the grade item with grade of 2.
362        $grade_items[0]->set_hidden(1, true);
363        $this->helper_test_grade_aggregation_result($grade_category, $correct2, 'Testing aggregation method('.$aggmethodname.') with 1 item hidden %s');
364    }
365
366    /**
367     * Verify the value of the category grade item for $this->userid
368     * @param grade_category $grade_category the category to be tested
369     * @param int $correctgrade the expected grade
370     * @param string msg The message that should be displayed if the correct grade is not found
371     * @return void
372     */
373    protected function helper_test_grade_aggregation_result($grade_category, $correctgrade, $msg) {
374        global $DB;
375
376        $category_grade_item = $grade_category->get_grade_item();
377
378        // This creates all the grade_grades we need.
379        grade_regrade_final_grades($this->courseid);
380
381        $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
382        $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
383        $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
384
385        /*
386         * TODO this doesnt work as the grade_grades created by $grade_category->generate_grades(); dont
387         * observe the category's max grade
388        // delete the grade_grades for the category itself and check they get recreated correctly.
389        $DB->delete_records('grade_grades', array('itemid'=>$category_grade_item->id));
390        $grade_category->generate_grades();
391
392        $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
393        $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
394        $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
395         *
396         */
397    }
398
399    protected function sub_test_grade_category_aggregate_grades() {
400        $category = new grade_category($this->grade_categories[0]);
401        $this->assertTrue(method_exists($category, 'aggregate_grades'));
402        // Tested more fully via test_grade_category_generate_grades().
403    }
404
405    protected function sub_test_grade_category_apply_limit_rules() {
406        $items[$this->grade_items[0]->id] = new grade_item($this->grade_items[0], false);
407        $items[$this->grade_items[1]->id] = new grade_item($this->grade_items[1], false);
408        $items[$this->grade_items[2]->id] = new grade_item($this->grade_items[2], false);
409        $items[$this->grade_items[4]->id] = new grade_item($this->grade_items[4], false);
410
411        // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades.
412        $category = new grade_category();
413        $category->droplow = 2;
414        $grades = array($this->grade_items[0]->id=>5.374,
415                        $this->grade_items[1]->id=>9.4743,
416                        $this->grade_items[2]->id=>2.5474,
417                        $this->grade_items[4]->id=>7.3754);
418        $category->apply_limit_rules($grades, $items);
419        $this->assertEquals(count($grades), 2);
420        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
421        $this->assertEquals($grades[$this->grade_items[4]->id], 7.3754);
422
423        // Test aggregating only the highest 1 out of 4 grades.
424        $category = new grade_category();
425        $category->keephigh = 1;
426        $category->droplow = 0;
427        $grades = array($this->grade_items[0]->id=>5.374,
428                        $this->grade_items[1]->id=>9.4743,
429                        $this->grade_items[2]->id=>2.5474,
430                        $this->grade_items[4]->id=>7.3754);
431        $category->apply_limit_rules($grades, $items);
432        $this->assertEquals(count($grades), 1);
433        $grade = reset($grades);
434        $this->assertEquals(9.4743, $grade);
435
436        // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades.
437        // An extra credit grade item should be kept even if droplow means it would otherwise be excluded.
438        $category = new grade_category();
439        $category->droplow     = 2;
440        $category->aggregation = GRADE_AGGREGATE_SUM;
441        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
442        $grades = array($this->grade_items[0]->id=>5.374,
443                        $this->grade_items[1]->id=>9.4743,
444                        $this->grade_items[2]->id=>2.5474,
445                        $this->grade_items[4]->id=>7.3754);
446        $category->apply_limit_rules($grades, $items);
447        $this->assertEquals(count($grades), 2);
448        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
449        $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
450
451        // Test only aggregating the highest 1 out of 4 grades.
452        // An extra credit grade item is retained in addition to the highest grade.
453        $category = new grade_category();
454        $category->keephigh = 1;
455        $category->droplow = 0;
456        $category->aggregation = GRADE_AGGREGATE_SUM;
457        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
458        $grades = array($this->grade_items[0]->id=>5.374,
459                        $this->grade_items[1]->id=>9.4743,
460                        $this->grade_items[2]->id=>2.5474,
461                        $this->grade_items[4]->id=>7.3754);
462        $category->apply_limit_rules($grades, $items);
463        $this->assertEquals(count($grades), 2);
464        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
465        $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
466
467        // Test excluding the lowest 1 out of 4 grades from aggregation with two 0 grades.
468        $items[$this->grade_items[2]->id]->aggregationcoef = 0; // Undo marking grade item 2 as "extra credit".
469        $category = new grade_category();
470        $category->droplow     = 1;
471        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
472        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
473                        $this->grade_items[1]->id=>5, // 5 out of 100.
474                        $this->grade_items[2]->id=>2, // 0 out of 6.
475                        $this->grade_items[4]->id=>0); // 0 out of 100.
476        $category->apply_limit_rules($grades, $items);
477        $this->assertEquals(count($grades), 3);
478        $this->assertEquals($grades[$this->grade_items[1]->id], 5);
479        $this->assertEquals($grades[$this->grade_items[2]->id], 2);
480        $this->assertEquals($grades[$this->grade_items[4]->id], 0);
481
482        // Test excluding the lowest 2 out of 4 grades from aggregation with three 0 grades.
483        $category = new grade_category();
484        $category->droplow     = 2;
485        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
486        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
487                        $this->grade_items[1]->id=>5, // 5 out of 100.
488                        $this->grade_items[2]->id=>0, // 0 out of 6.
489                        $this->grade_items[4]->id=>0); // 0 out of 100. Should be excluded from aggregation.
490        $category->apply_limit_rules($grades, $items);
491        $this->assertEquals(count($grades), 2);
492        $this->assertEquals($grades[$this->grade_items[1]->id], 5);
493        $this->assertEquals($grades[$this->grade_items[2]->id], 0);
494
495        // Test excluding the lowest 5 out of 4 grades from aggregation.
496        // Just to check we handle this sensibly.
497        $category = new grade_category();
498        $category->droplow     = 5;
499        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
500        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
501                        $this->grade_items[1]->id=>5, // 5 out of 100.
502                        $this->grade_items[2]->id=>6, // 6 out of 6.
503                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
504        $category->apply_limit_rules($grades, $items);
505        $this->assertEquals(count($grades), 0);
506
507        // Test excluding the lowest 4 out of 4 grades from aggregation with one marked as extra credit.
508        $category = new grade_category();
509        $category->droplow     = 4;
510        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
511        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit".
512        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
513                        $this->grade_items[1]->id=>5, // 5 out of 100. Should be excluded from aggregation.
514                        $this->grade_items[2]->id=>6, // 6 out of 6. Extra credit. Should be retained.
515                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
516        $category->apply_limit_rules($grades, $items);
517        $this->assertEquals(count($grades), 1);
518        $this->assertEquals($grades[$this->grade_items[2]->id], 6);
519
520        // MDL-35667 - There was an infinite loop if several items had the same grade and at least one was extra credit.
521        $category = new grade_category();
522        $category->droplow     = 1;
523        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean.
524        $items[$this->grade_items[1]->id]->aggregationcoef = 1; // Mark grade item 1 as "extra credit".
525        $grades = array($this->grade_items[0]->id=>1, // 1 out of 110. Should be excluded from aggregation.
526                        $this->grade_items[1]->id=>1, // 1 out of 100. Extra credit. Should be retained.
527                        $this->grade_items[2]->id=>1, // 1 out of 6. Should be retained.
528                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be retained.
529        $category->apply_limit_rules($grades, $items);
530        $this->assertEquals(count($grades), 3);
531        $this->assertEquals($grades[$this->grade_items[1]->id], 1);
532        $this->assertEquals($grades[$this->grade_items[2]->id], 1);
533        $this->assertEquals($grades[$this->grade_items[4]->id], 1);
534
535    }
536
537    protected function sub_test_grade_category_is_aggregationcoef_used() {
538        $category = new grade_category();
539        // Following use aggregationcoef.
540        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
541        $this->assertTrue($category->is_aggregationcoef_used());
542        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
543        $this->assertTrue($category->is_aggregationcoef_used());
544        $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
545        $this->assertTrue($category->is_aggregationcoef_used());
546        $category->aggregation = GRADE_AGGREGATE_SUM;
547        $this->assertTrue($category->is_aggregationcoef_used());
548
549        // Following don't use aggregationcoef.
550        $category->aggregation = GRADE_AGGREGATE_MAX;
551        $this->assertFalse($category->is_aggregationcoef_used());
552        $category->aggregation = GRADE_AGGREGATE_MEAN;
553        $this->assertFalse($category->is_aggregationcoef_used());
554        $category->aggregation = GRADE_AGGREGATE_MEDIAN;
555        $this->assertFalse($category->is_aggregationcoef_used());
556        $category->aggregation = GRADE_AGGREGATE_MIN;
557        $this->assertFalse($category->is_aggregationcoef_used());
558        $category->aggregation = GRADE_AGGREGATE_MODE;
559        $this->assertFalse($category->is_aggregationcoef_used());
560    }
561
562    protected function sub_test_grade_category_aggregation_uses_aggregationcoef() {
563
564        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN));
565        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN2));
566        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
567        $this->assertTrue(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_SUM));
568
569        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MAX));
570        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEAN));
571        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEDIAN));
572        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MIN));
573        $this->assertFalse(grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MODE));
574    }
575
576    protected function sub_test_grade_category_fetch_course_tree() {
577        $category = new grade_category();
578        $this->assertTrue(method_exists($category, 'fetch_course_tree'));
579        // TODO: add some tests.
580    }
581
582    protected function sub_test_grade_category_get_children() {
583        $course_category = grade_category::fetch_course_category($this->courseid);
584
585        $category = new grade_category($this->grade_categories[0]);
586        $this->assertTrue(method_exists($category, 'get_children'));
587
588        $children_array = $category->get_children(0);
589
590        $this->assertTrue(is_array($children_array));
591        $this->assertFalse(empty($children_array[2]));
592        $this->assertFalse(empty($children_array[2]['object']));
593        $this->assertFalse(empty($children_array[2]['children']));
594        $this->assertEquals($this->grade_categories[1]->id, $children_array[2]['object']->id);
595        $this->assertEquals($this->grade_categories[2]->id, $children_array[5]['object']->id);
596        $this->assertEquals($this->grade_items[0]->id, $children_array[2]['children'][3]['object']->id);
597        $this->assertEquals($this->grade_items[1]->id, $children_array[2]['children'][4]['object']->id);
598        $this->assertEquals($this->grade_items[2]->id, $children_array[5]['children'][6]['object']->id);
599    }
600
601    protected function sub_test_grade_category_load_grade_item() {
602        $category = new grade_category($this->grade_categories[0]);
603        $this->assertTrue(method_exists($category, 'load_grade_item'));
604        $this->assertEquals(null, $category->grade_item);
605        $category->load_grade_item();
606        $this->assertEquals($this->grade_items[3]->id, $category->grade_item->id);
607    }
608
609    protected function sub_test_grade_category_get_grade_item() {
610        $category = new grade_category($this->grade_categories[0]);
611        $this->assertTrue(method_exists($category, 'get_grade_item'));
612        $grade_item = $category->get_grade_item();
613        $this->assertEquals($this->grade_items[3]->id, $grade_item->id);
614    }
615
616    protected function sub_test_grade_category_load_parent_category() {
617        $category = new grade_category($this->grade_categories[1]);
618        $this->assertTrue(method_exists($category, 'load_parent_category'));
619        $this->assertEquals(null, $category->parent_category);
620        $category->load_parent_category();
621        $this->assertEquals($this->grade_categories[0]->id, $category->parent_category->id);
622    }
623
624    protected function sub_test_grade_category_get_parent_category() {
625        $category = new grade_category($this->grade_categories[1]);
626        $this->assertTrue(method_exists($category, 'get_parent_category'));
627        $parent_category = $category->get_parent_category();
628        $this->assertEquals($this->grade_categories[0]->id, $parent_category->id);
629    }
630
631    protected function sub_test_grade_category_get_name() {
632        $category = new grade_category($this->grade_categories[0]);
633        $this->assertTrue(method_exists($category, 'get_name'));
634        $this->assertEquals($this->grade_categories[0]->fullname, $category->get_name());
635    }
636
637    protected function sub_test_grade_category_set_parent() {
638        $category = new grade_category($this->grade_categories[1]);
639        $this->assertTrue(method_exists($category, 'set_parent'));
640        // TODO: implement detailed tests.
641
642        $course_category = grade_category::fetch_course_category($this->courseid);
643        $this->assertTrue($category->set_parent($course_category->id));
644        $this->assertEquals($course_category->id, $category->parent);
645    }
646
647    protected function sub_test_grade_category_get_final() {
648        $category = new grade_category($this->grade_categories[0]);
649        $this->assertTrue(method_exists($category, 'get_final'));
650        $category->load_grade_item();
651        $this->assertEquals($category->get_final(), $category->grade_item->get_final());
652    }
653
654    protected function sub_test_grade_category_get_sortorder() {
655        $category = new grade_category($this->grade_categories[0]);
656        $this->assertTrue(method_exists($category, 'get_sortorder'));
657        $category->load_grade_item();
658        $this->assertEquals($category->get_sortorder(), $category->grade_item->get_sortorder());
659    }
660
661    protected function sub_test_grade_category_set_sortorder() {
662        $category = new grade_category($this->grade_categories[0]);
663        $this->assertTrue(method_exists($category, 'set_sortorder'));
664        $category->load_grade_item();
665        $this->assertEquals($category->set_sortorder(10), $category->grade_item->set_sortorder(10));
666    }
667
668    protected function sub_test_grade_category_move_after_sortorder() {
669        $category = new grade_category($this->grade_categories[0]);
670        $this->assertTrue(method_exists($category, 'move_after_sortorder'));
671        $category->load_grade_item();
672        $this->assertEquals($category->move_after_sortorder(10), $category->grade_item->move_after_sortorder(10));
673    }
674
675    protected function sub_test_grade_category_is_course_category() {
676        $category = grade_category::fetch_course_category($this->courseid);
677        $this->assertTrue(method_exists($category, 'is_course_category'));
678        $this->assertTrue($category->is_course_category());
679    }
680
681    protected function sub_test_grade_category_fetch_course_category() {
682        $category = new grade_category();
683        $this->assertTrue(method_exists($category, 'fetch_course_category'));
684        $category = grade_category::fetch_course_category($this->courseid);
685        $this->assertTrue(empty($category->parent));
686    }
687    /**
688     * TODO implement
689     */
690    protected function sub_test_grade_category_is_editable() {
691
692    }
693
694    protected function sub_test_grade_category_is_locked() {
695        $category = new grade_category($this->grade_categories[0]);
696        $this->assertTrue(method_exists($category, 'is_locked'));
697        $category->load_grade_item();
698        $this->assertEquals($category->is_locked(), $category->grade_item->is_locked());
699    }
700
701    protected function sub_test_grade_category_set_locked() {
702        $category = new grade_category($this->grade_categories[0]);
703        $this->assertTrue(method_exists($category, 'set_locked'));
704
705        // Will return false as cannot lock a grade that needs updating.
706        $this->assertFalse($category->set_locked(1));
707        grade_regrade_final_grades($this->courseid);
708
709        // Get the category from the db again.
710        $category = new grade_category($this->grade_categories[0]);
711        $this->assertTrue($category->set_locked(1));
712    }
713
714    protected function sub_test_grade_category_is_hidden() {
715        $category = new grade_category($this->grade_categories[0]);
716        $this->assertTrue(method_exists($category, 'is_hidden'));
717        $category->load_grade_item();
718        $this->assertEquals($category->is_hidden(), $category->grade_item->is_hidden());
719    }
720
721    protected function sub_test_grade_category_set_hidden() {
722        $category = new grade_category($this->grade_categories[0]);
723        $this->assertTrue(method_exists($category, 'set_hidden'));
724        $category->set_hidden(1);
725        $category->load_grade_item();
726        $this->assertEquals(true, $category->grade_item->is_hidden());
727    }
728
729    protected function sub_test_grade_category_can_control_visibility() {
730        $category = new grade_category($this->grade_categories[0]);
731        $this->assertTrue($category->can_control_visibility());
732    }
733
734    protected function sub_test_grade_category_insert_course_category() {
735        // Beware: adding a duplicate course category messes up the data in a way that's hard to recover from.
736        $grade_category = new grade_category();
737        $this->assertTrue(method_exists($grade_category, 'insert_course_category'));
738
739        $id = $grade_category->insert_course_category($this->courseid);
740        $this->assertNotNull($id);
741        $this->assertEquals('?', $grade_category->fullname);
742        $this->assertEquals(GRADE_AGGREGATE_WEIGHTED_MEAN2, $grade_category->aggregation);
743        $this->assertEquals("/$id/", $grade_category->path);
744        $this->assertEquals(1, $grade_category->depth);
745        $this->assertNull($grade_category->parent);
746    }
747
748    protected function generate_random_raw_grade($item, $userid) {
749        $grade = new grade_grade();
750        $grade->itemid = $item->id;
751        $grade->userid = $userid;
752        $grade->grademin = 0;
753        $grade->grademax = 1;
754        $valuetype = "grade$item->gradetype";
755        $grade->rawgrade = rand(0, 1000) / 1000;
756        $grade->insert();
757        return $grade->rawgrade;
758    }
759
760    protected function sub_test_grade_category_is_extracredit_used() {
761        $category = new grade_category();
762        // Following use aggregationcoef.
763        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2;
764        $this->assertTrue($category->is_extracredit_used());
765        $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
766        $this->assertTrue($category->is_extracredit_used());
767        $category->aggregation = GRADE_AGGREGATE_SUM;
768        $this->assertTrue($category->is_extracredit_used());
769
770        // Following don't use aggregationcoef.
771        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
772        $this->assertFalse($category->is_extracredit_used());
773        $category->aggregation = GRADE_AGGREGATE_MAX;
774        $this->assertFalse($category->is_extracredit_used());
775        $category->aggregation = GRADE_AGGREGATE_MEAN;
776        $this->assertFalse($category->is_extracredit_used());
777        $category->aggregation = GRADE_AGGREGATE_MEDIAN;
778        $this->assertFalse($category->is_extracredit_used());
779        $category->aggregation = GRADE_AGGREGATE_MIN;
780        $this->assertFalse($category->is_extracredit_used());
781        $category->aggregation = GRADE_AGGREGATE_MODE;
782        $this->assertFalse($category->is_extracredit_used());
783    }
784
785    protected function sub_test_grade_category_aggregation_uses_extracredit() {
786
787        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN2));
788        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_EXTRACREDIT_MEAN));
789        $this->assertTrue(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_SUM));
790
791        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN));
792        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MAX));
793        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEAN));
794        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEDIAN));
795        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MIN));
796        $this->assertFalse(grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MODE));
797    }
798}