PageRenderTime 69ms CodeModel.GetById 35ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/grade/tests/grade_category_test.php

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