MDL-32323 add grade tests
authorPetr Skoda <commits@skodak.org>
Sun, 8 Apr 2012 09:30:44 +0000 (11:30 +0200)
committerPetr Skoda <commits@skodak.org>
Tue, 10 Apr 2012 13:27:10 +0000 (15:27 +0200)
lib/grade/tests/fixtures/lib.php [new file with mode: 0644]
lib/grade/tests/grade_category_test.php [new file with mode: 0644]
lib/grade/tests/grade_grade_test.php [new file with mode: 0644]
lib/grade/tests/grade_item_test.php [new file with mode: 0644]
lib/grade/tests/grade_outcome_test.php [new file with mode: 0644]
lib/grade/tests/grade_scale_test.php [new file with mode: 0644]
phpunit.xml.dist

diff --git a/lib/grade/tests/fixtures/lib.php b/lib/grade/tests/fixtures/lib.php
new file mode 100644 (file)
index 0000000..7ca2520
--- /dev/null
@@ -0,0 +1,773 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/gradelib.php');
+
+
+/**
+ * Shared code for all grade related tests.
+ *
+ * Here is a brief explanation of the test data set up in these unit tests.
+ * category1 => array(category2 => array(grade_item1, grade_item2), category3 => array(grade_item3))
+ * 3 users for 3 grade_items
+ */
+class grade_base_testcase extends advanced_testcase {
+
+    protected $course;
+    protected $activities = array();
+    protected $grade_items = array();
+    protected $grade_categories = array();
+    protected $grade_grades = array();
+    protected $grade_outcomes = array();
+    protected $scale = array();
+    protected $scalemax = array();
+
+    protected $courseid;
+    protected $userid;
+
+    protected function setUp() {
+        global $CFG;
+        parent::setup();
+
+        $this->resetAfterTest(true);
+
+        $CFG->grade_droplow = -1;
+        $CFG->grade_keephigh = -1;
+        $CFG->grade_aggregation = -1;
+        $CFG->grade_aggregateonlygraded = -1;
+        $CFG->grade_aggregateoutcomes = -1;
+        $CFG->grade_aggregatesubcats = -1;
+
+        $this->course = $this->getDataGenerator()->create_course();
+        $this->courseid = $this->course->id;
+
+        $this->user[0] = $this->getDataGenerator()->create_user();
+        $this->user[1] = $this->getDataGenerator()->create_user();
+        $this->user[2] = $this->getDataGenerator()->create_user();
+        $this->user[3] = $this->getDataGenerator()->create_user();
+        $this->userid = $this->user[0]->id;
+
+        $this->load_modules();
+
+        $this->load_scales();
+        $this->load_grade_categories();
+        $this->load_grade_items();
+        $this->load_grade_grades();
+        $this->load_grade_outcomes();
+    }
+
+    public function test_void () {
+        // empty method to keep PHPUnit happy
+    }
+
+    private function load_modules() {
+        $this->activities[0] = $this->getDataGenerator()->create_module('assignment', array('course'=>$this->course->id));
+        $this->course_module[0] = get_coursemodule_from_instance('assignment', $this->activities[0]->id);
+
+        $this->activities[1] = $this->getDataGenerator()->create_module('assignment', array('course'=>$this->course->id));
+        $this->course_module[1] = get_coursemodule_from_instance('assignment', $this->activities[1]->id);
+
+        $this->activities[2] = $this->getDataGenerator()->create_module('forum', array('course'=>$this->course->id));
+        $this->course_module[2] = get_coursemodule_from_instance('forum', $this->activities[2]->id);
+
+        $this->activities[3] = $this->getDataGenerator()->create_module('page', array('course'=>$this->course->id));
+        $this->course_module[3] = get_coursemodule_from_instance('page', $this->activities[3]->id);
+
+        $this->activities[4] = $this->getDataGenerator()->create_module('forum', array('course'=>$this->course->id));
+        $this->course_module[4] = get_coursemodule_from_instance('forum', $this->activities[4]->id);
+
+        $this->activities[5] = $this->getDataGenerator()->create_module('forum', array('course'=>$this->course->id));
+        $this->course_module[5] = get_coursemodule_from_instance('forum', $this->activities[5]->id);
+
+        $this->activities[6] = $this->getDataGenerator()->create_module('forum', array('course'=>$this->course->id));
+        $this->course_module[6] = get_coursemodule_from_instance('forum', $this->activities[6]->id);
+    }
+
+    private function load_scales() {
+        $scale = new stdClass();
+        $scale->name        = 'unittestscale1';
+        $scale->courseid    = $this->course->id;
+        $scale->userid      = $this->user[0]->id;
+        $scale->scale       = 'Way off topic, Not very helpful, Fairly neutral, Fairly helpful, Supportive, Some good information, Perfect answer!';
+        $scale->description = 'This scale defines some of qualities that make posts helpful within the Moodle help forums.\n Your feedback will help others see how their posts are being received.';
+
+        $this->scale[0] = $this->getDataGenerator()->create_scale($scale);
+        $this->scalemax[0] = substr_count($scale->scale, ',');
+
+        $scale = new stdClass();
+        $scale->name        = 'unittestscale2';
+        $scale->courseid    = $this->course->id;
+        $scale->userid      = $this->user[0]->id;
+        $scale->scale       = 'Distinction, Very Good, Good, Pass, Fail';
+        $scale->description = 'This scale is used to mark standard assignments.';
+
+        $this->scale[1] = $this->getDataGenerator()->create_scale($scale);
+        $this->scalemax[1] = substr_count($scale->scale, ',');
+
+        $scale = new stdClass();
+        $scale->name        = 'unittestscale3';
+        $scale->courseid    = $this->course->id;
+        $scale->userid      = $this->user[0]->id;
+        $scale->scale       = 'Loner, Contentious, Disinterested, Participative, Follower, Leader';
+        $scale->description = 'Describes the level of teamwork of a student.';
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
+
+        $this->scale[2] = $this->getDataGenerator()->create_scale($scale);
+        $this->scalemax[2] = substr_count($scale->scale, ',');
+
+        $scale = new stdClass();
+        $scale->name        = 'unittestscale4';
+        $scale->courseid    = $this->course->id;
+        $scale->userid      = $this->user[0]->id;
+        $scale->scale       = 'Does not understand theory, Understands theory but fails practice, Manages through, Excels';
+        $scale->description = 'Level of expertise at a technical task, with a theoretical framework.';
+        $temp  = explode(',', $scale->scale);
+        $scale->max         = count($temp) -1;
+
+        $this->scale[3] = $this->getDataGenerator()->create_scale($scale);
+        $this->scalemax[3] = substr_count($scale->scale, ',');
+
+        $scale = new stdClass();
+        $scale->name        = 'unittestscale5';
+        $scale->courseid    = $this->course->id;
+        $scale->userid      = $this->user[0]->id;
+        $scale->scale       = 'Insufficient, Acceptable, Excellent.';
+        $scale->description = 'Description of skills.';
+
+        $this->scale[4] = $this->getDataGenerator()->create_scale($scale);
+        $this->scalemax[4] = substr_count($scale->scale, ',');
+    }
+
+    /**
+     * Load grade_category data into the database, and adds the corresponding objects to this class' variable.
+     * category structure:
+                              course category
+                                    |
+                           +--------+-------------+
+                           |                      |
+             unittestcategory1               level1category
+                  |
+         +--------+-------------+
+         |                      |
+        unittestcategory2  unittestcategory3
+     */
+    private function load_grade_categories() {
+        global $DB;
+
+        $course_category = grade_category::fetch_course_category($this->course->id);
+
+        $grade_category = new stdClass();
+
+        $grade_category->fullname    = 'unittestcategory1';
+        $grade_category->courseid    = $this->course->id;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregateonlygraded = 1;
+        $grade_category->keephigh    = 0;
+        $grade_category->droplow     = 0;
+        $grade_category->parent      = $course_category->id;
+        $grade_category->timecreated = time();
+        $grade_category->timemodified = time();
+        $grade_category->depth = 2;
+
+        $grade_category->id = $DB->insert_record('grade_categories', $grade_category);
+        $grade_category->path = '/'.$course_category->id.'/'.$grade_category->id.'/';
+        $DB->update_record('grade_categories', $grade_category);
+        $this->grade_categories[0] = $grade_category;
+
+        $grade_category = new stdClass();
+
+        $grade_category->fullname    = 'unittestcategory2';
+        $grade_category->courseid    = $this->course->id;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregateonlygraded = 1;
+        $grade_category->keephigh    = 0;
+        $grade_category->droplow     = 0;
+        $grade_category->parent      = $this->grade_categories[0]->id;
+        $grade_category->timecreated = time();
+        $grade_category->timemodified = time();
+        $grade_category->depth = 3;
+
+        $grade_category->id = $DB->insert_record('grade_categories', $grade_category);
+        $grade_category->path = $this->grade_categories[0]->path.$grade_category->id.'/';
+        $DB->update_record('grade_categories', $grade_category);
+        $this->grade_categories[1] = $grade_category;
+
+        $grade_category = new stdClass();
+
+        $grade_category->fullname    = 'unittestcategory3';
+        $grade_category->courseid    = $this->course->id;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregateonlygraded = 1;
+        $grade_category->keephigh    = 0;
+        $grade_category->droplow     = 0;
+        $grade_category->parent      = $this->grade_categories[0]->id;
+        $grade_category->timecreated = time();
+        $grade_category->timemodified = time();
+        $grade_category->depth = 3;
+
+        $grade_category->id = $DB->insert_record('grade_categories', $grade_category);
+        $grade_category->path = $this->grade_categories[0]->path.$grade_category->id.'/';
+        $DB->update_record('grade_categories', $grade_category);
+        $this->grade_categories[2] = $grade_category;
+
+        // A category with no parent, but grade_items as children
+
+        $grade_category = new stdClass();
+
+        $grade_category->fullname    = 'level1category';
+        $grade_category->courseid    = $this->course->id;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregateonlygraded = 1;
+        $grade_category->keephigh    = 0;
+        $grade_category->droplow     = 0;
+        $grade_category->parent      = $course_category->id;
+        $grade_category->timecreated = time();
+        $grade_category->timemodified = time();
+        $grade_category->depth = 2;
+
+        $grade_category->id = $DB->insert_record('grade_categories', $grade_category);
+        $grade_category->path = '/'.$course_category->id.'/'.$grade_category->id.'/';
+        $DB->update_record('grade_categories', $grade_category);
+        $this->grade_categories[3] = $grade_category;
+    }
+
+    /**
+     * Load grade_item data into the database, and adds the corresponding objects to this class' variable.
+     */
+    protected function load_grade_items() {
+        global $DB;
+
+        $course_category = grade_category::fetch_course_category($this->course->id);
+
+        // id = 0
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[1]->id;
+        $grade_item->itemname = 'unittestgradeitem1';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = $this->course_module[0]->modname;
+        $grade_item->iteminstance = $this->course_module[0]->instance;
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 30;
+        $grade_item->grademax = 110;
+        $grade_item->itemnumber = 1;
+        $grade_item->idnumber = 'item id 0';
+        $grade_item->iteminfo = 'Grade item 0 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 3;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[0] = $grade_item;
+
+        // id = 1
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[1]->id;
+        $grade_item->itemname = 'unittestgradeitem2';
+        $grade_item->itemtype = 'import';
+        $grade_item->itemmodule = $this->course_module[1]->modname;
+        $grade_item->iteminstance = $this->course_module[1]->instance;
+        $grade_item->calculation = '= ##gi'.$this->grade_items[0]->id.'## + 30 + [[item id 0]] - [[item id 0]]';
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->itemnumber = null;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Grade item 1 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 4;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[1] = $grade_item;
+
+        // id = 2
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[2]->id;
+        $grade_item->itemname = 'unittestgradeitem3';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = $this->course_module[2]->modname;
+        $grade_item->iteminstance = $this->course_module[2]->instance;
+        $grade_item->gradetype = GRADE_TYPE_SCALE;
+        $grade_item->scaleid = $this->scale[0]->id;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = $this->scalemax[0];
+        $grade_item->iteminfo = 'Grade item 2 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 6;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[2] = $grade_item;
+
+        // Load grade_items associated with the 3 categories
+        // id = 3
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->iteminstance = $this->grade_categories[0]->id;
+        $grade_item->itemname = 'unittestgradeitemcategory1';
+        $grade_item->needsupdate = 0;
+        $grade_item->itemtype = 'category';
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Grade item 3 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 1;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[3] = $grade_item;
+
+        // id = 4
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->iteminstance = $this->grade_categories[1]->id;
+        $grade_item->itemname = 'unittestgradeitemcategory2';
+        $grade_item->itemtype = 'category';
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->needsupdate = 0;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Grade item 4 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 2;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[4] = $grade_item;
+
+        // id = 5
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->iteminstance = $this->grade_categories[2]->id;
+        $grade_item->itemname = 'unittestgradeitemcategory3';
+        $grade_item->itemtype = 'category';
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->needsupdate = true;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Grade item 5 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 5;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[5] = $grade_item;
+
+        // Orphan grade_item
+        // id = 6
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $course_category->id;
+        $grade_item->itemname = 'unittestorphangradeitem1';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = $this->course_module[4]->modname;
+        $grade_item->iteminstance = $this->course_module[4]->instance;
+        $grade_item->itemnumber = 0;
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 10;
+        $grade_item->grademax = 120;
+        $grade_item->locked = time();
+        $grade_item->iteminfo = 'Orphan Grade 6 item used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 7;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[6] = $grade_item;
+
+        // 2 grade items under level1category
+        // id = 7
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[3]->id;
+        $grade_item->itemname = 'singleparentitem1';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = $this->course_module[5]->modname;
+        $grade_item->iteminstance = $this->course_module[5]->instance;
+        $grade_item->gradetype = GRADE_TYPE_SCALE;
+        $grade_item->scaleid = $this->scale[0]->id;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = $this->scalemax[0];
+        $grade_item->iteminfo = 'Grade item 7 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 9;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[7] = $grade_item;
+
+        // id = 8
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $this->grade_categories[3]->id;
+        $grade_item->itemname = 'singleparentitem2';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = $this->course_module[6]->modname;
+        $grade_item->iteminstance = $this->course_module[6]->instance;
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Grade item 8 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 10;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[8] = $grade_item;
+
+        // Grade_item for level1category
+        // id = 9
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->itemname = 'grade_item for level1 category';
+        $grade_item->itemtype = 'category';
+        $grade_item->iteminstance = $this->grade_categories[3]->id;
+        $grade_item->needsupdate = true;
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Orphan Grade item 9 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+        $grade_item->sortorder = 8;
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[9] = $grade_item;
+
+        // Manual grade_item
+        // id = 10
+        $grade_item = new stdClass();
+
+        $grade_item->courseid = $this->course->id;
+        $grade_item->categoryid = $course_category->id;
+        $grade_item->itemname = 'manual grade_item';
+        $grade_item->itemtype = 'manual';
+        $grade_item->itemnumber = 0;
+        $grade_item->needsupdate = false;
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->iteminfo = 'Manual grade item 10 used for unit testing';
+        $grade_item->timecreated = time();
+        $grade_item->timemodified = time();
+
+        $grade_item->id = $DB->insert_record('grade_items', $grade_item);
+        $this->grade_items[10] = $grade_item;
+    }
+
+    /**
+     * Load grade_grades data into the database, and adds the corresponding objects to this class' variable.
+     */
+    private function load_grade_grades() {
+        global $DB;
+
+        //this method is called once for each test method. Avoid adding things to $this->grade_grades multiple times
+        $this->grade_grades = array();
+
+        // Grades for grade_item 1
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[0]->id;
+        $grade->userid = $this->user[1]->id;
+        $grade->rawgrade = 15; // too small
+        $grade->finalgrade = 30;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '1 of 17 grade_grades';
+        $grade->informationformat = FORMAT_PLAIN;
+        $grade->feedback = 'Good, but not good enough..';
+        $grade->feedbackformat = FORMAT_PLAIN;
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[0] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[0]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->rawgrade = 40;
+        $grade->finalgrade = 40;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '2 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[1] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[0]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->rawgrade = 170; // too big
+        $grade->finalgrade = 110;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '3 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[2] = $grade;
+
+
+        // No raw grades for grade_item 2 - it is calculated
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[1]->id;
+        $grade->userid = $this->user[1]->id;
+        $grade->finalgrade = 60;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '4 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[3] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[1]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->finalgrade = 70;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '5 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[4] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[1]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->finalgrade = 100;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '6 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[5] = $grade;
+
+
+        // Grades for grade_item 3
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[2]->id;
+        $grade->userid = $this->user[1]->id;
+        $grade->rawgrade = 2;
+        $grade->finalgrade = 6;
+        $grade->scaleid = $this->scale[3]->id;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '7 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[6] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[2]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->rawgrade = 3;
+        $grade->finalgrade = 2;
+        $grade->scaleid = $this->scale[3]->id;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '8 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[2]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->rawgrade = 1;
+        $grade->finalgrade = 3;
+        $grade->scaleid = $this->scale[3]->id;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '9 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        // Grades for grade_item 7
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[6]->id;
+        $grade->userid = $this->user[1]->id;
+        $grade->rawgrade = 97;
+        $grade->finalgrade = 69;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '10 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[6]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->rawgrade = 49;
+        $grade->finalgrade = 87;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '11 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[6]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->rawgrade = 67;
+        $grade->finalgrade = 94;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '12 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        // Grades for grade_item 8
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[7]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->rawgrade = 3;
+        $grade->finalgrade = 3;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '13 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[7]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->rawgrade = 6;
+        $grade->finalgrade = 6;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '14 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        // Grades for grade_item 9
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[8]->id;
+        $grade->userid = $this->user[1]->id;
+        $grade->rawgrade = 20;
+        $grade->finalgrade = 20;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '15 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[8]->id;
+        $grade->userid = $this->user[2]->id;
+        $grade->rawgrade = 50;
+        $grade->finalgrade = 50;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '16 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+
+        $grade = new stdClass();
+        $grade->itemid = $this->grade_items[8]->id;
+        $grade->userid = $this->user[3]->id;
+        $grade->rawgrade = 100;
+        $grade->finalgrade = 100;
+        $grade->timecreated = time();
+        $grade->timemodified = time();
+        $grade->information = '17 of 17 grade_grades';
+
+        $grade->id = $DB->insert_record('grade_grades', $grade);
+        $this->grade_grades[] = $grade;
+    }
+
+    /**
+     * Load grade_outcome data into the database, and adds the corresponding objects to this class' variable.
+     */
+    private function load_grade_outcomes() {
+        global $DB;
+
+        //this method is called once for each test method. Avoid adding things to $this->grade_outcomes multiple times
+        $this->grade_outcomes = array();
+
+        // Calculation for grade_item 1
+        $grade_outcome = new stdClass();
+        $grade_outcome->fullname = 'Team work';
+        $grade_outcome->shortname = 'Team work';
+        $grade_outcome->fullname = 'Team work outcome';
+        $grade_outcome->timecreated = time();
+        $grade_outcome->timemodified = time();
+        $grade_outcome->scaleid = $this->scale[2]->id;
+
+        $grade_outcome->id = $DB->insert_record('grade_outcomes', $grade_outcome);
+        $this->grade_outcomes[] = $grade_outcome;
+
+        // Calculation for grade_item 2
+        $grade_outcome = new stdClass();
+        $grade_outcome->fullname = 'Complete circuit board';
+        $grade_outcome->shortname = 'Complete circuit board';
+        $grade_outcome->fullname = 'Complete circuit board';
+        $grade_outcome->timecreated = time();
+        $grade_outcome->timemodified = time();
+        $grade_outcome->scaleid = $this->scale[3]->id;
+
+        $grade_outcome->id = $DB->insert_record('grade_outcomes', $grade_outcome);
+        $this->grade_outcomes[] = $grade_outcome;
+
+        // Calculation for grade_item 3
+        $grade_outcome = new stdClass();
+        $grade_outcome->fullname = 'Debug Java program';
+        $grade_outcome->shortname = 'Debug Java program';
+        $grade_outcome->fullname = 'Debug Java program';
+        $grade_outcome->timecreated = time();
+        $grade_outcome->timemodified = time();
+        $grade_outcome->scaleid = $this->scale[4]->id;
+
+        $grade_outcome->id = $DB->insert_record('grade_outcomes', $grade_outcome);
+        $this->grade_outcomes[] = $grade_outcome;
+    }
+}
+
+
diff --git a/lib/grade/tests/grade_category_test.php b/lib/grade/tests/grade_category_test.php
new file mode 100644 (file)
index 0000000..102ef86
--- /dev/null
@@ -0,0 +1,646 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/lib.php');
+
+
+class grade_category_testcase extends grade_base_testcase {
+
+    public function test_grade_category() {
+        $this->sub_test_grade_category_construct();
+        $this->sub_test_grade_category_build_path();
+        $this->sub_test_grade_category_fetch();
+        $this->sub_test_grade_category_fetch_all();
+        $this->sub_test_grade_category_update();
+        $this->sub_test_grade_category_delete();
+        $this->sub_test_grade_category_insert();
+        $this->sub_test_grade_category_qualifies_for_regrading();
+        $this->sub_test_grade_category_force_regrading();
+        $this->sub_test_grade_category_aggregate_grades();
+        $this->sub_test_grade_category_apply_limit_rules();
+        $this->sub_test_grade_category_is_aggregationcoef_used();
+        $this->sub_test_grade_category_fetch_course_tree();
+        $this->sub_test_grade_category_get_children();
+        $this->sub_test_grade_category_load_grade_item();
+        $this->sub_test_grade_category_get_grade_item();
+        $this->sub_test_grade_category_load_parent_category();
+        $this->sub_test_grade_category_get_parent_category();
+        $this->sub_test_grade_category_get_name();
+        $this->sub_test_grade_category_set_parent();
+        $this->sub_test_grade_category_get_final();
+        $this->sub_test_grade_category_get_sortorder();
+        $this->sub_test_grade_category_set_sortorder();
+        $this->sub_test_grade_category_is_editable();
+        $this->sub_test_grade_category_move_after_sortorder();
+        $this->sub_test_grade_category_is_course_category();
+        $this->sub_test_grade_category_fetch_course_category();
+        $this->sub_test_grade_category_is_locked();
+        $this->sub_test_grade_category_set_locked();
+        $this->sub_test_grade_category_is_hidden();
+        $this->sub_test_grade_category_set_hidden();
+
+        //this won't work until MDL-11837 is complete
+        //$this->sub_test_grade_category_generate_grades();
+
+        //do this last as adding a second course category messes up the data
+        $this->sub_test_grade_category_insert_course_category();
+    }
+
+    //adds 3 new grade categories at various depths
+    protected function sub_test_grade_category_construct() {
+        $course_category = grade_category::fetch_course_category($this->courseid);
+
+        $params = new stdClass();
+
+        $params->courseid = $this->courseid;
+        $params->fullname = 'unittestcategory4';
+
+        $grade_category = new grade_category($params, false);
+        $grade_category->insert();
+        $this->grade_categories[] = $grade_category;
+
+        $this->assertEquals($params->courseid, $grade_category->courseid);
+        $this->assertEquals($params->fullname, $grade_category->fullname);
+        $this->assertEquals(2, $grade_category->depth);
+        $this->assertEquals("/$course_category->id/$grade_category->id/", $grade_category->path);
+        $parentpath = $grade_category->path;
+
+        // Test a child category
+        $params->parent = $grade_category->id;
+        $params->fullname = 'unittestcategory5';
+        $grade_category = new grade_category($params, false);
+        $grade_category->insert();
+        $this->grade_categories[] = $grade_category;
+
+        $this->assertEquals(3, $grade_category->depth);
+        $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
+        $parentpath = $grade_category->path;
+
+        // Test a third depth category
+        $params->parent = $grade_category->id;
+        $params->fullname = 'unittestcategory6';
+        $grade_category = new grade_category($params, false);
+        $grade_category->insert();
+        $this->grade_categories[50] = $grade_category;//going to delete this one later hence the special index
+
+        $this->assertEquals(4, $grade_category->depth);
+        $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path);
+    }
+
+    protected function sub_test_grade_category_build_path() {
+        $grade_category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($grade_category, 'build_path'));
+        $path = grade_category::build_path($grade_category);
+        $this->assertEquals($grade_category->path, $path);
+    }
+
+    protected function sub_test_grade_category_fetch() {
+        $grade_category = new grade_category();
+        $this->assertTrue(method_exists($grade_category, 'fetch'));
+
+        $grade_category = grade_category::fetch(array('id'=>$this->grade_categories[0]->id));
+        $this->assertEquals($this->grade_categories[0]->id, $grade_category->id);
+        $this->assertEquals($this->grade_categories[0]->fullname, $grade_category->fullname);
+    }
+
+    protected function sub_test_grade_category_fetch_all() {
+        $grade_category = new grade_category();
+        $this->assertTrue(method_exists($grade_category, 'fetch_all'));
+
+        $grade_categories = grade_category::fetch_all(array('courseid'=>$this->courseid));
+        $this->assertEquals(count($this->grade_categories), count($grade_categories)-1);
+    }
+
+    protected function sub_test_grade_category_update() {
+        global $DB;
+        $grade_category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($grade_category, 'update'));
+
+        $grade_category->fullname = 'Updated info for this unittest grade_category';
+        $grade_category->path = null; // path must be recalculated if missing
+        $grade_category->depth = null;
+        $grade_category->aggregation = GRADE_AGGREGATE_MAX; // should force regrading
+
+        $grade_item = $grade_category->get_grade_item();
+        $this->assertEquals(0, $grade_item->needsupdate);
+
+        $this->assertTrue($grade_category->update());
+
+        $fullname = $DB->get_field('grade_categories', 'fullname', array('id' => $this->grade_categories[0]->id));
+        $this->assertEquals($grade_category->fullname, $fullname);
+
+        $path = $DB->get_field('grade_categories', 'path', array('id' => $this->grade_categories[0]->id));
+        $this->assertEquals($grade_category->path, $path);
+
+        $depth = $DB->get_field('grade_categories', 'depth', array('id' => $this->grade_categories[0]->id));
+        $this->assertEquals($grade_category->depth, $depth);
+
+        $grade_item = $grade_category->get_grade_item();
+        $this->assertEquals(1, $grade_item->needsupdate);
+    }
+
+    protected function sub_test_grade_category_delete() {
+        global $DB;
+
+        $grade_category = new grade_category($this->grade_categories[50]);
+        $this->assertTrue(method_exists($grade_category, 'delete'));
+
+        $this->assertTrue($grade_category->delete());
+        $this->assertFalse($DB->get_record('grade_categories', array('id' => $grade_category->id)));
+    }
+
+    protected function sub_test_grade_category_insert() {
+        $course_category = grade_category::fetch_course_category($this->courseid);
+
+        $grade_category = new grade_category();
+        $this->assertTrue(method_exists($grade_category, 'insert'));
+
+        $grade_category->fullname    = 'unittestcategory4';
+        $grade_category->courseid    = $this->courseid;
+        $grade_category->aggregation = GRADE_AGGREGATE_MEAN;
+        $grade_category->aggregateonlygraded = 1;
+        $grade_category->keephigh    = 100;
+        $grade_category->droplow     = 10;
+        $grade_category->hidden      = 0;
+        $grade_category->parent      = $this->grade_categories[1]->id; //sub_test_grade_category_delete() removed the category at 0
+
+        $grade_category->insert();
+
+        $this->assertEquals('/'.$course_category->id.'/'.$this->grade_categories[1]->parent.'/'.$this->grade_categories[1]->id.'/'.$grade_category->id.'/', $grade_category->path);
+        $this->assertEquals(4, $grade_category->depth);
+
+        $last_grade_category = end($this->grade_categories);
+
+        $this->assertFalse(empty($grade_category->grade_item));
+        $this->assertEquals($grade_category->id, $grade_category->grade_item->iteminstance);
+        $this->assertEquals('category', $grade_category->grade_item->itemtype);
+
+        $this->assertEquals($grade_category->id, $last_grade_category->id + 1);
+        $this->assertFalse(empty($grade_category->timecreated));
+        $this->assertFalse(empty($grade_category->timemodified));
+    }
+
+    protected function sub_test_grade_category_qualifies_for_regrading() {
+        $grade_category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($grade_category, 'qualifies_for_regrading'));
+        $this->assertFalse($grade_category->qualifies_for_regrading());
+
+        $grade_category->aggregation = GRADE_AGGREGATE_MAX;
+        $this->assertTrue($grade_category->qualifies_for_regrading());
+
+        $grade_category = new grade_category($this->grade_categories[1]);
+        $grade_category->droplow = 99;
+        $this->assertTrue($grade_category->qualifies_for_regrading());
+
+        $grade_category = new grade_category($this->grade_categories[1]);
+        $grade_category->keephigh = 99;
+        $this->assertTrue($grade_category->qualifies_for_regrading());
+    }
+
+    protected function sub_test_grade_category_force_regrading() {
+        $grade_category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($grade_category, 'force_regrading'));
+
+        $grade_category->load_grade_item();
+        $this->assertEquals(0, $grade_category->grade_item->needsupdate);
+
+        $grade_category->force_regrading();
+
+        $grade_category->grade_item = null;
+        $grade_category->load_grade_item();
+
+        $this->assertEquals(1, $grade_category->grade_item->needsupdate);
+    }
+
+    /**
+     * Tests the calculation of grades using the various aggregation methods with and without hidden grades
+     * This will not work entirely until MDL-11837 is done
+     * @global type $DB
+     */
+    protected function sub_test_grade_category_generate_grades() {
+        global $DB;
+
+        //inserting some special grade items to make testing the final grade calculation easier
+        $params->courseid = $this->courseid;
+        $params->fullname = 'unittestgradecalccategory';
+        $params->aggregation = GRADE_AGGREGATE_MEAN;
+        $params->aggregateonlygraded = 0;
+        $grade_category = new grade_category($params, false);
+        $grade_category->insert();
+
+        $this->assertTrue(method_exists($grade_category, 'generate_grades'));
+
+        $grade_category->load_grade_item();
+        $cgi = $grade_category->get_grade_item();
+        $cgi->grademin = 0;
+        $cgi->grademax = 20;//3 grade items out of 10 but category is out of 20 to force scaling to occur
+        $cgi->update();
+
+        //3 grade items each with a maximum grade of 10
+        $grade_items = array();
+        for ($i=0; $i<3; $i++) {
+            $grade_items[$i] = new grade_item();
+            $grade_items[$i]->courseid = $this->courseid;
+            $grade_items[$i]->categoryid = $grade_category->id;
+            $grade_items[$i]->itemname = 'manual grade_item '.$i;
+            $grade_items[$i]->itemtype = 'manual';
+            $grade_items[$i]->itemnumber = 0;
+            $grade_items[$i]->needsupdate = false;
+            $grade_items[$i]->gradetype = GRADE_TYPE_VALUE;
+            $grade_items[$i]->grademin = 0;
+            $grade_items[$i]->grademax = 10;
+            $grade_items[$i]->iteminfo = 'Manual grade item used for unit testing';
+            $grade_items[$i]->timecreated = time();
+            $grade_items[$i]->timemodified = time();
+
+            //used as the weight by weighted mean and as extra credit by mean with extra credit
+            //Will be 0, 1 and 2
+            $grade_items[$i]->aggregationcoef = $i;
+
+            $grade_items[$i]->insert();
+        }
+
+        //a grade for each grade item
+        $grade_grades = array();
+        for ($i=0; $i<3; $i++) {
+            $grade_grades[$i] = new grade_grade();
+            $grade_grades[$i]->itemid = $grade_items[$i]->id;
+            $grade_grades[$i]->userid = $this->userid;
+            $grade_grades[$i]->rawgrade = ($i+1)*2;//produce grade grades of 2, 4 and 6
+            $grade_grades[$i]->finalgrade = ($i+1)*2;
+            $grade_grades[$i]->timecreated = time();
+            $grade_grades[$i]->timemodified = time();
+            $grade_grades[$i]->information = '1 of 2 grade_grades';
+            $grade_grades[$i]->informationformat = FORMAT_PLAIN;
+            $grade_grades[$i]->feedback = 'Good, but not good enough..';
+            $grade_grades[$i]->feedbackformat = FORMAT_PLAIN;
+
+            $grade_grades[$i]->insert();
+        }
+
+        //3 grade items with 1 grade_grade each.
+        //grade grades have the values 2, 4 and 6
+
+        //First correct answer is the aggregate with all 3 grades
+        //Second correct answer is with the first grade (value 2) hidden
+
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEDIAN, 'GRADE_AGGREGATE_MEDIAN', 8, 8);
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MAX, 'GRADE_AGGREGATE_MAX', 12, 12);
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MODE, 'GRADE_AGGREGATE_MODE', 12, 12);
+
+        //weighted mean. note grade totals are rounded to an int to prevent rounding discrepancies. correct final grade isnt actually exactly 10
+        //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.
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN, 'GRADE_AGGREGATE_WEIGHTED_MEAN', 10, 10);
+
+        //simple weighted mean
+        //3 items with grades 2, 4 and 6 equally weighted and all out of 10. then doubled to be out of 20.
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN2, 'GRADE_AGGREGATE_WEIGHTED_MEAN2', 8, 10);
+
+        //mean of grades with extra credit
+        //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.
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_EXTRACREDIT_MEAN, 'GRADE_AGGREGATE_EXTRACREDIT_MEAN', 10, 13);
+
+        //aggregation tests the are affected by a hidden grade currently dont work as we dont store the altered grade in the database
+        //instead an in memory recalculation is done. This should be remedied by MDL-11837
+
+        //fails with 1 grade hidden. still reports 8 as being correct
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEAN, 'GRADE_AGGREGATE_MEAN', 8, 10);
+
+        //fails with 1 grade hidden. still reports 4 as being correct
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MIN, 'GRADE_AGGREGATE_MIN', 4, 8);
+
+        //fails with 1 grade hidden. still reports 12 as being correct
+        $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_SUM, 'GRADE_AGGREGATE_SUM', 12, 10);
+    }
+
+    /**
+     * Test grade category aggregation using the supplied grade objects and aggregation method
+     * @param grade_category $grade_category the category to be tested
+     * @param array $grade_items array of instance of grade_item
+     * @param array $grade_grades array of instances of grade_grade
+     * @param int $aggmethod the aggregation method to apply ie GRADE_AGGREGATE_MEAN
+     * @param string $aggmethodname the name of the aggregation method to apply. Used to display any test failure messages
+     * @param int $correct1 the correct final grade for the category with NO items hidden
+     * @param int $correct2 the correct final grade for the category with the grade at $grade_grades[0] hidden
+     * @return void
+    */
+    protected function helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, $aggmethod, $aggmethodname, $correct1, $correct2) {
+        global $DB;
+
+        $grade_category->aggregation = $aggmethod;
+        $grade_category->update();
+
+        //check grade_item isnt hidden from a previous test
+        $grade_items[0]->set_hidden(0, true);
+        $this->helper_test_grade_aggregation_result($grade_category, $correct1, 'Testing aggregation method('.$aggmethodname.') with no items hidden %s');
+
+        //hide the grade item with grade of 2
+        $grade_items[0]->set_hidden(1, true);
+        $this->helper_test_grade_aggregation_result($grade_category, $correct2, 'Testing aggregation method('.$aggmethodname.') with 1 item hidden %s');
+    }
+
+    /**
+     * Verify the value of the category grade item for $this->userid
+     * @param grade_category $grade_category the category to be tested
+     * @param int $correctgrade the expected grade
+     * @param string msg The message that should be displayed if the correct grade is not found
+     * @return void
+     */
+    protected function helper_test_grade_aggregation_result($grade_category, $correctgrade, $msg) {
+        global $DB;
+
+        $category_grade_item = $grade_category->get_grade_item();
+
+        //this creates all the grade_grades we need
+        grade_regrade_final_grades($this->courseid);
+
+        $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
+        $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
+        $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
+
+        /*
+         * TODO this doesnt work as the grade_grades created by $grade_category->generate_grades(); dont
+         * observe the category's max grade
+        //delete the grade_grades for the category itself and check they get recreated correctly
+        $DB->delete_records('grade_grades', array('itemid'=>$category_grade_item->id));
+        $grade_category->generate_grades();
+
+        $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid));
+        $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax);
+        $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg);
+         *
+         */
+    }
+
+    protected function sub_test_grade_category_aggregate_grades() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'aggregate_grades'));
+        // tested more fully via test_grade_category_generate_grades()
+    }
+
+    protected function sub_test_grade_category_apply_limit_rules() {
+        $items[$this->grade_items[0]->id] = new grade_item($this->grade_items[0], false);
+        $items[$this->grade_items[1]->id] = new grade_item($this->grade_items[1], false);
+        $items[$this->grade_items[2]->id] = new grade_item($this->grade_items[2], false);
+        $items[$this->grade_items[4]->id] = new grade_item($this->grade_items[4], false);
+
+        $category = new grade_category();
+        $category->droplow = 2;
+        $grades = array($this->grade_items[0]->id=>5.374,
+                        $this->grade_items[1]->id=>9.4743,
+                        $this->grade_items[2]->id=>2.5474,
+                        $this->grade_items[4]->id=>7.3754);
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 2);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
+        $this->assertEquals($grades[$this->grade_items[4]->id], 7.3754);
+
+        $category = new grade_category();
+        $category->keephigh = 1;
+        $category->droplow = 0;
+        $grades = array($this->grade_items[0]->id=>5.374,
+                        $this->grade_items[1]->id=>9.4743,
+                        $this->grade_items[2]->id=>2.5474,
+                        $this->grade_items[4]->id=>7.3754);
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 1);
+        $grade = reset($grades);
+        $this->assertEquals(9.4743, $grade);
+
+        $category = new grade_category();
+        $category->droplow     = 2;
+        $category->aggregation = GRADE_AGGREGATE_SUM;
+        $items[$this->grade_items[2]->id]->aggregationcoef = 1;
+        $grades = array($this->grade_items[0]->id=>5.374,
+                        $this->grade_items[1]->id=>9.4743,
+                        $this->grade_items[2]->id=>2.5474,
+                        $this->grade_items[4]->id=>7.3754);
+
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 2);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
+
+        $category = new grade_category();
+        $category->keephigh = 1;
+        $category->droplow = 0;
+        $category->aggregation = GRADE_AGGREGATE_SUM;
+        $items[$this->grade_items[2]->id]->aggregationcoef = 1;
+        $grades = array($this->grade_items[0]->id=>5.374,
+                        $this->grade_items[1]->id=>9.4743,
+                        $this->grade_items[2]->id=>2.5474,
+                        $this->grade_items[4]->id=>7.3754);
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 2);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
+    }
+
+    /**
+     * TODO implement
+     */
+    protected function sub_test_grade_category_is_aggregationcoef_used() {
+
+    }
+
+    protected function sub_test_grade_category_fetch_course_tree() {
+        $category = new grade_category();
+        $this->assertTrue(method_exists($category, 'fetch_course_tree'));
+        //TODO: add some tests
+    }
+
+    protected function sub_test_grade_category_get_children() {
+        $course_category = grade_category::fetch_course_category($this->courseid);
+
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'get_children'));
+
+        $children_array = $category->get_children(0);
+
+        $this->assertTrue(is_array($children_array));
+        $this->assertFalse(empty($children_array[2]));
+        $this->assertFalse(empty($children_array[2]['object']));
+        $this->assertFalse(empty($children_array[2]['children']));
+        $this->assertEquals($this->grade_categories[1]->id, $children_array[2]['object']->id);
+        $this->assertEquals($this->grade_categories[2]->id, $children_array[5]['object']->id);
+        $this->assertEquals($this->grade_items[0]->id, $children_array[2]['children'][3]['object']->id);
+        $this->assertEquals($this->grade_items[1]->id, $children_array[2]['children'][4]['object']->id);
+        $this->assertEquals($this->grade_items[2]->id, $children_array[5]['children'][6]['object']->id);
+    }
+
+    protected function sub_test_grade_category_load_grade_item() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'load_grade_item'));
+        $this->assertEquals(null, $category->grade_item);
+        $category->load_grade_item();
+        $this->assertEquals($this->grade_items[3]->id, $category->grade_item->id);
+    }
+
+    protected function sub_test_grade_category_get_grade_item() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'get_grade_item'));
+        $grade_item = $category->get_grade_item();
+        $this->assertEquals($this->grade_items[3]->id, $grade_item->id);
+    }
+
+    protected function sub_test_grade_category_load_parent_category() {
+        $category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($category, 'load_parent_category'));
+        $this->assertEquals(null, $category->parent_category);
+        $category->load_parent_category();
+        $this->assertEquals($this->grade_categories[0]->id, $category->parent_category->id);
+    }
+
+    protected function sub_test_grade_category_get_parent_category() {
+        $category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($category, 'get_parent_category'));
+        $parent_category = $category->get_parent_category();
+        $this->assertEquals($this->grade_categories[0]->id, $parent_category->id);
+    }
+
+    protected function sub_test_grade_category_get_name() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'get_name'));
+        $this->assertEquals($this->grade_categories[0]->fullname, $category->get_name());
+    }
+
+    protected function sub_test_grade_category_set_parent() {
+        $category = new grade_category($this->grade_categories[1]);
+        $this->assertTrue(method_exists($category, 'set_parent'));
+        // TODO: implement detailed tests
+
+        $course_category = grade_category::fetch_course_category($this->courseid);
+        $this->assertTrue($category->set_parent($course_category->id));
+        $this->assertEquals($course_category->id, $category->parent);
+    }
+
+    protected function sub_test_grade_category_get_final() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'get_final'));
+        $category->load_grade_item();
+        $this->assertEquals($category->get_final(), $category->grade_item->get_final());
+    }
+
+    protected function sub_test_grade_category_get_sortorder() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'get_sortorder'));
+        $category->load_grade_item();
+        $this->assertEquals($category->get_sortorder(), $category->grade_item->get_sortorder());
+    }
+
+    protected function sub_test_grade_category_set_sortorder() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'set_sortorder'));
+        $category->load_grade_item();
+        $this->assertEquals($category->set_sortorder(10), $category->grade_item->set_sortorder(10));
+    }
+
+    protected function sub_test_grade_category_move_after_sortorder() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'move_after_sortorder'));
+        $category->load_grade_item();
+        $this->assertEquals($category->move_after_sortorder(10), $category->grade_item->move_after_sortorder(10));
+    }
+
+    protected function sub_test_grade_category_is_course_category() {
+        $category = grade_category::fetch_course_category($this->courseid);
+        $this->assertTrue(method_exists($category, 'is_course_category'));
+        $this->assertTrue($category->is_course_category());
+    }
+
+    protected function sub_test_grade_category_fetch_course_category() {
+        $category = new grade_category();
+        $this->assertTrue(method_exists($category, 'fetch_course_category'));
+        $category = grade_category::fetch_course_category($this->courseid);
+        $this->assertTrue(empty($category->parent));
+    }
+    /**
+     * TODO implement
+     */
+    protected function sub_test_grade_category_is_editable() {
+
+    }
+
+    protected function sub_test_grade_category_is_locked() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'is_locked'));
+        $category->load_grade_item();
+        $this->assertEquals($category->is_locked(), $category->grade_item->is_locked());
+    }
+
+    protected function sub_test_grade_category_set_locked() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'set_locked'));
+
+        //will return false as cannot lock a grade that needs updating
+        $this->assertFalse($category->set_locked(1));
+        grade_regrade_final_grades($this->courseid);
+
+        //get the category from the db again
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue($category->set_locked(1));
+    }
+
+    protected function sub_test_grade_category_is_hidden() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'is_hidden'));
+        $category->load_grade_item();
+        $this->assertEquals($category->is_hidden(), $category->grade_item->is_hidden());
+    }
+
+    protected function sub_test_grade_category_set_hidden() {
+        $category = new grade_category($this->grade_categories[0]);
+        $this->assertTrue(method_exists($category, 'set_hidden'));
+        $category->set_hidden(1);
+        $category->load_grade_item();
+        $this->assertEquals(true, $category->grade_item->is_hidden());
+    }
+
+    //beware: adding a duplicate course category messes up the data in a way that's hard to recover from
+    protected function sub_test_grade_category_insert_course_category() {
+        $grade_category = new grade_category();
+        $this->assertTrue(method_exists($grade_category, 'insert_course_category'));
+
+        $id = $grade_category->insert_course_category($this->courseid);
+        $this->assertNotNull($id);
+        $this->assertEquals('?', $grade_category->fullname);
+        $this->assertEquals(GRADE_AGGREGATE_WEIGHTED_MEAN2, $grade_category->aggregation);
+        $this->assertEquals("/$id/", $grade_category->path);
+        $this->assertEquals(1, $grade_category->depth);
+        $this->assertNull($grade_category->parent);
+    }
+
+    protected function generate_random_raw_grade($item, $userid) {
+        $grade = new grade_grade();
+        $grade->itemid = $item->id;
+        $grade->userid = $userid;
+        $grade->grademin = 0;
+        $grade->grademax = 1;
+        $valuetype = "grade$item->gradetype";
+        $grade->rawgrade = rand(0, 1000) / 1000;
+        $grade->insert();
+        return $grade->rawgrade;
+    }
+}
diff --git a/lib/grade/tests/grade_grade_test.php b/lib/grade/tests/grade_grade_test.php
new file mode 100644 (file)
index 0000000..0227b23
--- /dev/null
@@ -0,0 +1,196 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/lib.php');
+
+
+class grade_grade_testcase extends grade_base_testcase {
+
+    public function test_grade_grade() {
+        $this->sub_test_grade_grade_construct();
+        $this->sub_test_grade_grade_insert();
+        $this->sub_test_grade_grade_update();
+        $this->sub_test_grade_grade_fetch();
+        $this->sub_test_grade_grade_fetch_all();
+        $this->sub_test_grade_grade_load_grade_item();
+        $this->sub_test_grade_grade_standardise_score();
+        $this->sub_test_grade_grade_is_locked();
+        $this->sub_test_grade_grade_set_hidden();
+        $this->sub_test_grade_grade_is_hidden();
+    }
+
+    protected function sub_test_grade_grade_construct() {
+        $params = new stdClass();
+
+        $params->itemid = $this->grade_items[0]->id;
+        $params->userid = 1;
+        $params->rawgrade = 88;
+        $params->rawgrademax = 110;
+        $params->rawgrademin = 18;
+
+        $grade_grade = new grade_grade($params, false);
+        $this->assertEquals($params->itemid, $grade_grade->itemid);
+        $this->assertEquals($params->rawgrade, $grade_grade->rawgrade);
+    }
+
+    protected function sub_test_grade_grade_insert() {
+        $grade_grade = new grade_grade();
+        $this->assertTrue(method_exists($grade_grade, 'insert'));
+
+        $grade_grade->itemid = $this->grade_items[0]->id;
+        $grade_grade->userid = 10;
+        $grade_grade->rawgrade = 88;
+        $grade_grade->rawgrademax = 110;
+        $grade_grade->rawgrademin = 18;
+
+        // Check the grade_item's needsupdate variable first
+        $grade_grade->load_grade_item();
+        $this->assertEmpty($grade_grade->grade_item->needsupdate);
+
+        $grade_grade->insert();
+
+        $last_grade_grade = end($this->grade_grades);
+
+        $this->assertEquals($grade_grade->id, $last_grade_grade->id + 1);
+
+        // timecreated will only be set if the grade was submitted by an activity module
+        $this->assertTrue(empty($grade_grade->timecreated));
+        // timemodified will only be set if the grade was submitted by an activity module
+        $this->assertTrue(empty($grade_grade->timemodified));
+
+        //keep our collection the same as is in the database
+        $this->grade_grades[] = $grade_grade;
+    }
+
+    protected function sub_test_grade_grade_update() {
+        $grade_grade = new grade_grade($this->grade_grades[0]);
+        $this->assertTrue(method_exists($grade_grade, 'update'));
+    }
+
+    protected function sub_test_grade_grade_fetch() {
+        $grade_grade = new grade_grade();
+        $this->assertTrue(method_exists($grade_grade, 'fetch'));
+
+        $grades = grade_grade::fetch(array('id'=>$this->grade_grades[0]->id));
+        $this->assertEquals($this->grade_grades[0]->id, $grades->id);
+        $this->assertEquals($this->grade_grades[0]->rawgrade, $grades->rawgrade);
+    }
+
+    protected function sub_test_grade_grade_fetch_all() {
+        $grade_grade = new grade_grade();
+        $this->assertTrue(method_exists($grade_grade, 'fetch_all'));
+
+        $grades = grade_grade::fetch_all(array());
+        $this->assertEquals(count($this->grade_grades), count($grades));
+    }
+
+    protected function sub_test_grade_grade_load_grade_item() {
+        $grade_grade = new grade_grade($this->grade_grades[0]);
+        $this->assertTrue(method_exists($grade_grade, 'load_grade_item'));
+        $this->assertNull($grade_grade->grade_item);
+        $this->assertNotEmpty($grade_grade->itemid);
+        $this->assertNotNull($grade_grade->load_grade_item());
+        $this->assertNotNull($grade_grade->grade_item);
+        $this->assertEquals($this->grade_items[0]->id, $grade_grade->grade_item->id);
+    }
+
+
+    protected function sub_test_grade_grade_standardise_score() {
+        $this->assertEquals(4, round(grade_grade::standardise_score(6, 0, 7, 0, 5)));
+        $this->assertEquals(40, grade_grade::standardise_score(50, 30, 80, 0, 100));
+    }
+
+
+    /*
+     * Disabling this test: the set_locked() arguments have been modified, rendering these tests useless until they are re-written
+
+    protected function test_grade_grade_set_locked() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $grade = new grade_grade($grade_item->get_final(1));
+        $this->assertTrue(method_exists($grade, 'set_locked'));
+
+        $this->assertTrue(empty($grade_item->locked));
+        $this->assertTrue(empty($grade->locked));
+
+        $this->assertTrue($grade->set_locked(true));
+        $this->assertFalse(empty($grade->locked));
+        $this->assertTrue($grade->set_locked(false));
+        $this->assertTrue(empty($grade->locked));
+
+        $this->assertTrue($grade_item->set_locked(true, true));
+        $grade = new grade_grade($grade_item->get_final(1));
+
+        $this->assertFalse(empty($grade->locked));
+        $this->assertFalse($grade->set_locked(true, false));
+
+        $this->assertTrue($grade_item->set_locked(true, false));
+        $grade = new grade_grade($grade_item->get_final(1));
+
+        $this->assertTrue($grade->set_locked(true, false));
+    }
+    */
+
+    protected function sub_test_grade_grade_is_locked() {
+        $grade = new grade_grade($this->grade_grades[0]);
+        $this->assertTrue(method_exists($grade, 'is_locked'));
+
+        $this->assertFalse($grade->is_locked());
+        $grade->locked = time();
+        $this->assertTrue($grade->is_locked());
+    }
+
+    protected function sub_test_grade_grade_set_hidden() {
+        $grade = new grade_grade($this->grade_grades[0]);
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade, 'set_hidden'));
+
+        $this->assertEquals(0, $grade_item->hidden);
+        $this->assertEquals(0, $grade->hidden);
+
+        $grade->set_hidden(0);
+        $this->assertEquals(0, $grade->hidden);
+
+        $grade->set_hidden(1);
+        $this->assertEquals(1, $grade->hidden);
+
+        $grade->set_hidden(0);
+        $this->assertEquals(0, $grade->hidden);
+    }
+
+    protected function sub_test_grade_grade_is_hidden() {
+        $grade = new grade_grade($this->grade_grades[0]);
+        $this->assertTrue(method_exists($grade, 'is_hidden'));
+
+        $this->assertFalse($grade->is_hidden());
+        $grade->hidden = 1;
+        $this->assertTrue($grade->is_hidden());
+
+        $grade->hidden = time()-666;
+        $this->assertFalse($grade->is_hidden());
+
+        $grade->hidden = time()+666;
+        $this->assertTrue($grade->is_hidden());
+    }
+}
diff --git a/lib/grade/tests/grade_item_test.php b/lib/grade/tests/grade_item_test.php
new file mode 100644 (file)
index 0000000..dda234b
--- /dev/null
@@ -0,0 +1,547 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/lib.php');
+
+class grade_item_testcase extends grade_base_testcase {
+    public function test_grade_item() {
+        $this->sub_test_grade_item_construct();
+        $this->sub_test_grade_item_insert();
+        $this->sub_test_grade_item_delete();
+        $this->sub_test_grade_item_update();
+        $this->sub_test_grade_item_load_scale();
+        $this->sub_test_grade_item_load_outcome();
+        $this->sub_test_grade_item_qualifies_for_regrading();
+        $this->sub_test_grade_item_force_regrading();
+        $this->sub_test_grade_item_fetch();
+        $this->sub_test_grade_item_fetch_all();
+        $this->sub_test_grade_item_get_all_finals();
+        $this->sub_test_grade_item_get_final();
+        $this->sub_test_grade_item_get_sortorder();
+        $this->sub_test_grade_item_set_sortorder();
+        $this->sub_test_grade_item_move_after_sortorder();
+        $this->sub_test_grade_item_get_name();
+        $this->sub_test_grade_item_set_parent();
+        $this->sub_test_grade_item_get_parent_category();
+        $this->sub_test_grade_item_load_parent_category();
+        $this->sub_test_grade_item_get_item_category();
+        $this->sub_test_grade_item_load_item_category();
+        $this->sub_test_grade_item_regrade_final_grades();
+        $this->sub_test_grade_item_adjust_raw_grade();
+        $this->sub_test_grade_item_set_locked();
+        $this->sub_test_grade_item_is_locked();
+        $this->sub_test_grade_item_set_hidden();
+        $this->sub_test_grade_item_is_hidden();
+        $this->sub_test_grade_item_is_category_item();
+        $this->sub_test_grade_item_is_course_item();
+        $this->sub_test_grade_item_fetch_course_item();
+        $this->sub_test_grade_item_depends_on();
+        $this->sub_test_grade_item_is_calculated();
+        $this->sub_test_grade_item_set_calculation();
+        $this->sub_test_grade_item_get_calculation();
+        $this->sub_test_grade_item_compute();
+    }
+
+    protected function sub_test_grade_item_construct() {
+        $params = new stdClass();
+
+        $params->courseid = $this->courseid;
+        $params->categoryid = $this->grade_categories[1]->id;
+        $params->itemname = 'unittestgradeitem4';
+        $params->itemtype = 'mod';
+        $params->itemmodule = 'database';
+        $params->iteminfo = 'Grade item used for unit testing';
+
+        $grade_item = new grade_item($params, false);
+
+        $this->assertEquals($params->courseid, $grade_item->courseid);
+        $this->assertEquals($params->categoryid, $grade_item->categoryid);
+        $this->assertEquals($params->itemmodule, $grade_item->itemmodule);
+    }
+
+    protected function sub_test_grade_item_insert() {
+        $grade_item = new grade_item();
+        $this->assertTrue(method_exists($grade_item, 'insert'));
+
+        $grade_item->courseid = $this->courseid;
+        $grade_item->categoryid = $this->grade_categories[1]->id;
+        $grade_item->itemname = 'unittestgradeitem4';
+        $grade_item->itemtype = 'mod';
+        $grade_item->itemmodule = 'quiz';
+        $grade_item->iteminfo = 'Grade item used for unit testing';
+
+        $grade_item->insert();
+
+        $last_grade_item = end($this->grade_items);
+
+        $this->assertEquals($grade_item->id, $last_grade_item->id + 1);
+        $this->assertEquals(11, $grade_item->sortorder);
+
+        //keep our reference collection the same as what is in the database
+        $this->grade_items[] = $grade_item;
+    }
+
+    protected function sub_test_grade_item_delete() {
+        global $DB;
+        $grade_item = new grade_item($this->grade_items[7]);//use a grade item not touched by previous (or future) tests
+        $this->assertTrue(method_exists($grade_item, 'delete'));
+
+        $this->assertTrue($grade_item->delete());
+
+        $this->assertFalse($DB->get_record('grade_items', array('id' => $grade_item->id)));
+
+        //keep our reference collection the same as the database
+        unset($this->grade_items[7]);
+    }
+
+    protected function sub_test_grade_item_update() {
+        global $DB;
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'update'));
+
+        $grade_item->iteminfo = 'Updated info for this unittest grade_item';
+
+        $this->assertTrue($grade_item->update());
+
+        $grade_item->grademin = 14;
+        $this->assertTrue($grade_item->qualifies_for_regrading());
+        $this->assertTrue($grade_item->update());
+
+        $iteminfo = $DB->get_field('grade_items', 'iteminfo', array('id' => $this->grade_items[0]->id));
+        $this->assertEquals($grade_item->iteminfo, $iteminfo);
+    }
+
+    protected function sub_test_grade_item_load_scale() {
+        $grade_item = new grade_item($this->grade_items[2]);
+        $this->assertTrue(method_exists($grade_item, 'load_scale'));
+        $scale = $grade_item->load_scale();
+        $this->assertFalse(empty($grade_item->scale));
+        $this->assertEquals($scale->id, $this->grade_items[2]->scaleid);
+    }
+
+    protected function sub_test_grade_item_load_outcome() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'load_outcome'));
+        //TODO: add tests
+    }
+
+    protected function sub_test_grade_item_qualifies_for_regrading() {
+        $grade_item = new grade_item($this->grade_items[1]);//use a grade item not touched by previous tests
+        $this->assertTrue(method_exists($grade_item, 'qualifies_for_regrading'));
+
+        $this->assertFalse($grade_item->qualifies_for_regrading());
+
+        $grade_item->iteminfo = 'Updated info for this unittest grade_item';
+
+        $this->assertFalse($grade_item->qualifies_for_regrading());
+
+        $grade_item->grademin = 14;
+
+        $this->assertTrue($grade_item->qualifies_for_regrading());
+    }
+
+    protected function sub_test_grade_item_force_regrading() {
+        $grade_item = new grade_item($this->grade_items[2]);//use a grade item not touched by previous tests
+        $this->assertTrue(method_exists($grade_item, 'force_regrading'));
+
+        $this->assertEquals(0, $grade_item->needsupdate);
+
+        $grade_item->force_regrading();
+        $this->assertEquals(1, $grade_item->needsupdate);
+        $grade_item->update_from_db();
+        $this->assertEquals(1, $grade_item->needsupdate);
+    }
+
+    protected function sub_test_grade_item_fetch() {
+        $grade_item = new grade_item();
+        $this->assertTrue(method_exists($grade_item, 'fetch'));
+
+        //not using $this->grade_items[0] as it's iteminfo was modified by sub_test_grade_item_qualifies_for_regrading()
+        $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
+        $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
+        $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
+
+        $grade_item = grade_item::fetch(array('itemtype'=>$this->grade_items[1]->itemtype, 'itemmodule'=>$this->grade_items[1]->itemmodule));
+        $this->assertEquals($this->grade_items[1]->id, $grade_item->id);
+        $this->assertEquals($this->grade_items[1]->iteminfo, $grade_item->iteminfo);
+    }
+
+    protected function sub_test_grade_item_fetch_all() {
+        $grade_item = new grade_item();
+        $this->assertTrue(method_exists($grade_item, 'fetch_all'));
+
+        $grade_items = grade_item::fetch_all(array('courseid'=>$this->courseid));
+        $this->assertEquals(count($this->grade_items), count($grade_items)-1);//-1 to account for the course grade item
+    }
+
+    // Retrieve all final scores for a given grade_item.
+    protected function sub_test_grade_item_get_all_finals() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'get_final'));
+
+        $final_grades = $grade_item->get_final();
+        $this->assertEquals(3, count($final_grades));
+    }
+
+
+    // Retrieve all final scores for a specific userid.
+    protected function sub_test_grade_item_get_final() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'get_final'));
+        $final_grade = $grade_item->get_final($this->user[1]->id);
+        $this->assertEquals($this->grade_grades[0]->finalgrade, $final_grade->finalgrade);
+    }
+
+    protected function sub_test_grade_item_get_sortorder() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'get_sortorder'));
+        $sortorder = $grade_item->get_sortorder();
+        $this->assertEquals($this->grade_items[0]->sortorder, $sortorder);
+    }
+
+    protected function sub_test_grade_item_set_sortorder() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'set_sortorder'));
+        $grade_item->set_sortorder(999);
+        $this->assertEquals($grade_item->sortorder, 999);
+    }
+
+    protected function sub_test_grade_item_move_after_sortorder() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'move_after_sortorder'));
+        $grade_item->move_after_sortorder(5);
+        $this->assertEquals($grade_item->sortorder, 6);
+
+        $grade_item = grade_item::fetch(array('id'=>$this->grade_items[0]->id));
+        $this->assertEquals($grade_item->sortorder, 6);
+
+        $after = grade_item::fetch(array('id'=>$this->grade_items[6]->id));
+        $this->assertEquals($after->sortorder, 8);
+    }
+
+    protected function sub_test_grade_item_get_name() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'get_name'));
+
+        $name = $grade_item->get_name();
+        $this->assertEquals($this->grade_items[0]->itemname, $name);
+    }
+
+    protected function sub_test_grade_item_set_parent() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'set_parent'));
+
+        $old = $grade_item->get_parent_category();
+        $new = new grade_category($this->grade_categories[3]);
+        $new_item = $new->get_grade_item();
+
+        $this->assertTrue($grade_item->set_parent($new->id));
+
+        $new_item->update_from_db();
+        $grade_item->update_from_db();
+
+        $this->assertEquals($grade_item->categoryid, $new->id);
+    }
+
+    protected function sub_test_grade_item_get_parent_category() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'get_parent_category'));
+
+        $category = $grade_item->get_parent_category();
+        $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
+    }
+
+    protected function sub_test_grade_item_load_parent_category() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'load_parent_category'));
+
+        $category = $grade_item->load_parent_category();
+        $this->assertEquals($this->grade_categories[1]->fullname, $category->fullname);
+        $this->assertEquals($this->grade_categories[1]->fullname, $grade_item->parent_category->fullname);
+    }
+
+    protected function sub_test_grade_item_get_item_category() {
+        $grade_item = new grade_item($this->grade_items[3]);
+        $this->assertTrue(method_exists($grade_item, 'get_item_category'));
+
+        $category = $grade_item->get_item_category();
+        $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
+    }
+
+    protected function sub_test_grade_item_load_item_category() {
+        $grade_item = new grade_item($this->grade_items[3]);
+        $this->assertTrue(method_exists($grade_item, 'load_item_category'));
+
+        $category = $grade_item->load_item_category();
+        $this->assertEquals($this->grade_categories[0]->fullname, $category->fullname);
+        $this->assertEquals($this->grade_categories[0]->fullname, $grade_item->item_category->fullname);
+    }
+
+    // Test update of all final grades
+    protected function sub_test_grade_item_regrade_final_grades() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
+        $this->assertEquals(true, $grade_item->regrade_final_grades());
+        //TODO: add more tests
+    }
+
+    // Test the adjust_raw_grade method
+    protected function sub_test_grade_item_adjust_raw_grade() {
+        $grade_item = new grade_item($this->grade_items[2]); // anything but assignment module!
+        $this->assertTrue(method_exists($grade_item, 'adjust_raw_grade'));
+
+        $grade_raw = new stdClass();
+        $grade_raw->rawgrade = 40;
+        $grade_raw->grademax = 100;
+        $grade_raw->grademin = 0;
+
+        $grade_item->gradetype = GRADE_TYPE_VALUE;
+        $grade_item->multfactor = 1;
+        $grade_item->plusfactor = 0;
+        $grade_item->grademax = 50;
+        $grade_item->grademin = 0;
+
+        $original_grade_raw  = clone($grade_raw);
+        $original_grade_item = clone($grade_item);
+
+        $this->assertEquals(20, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Try a larger maximum grade
+        $grade_item->grademax = 150;
+        $grade_item->grademin = 0;
+        $this->assertEquals(60, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Try larger minimum grade
+        $grade_item->grademin = 50;
+
+        $this->assertEquals(90, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Rescaling from a small scale (0-50) to a larger scale (0-100)
+        $grade_raw->grademax = 50;
+        $grade_raw->grademin = 0;
+        $grade_item->grademax = 100;
+        $grade_item->grademin = 0;
+
+        $this->assertEquals(80, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Rescaling from a small scale (0-50) to a larger scale with offset (40-100)
+        $grade_item->grademax = 100;
+        $grade_item->grademin = 40;
+
+        $this->assertEquals(88, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Try multfactor and plusfactor
+        $grade_raw = clone($original_grade_raw);
+        $grade_item = clone($original_grade_item);
+        $grade_item->multfactor = 1.23;
+        $grade_item->plusfactor = 3;
+
+        $this->assertEquals(27.6, $grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax));
+
+        // Try multfactor below 0 and a negative plusfactor
+        $grade_raw = clone($original_grade_raw);
+        $grade_item = clone($original_grade_item);
+        $grade_item->multfactor = 0.23;
+        $grade_item->plusfactor = -3;
+
+        $this->assertEquals(round(1.6), round($grade_item->adjust_raw_grade($grade_raw->rawgrade, $grade_raw->grademin, $grade_raw->grademax)));
+    }
+
+    // Test locking of grade items
+    protected function sub_test_grade_item_set_locked() {
+        //getting a grade_item from the DB as set_locked() will fail if the grade items needs to be updated
+        //also needs to have at least one grade_grade or $grade_item->get_final(1) returns null
+        //$grade_item = new grade_item($this->grade_items[8]);
+        $grade_item = grade_item::fetch(array('id'=>$this->grade_items[8]->id));
+
+        $this->assertTrue(method_exists($grade_item, 'set_locked'));
+
+        $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id));
+        $this->assertTrue(empty($grade_item->locked));//not locked
+        $this->assertTrue(empty($grade_grade->locked));//not locked
+
+        $this->assertTrue($grade_item->set_locked(true, true, false));
+        $grade_grade = new grade_grade($grade_item->get_final($this->user[1]->id));
+
+        $this->assertFalse(empty($grade_item->locked));//locked
+        $this->assertFalse(empty($grade_grade->locked)); // individual grades should be locked too
+
+        $this->assertTrue($grade_item->set_locked(false, true, false));
+        $grade = new grade_grade($grade_item->get_final($this->user[1]->id));
+
+        $this->assertTrue(empty($grade_item->locked));
+        $this->assertTrue(empty($grade->locked)); // individual grades should be unlocked too
+    }
+
+    protected function sub_test_grade_item_is_locked() {
+        $grade_item = new grade_item($this->grade_items[10]);
+        $this->assertTrue(method_exists($grade_item, 'is_locked'));
+
+        $this->assertFalse($grade_item->is_locked());
+        $this->assertFalse($grade_item->is_locked($this->user[1]->id));
+        $this->assertTrue($grade_item->set_locked(true, true, false));
+        $this->assertTrue($grade_item->is_locked());
+        $this->assertTrue($grade_item->is_locked($this->user[1]->id));
+    }
+
+    // Test hiding of grade items
+    protected function sub_test_grade_item_set_hidden() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'set_hidden'));
+
+        $grade = new grade_grade($grade_item->get_final($this->user[1]->id));
+        $this->assertEquals(0, $grade_item->hidden);
+        $this->assertEquals(0, $grade->hidden);
+
+        $grade_item->set_hidden(666, true);
+        $grade = new grade_grade($grade_item->get_final($this->user[1]->id));
+
+        $this->assertEquals(666, $grade_item->hidden);
+        $this->assertEquals(666, $grade->hidden);
+    }
+
+    protected function sub_test_grade_item_is_hidden() {
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertTrue(method_exists($grade_item, 'is_hidden'));
+
+        $this->assertFalse($grade_item->is_hidden());
+        $this->assertFalse($grade_item->is_hidden(1));
+
+        $grade_item->set_hidden(1);
+        $this->assertTrue($grade_item->is_hidden());
+        $this->assertTrue($grade_item->is_hidden(1));
+
+        $grade_item->set_hidden(666);
+        $this->assertFalse($grade_item->is_hidden());
+        $this->assertFalse($grade_item->is_hidden(1));
+
+        $grade_item->set_hidden(time()+666);
+        $this->assertTrue($grade_item->is_hidden());
+        $this->assertTrue($grade_item->is_hidden(1));
+    }
+
+    protected function sub_test_grade_item_is_category_item() {
+        $grade_item = new grade_item($this->grade_items[3]);
+        $this->assertTrue(method_exists($grade_item, 'is_category_item'));
+        $this->assertTrue($grade_item->is_category_item());
+    }
+
+    protected function sub_test_grade_item_is_course_item() {
+        $grade_item = grade_item::fetch_course_item($this->courseid);
+        $this->assertTrue(method_exists($grade_item, 'is_course_item'));
+        $this->assertTrue($grade_item->is_course_item());
+    }
+
+    protected function sub_test_grade_item_fetch_course_item() {
+        $grade_item = grade_item::fetch_course_item($this->courseid);
+        $this->assertTrue(method_exists($grade_item, 'fetch_course_item'));
+        $this->assertEquals($grade_item->itemtype, 'course');
+    }
+
+    protected function sub_test_grade_item_depends_on() {
+        $grade_item = new grade_item($this->grade_items[1]);
+
+        // calculated grade dependency
+        $deps = $grade_item->depends_on();
+        sort($deps, SORT_NUMERIC); // for comparison
+        $this->assertEquals(array($this->grade_items[0]->id), $deps);
+
+        // simulate depends on returns none when locked
+        $grade_item->locked = time();
+        $grade_item->update();
+        $deps = $grade_item->depends_on();
+        sort($deps, SORT_NUMERIC); // for comparison
+        $this->assertEquals(array(), $deps);
+
+        // category dependency
+        $grade_item = new grade_item($this->grade_items[3]);
+        $deps = $grade_item->depends_on();
+        sort($deps, SORT_NUMERIC); // for comparison
+        $res = array($this->grade_items[4]->id, $this->grade_items[5]->id);
+        $this->assertEquals($res, $deps);
+    }
+
+    protected function sub_test_grade_item_is_calculated() {
+        $grade_item = new grade_item($this->grade_items[1]);
+        $this->assertTrue(method_exists($grade_item, 'is_calculated'));
+        $this->assertTrue($grade_item->is_calculated());
+
+        $grade_item = new grade_item($this->grade_items[0]);
+        $this->assertFalse($grade_item->is_calculated());
+    }
+
+    protected function sub_test_grade_item_set_calculation() {
+        $grade_item = new grade_item($this->grade_items[1]);
+        $this->assertTrue(method_exists($grade_item, 'set_calculation'));
+        $grade_itemsource = new grade_item($this->grade_items[0]);
+
+        $grade_item->set_calculation('=[['.$grade_itemsource->idnumber.']]');
+
+        $this->assertTrue(!empty($grade_item->needsupdate));
+        $this->assertEquals('=##gi'.$grade_itemsource->id.'##', $grade_item->calculation);
+    }
+
+    protected function sub_test_grade_item_get_calculation() {
+        $grade_item = new grade_item($this->grade_items[1]);
+        $this->assertTrue(method_exists($grade_item, 'get_calculation'));
+        $grade_itemsource = new grade_item($this->grade_items[0]);
+
+        $denormalizedformula = str_replace('##gi'.$grade_itemsource->id.'##', '[['.$grade_itemsource->idnumber.']]', $this->grade_items[1]->calculation);
+
+        $formula = $grade_item->get_calculation();
+        $this->assertTrue(!empty($grade_item->needsupdate));
+        $this->assertEquals($denormalizedformula, $formula);
+    }
+
+    public function sub_test_grade_item_compute() {
+        $grade_item = grade_item::fetch(array('id'=>$this->grade_items[1]->id));
+        $this->assertTrue(method_exists($grade_item, 'compute'));
+
+        //check the grade_grades in the array match those in the DB then delete $this->grade_items[1]'s grade_grades
+        $this->grade_grades[3] = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
+        $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[3]->id));
+        $grade_grade->delete();
+
+        $this->grade_grades[4] = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
+        $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[4]->id));
+        $grade_grade->delete();
+
+        $this->grade_grades[5] = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
+        $grade_grade = grade_grade::fetch(array('id'=>$this->grade_grades[5]->id));
+        $grade_grade->delete();
+
+        //recalculate the grades (its a calculation so pulls values from other grade_items) and reinsert them
+        $grade_item->compute();
+
+        $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[3]->userid, 'itemid'=>$this->grade_grades[3]->itemid));
+        $this->assertEquals($this->grade_grades[3]->finalgrade, $grade_grade->finalgrade);
+
+        $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[4]->userid, 'itemid'=>$this->grade_grades[4]->itemid));
+        $this->assertEquals($this->grade_grades[4]->finalgrade, $grade_grade->finalgrade);
+
+        $grade_grade = grade_grade::fetch(array('userid'=>$this->grade_grades[5]->userid, 'itemid'=>$this->grade_grades[5]->itemid));
+        $this->assertEquals($this->grade_grades[5]->finalgrade, $grade_grade->finalgrade);
+    }
+}
diff --git a/lib/grade/tests/grade_outcome_test.php b/lib/grade/tests/grade_outcome_test.php
new file mode 100644 (file)
index 0000000..0561d18
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/lib.php');
+
+
+class grade_outcome_testcase extends grade_base_testcase {
+
+    public function test_grade_outcome() {
+        $this->sub_test_grade_outcome_construct();
+        $this->sub_test_grade_outcome_insert();
+        $this->sub_test_grade_outcome_update();
+        $this->sub_test_grade_outcome_delete();
+        //$this->sub_test_grade_outcome_fetch();
+        $this->sub_test_grade_outcome_fetch_all();
+    }
+
+    protected function sub_test_grade_outcome_construct() {
+        $params = new stdClass();
+
+        $params->courseid = $this->courseid;
+        $params->shortname = 'Team work';
+
+        $grade_outcome = new grade_outcome($params, false);
+        $this->assertEquals($params->courseid, $grade_outcome->courseid);
+        $this->assertEquals($params->shortname, $grade_outcome->shortname);
+    }
+
+    protected function sub_test_grade_outcome_insert() {
+        $grade_outcome = new grade_outcome();
+        $this->assertTrue(method_exists($grade_outcome, 'insert'));
+
+        $grade_outcome->courseid = $this->courseid;
+        $grade_outcome->shortname = 'tw';
+        $grade_outcome->fullname = 'Team work';
+
+        $grade_outcome->insert();
+
+        $last_grade_outcome = end($this->grade_outcomes);
+
+        $this->assertEquals($grade_outcome->id, $last_grade_outcome->id + 1);
+        $this->assertFalse(empty($grade_outcome->timecreated));
+        $this->assertFalse(empty($grade_outcome->timemodified));
+    }
+
+    protected function sub_test_grade_outcome_update() {
+        global $DB;
+        $grade_outcome = new grade_outcome($this->grade_outcomes[0]);
+        $this->assertTrue(method_exists($grade_outcome, 'update'));
+        $grade_outcome->shortname = 'Team work';
+        $this->assertTrue($grade_outcome->update());
+        $shortname = $DB->get_field('grade_outcomes', 'shortname', array('id' => $this->grade_outcomes[0]->id));
+        $this->assertEquals($grade_outcome->shortname, $shortname);
+    }
+
+    protected function sub_test_grade_outcome_delete() {
+        global $DB;
+        $grade_outcome = new grade_outcome($this->grade_outcomes[0]);
+        $this->assertTrue(method_exists($grade_outcome, 'delete'));
+
+        $this->assertTrue($grade_outcome->delete());
+        $this->assertFalse($DB->get_record('grade_outcomes', array('id' => $grade_outcome->id)));
+    }
+
+    protected function sub_test_grade_outcome_fetch() {
+        $grade_outcome = new grade_outcome();
+        $this->assertTrue(method_exists($grade_outcome, 'fetch'));
+
+        $grade_outcome = grade_outcome::fetch(array('id'=>$this->grade_outcomes[0]->id));
+        $grade_outcome->load_scale();
+        $this->assertEquals($this->grade_outcomes[0]->id, $grade_outcome->id);
+        $this->assertEquals($this->grade_outcomes[0]->shortname, $grade_outcome->shortname);
+
+        $this->assertEquals($this->scale[2]->id, $grade_outcome->scale->id);
+    }
+
+    protected function sub_test_grade_outcome_fetch_all() {
+        $grade_outcome = new grade_outcome();
+        $this->assertTrue(method_exists($grade_outcome, 'fetch_all'));
+
+        $grade_outcomes = grade_outcome::fetch_all(array());
+        $this->assertEquals(count($this->grade_outcomes), count($grade_outcomes));
+    }
+}
diff --git a/lib/grade/tests/grade_scale_test.php b/lib/grade/tests/grade_scale_test.php
new file mode 100644 (file)
index 0000000..0a2f21c
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    core_grades
+ * @category   phpunit
+ * @copyright  nicolas@moodle.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__.'/fixtures/lib.php');
+
+
+class grade_scale_testcase extends grade_base_testcase {
+
+    public function test_grade_scale() {
+        $this->sub_test_scale_construct();
+        $this->sub_test_grade_scale_insert();
+        $this->sub_test_grade_scale_update();
+        $this->sub_test_grade_scale_delete();
+        $this->sub_test_grade_scale_fetch();
+        $this->sub_test_scale_load_items();
+        $this->sub_test_scale_compact_items();
+    }
+
+    protected function sub_test_scale_construct() {
+        $params = new stdClass();
+        $params->name        = 'unittestscale3';
+        $params->courseid    = $this->course->id;
+        $params->userid      = $this->userid;
+        $params->scale       = 'Distinction, Very Good, Good, Pass, Fail';
+        $params->description = 'This scale is used to mark standard assignments.';
+        $params->timemodified = time();
+
+        $scale = new grade_scale($params, false);
+
+        $this->assertEquals($params->name, $scale->name);
+        $this->assertEquals($params->scale, $scale->scale);
+        $this->assertEquals($params->description, $scale->description);
+
+    }
+
+    protected function sub_test_grade_scale_insert() {
+        $grade_scale = new grade_scale();
+        $this->assertTrue(method_exists($grade_scale, 'insert'));
+
+        $grade_scale->name        = 'unittestscale3';
+        $grade_scale->courseid    = $this->courseid;
+        $grade_scale->userid      = $this->userid;
+        $grade_scale->scale       = 'Distinction, Very Good, Good, Pass, Fail';
+        $grade_scale->description = 'This scale is used to mark standard assignments.';
+
+        $grade_scale->insert();
+
+        $last_grade_scale = end($this->scale);
+
+        $this->assertEquals($grade_scale->id, $last_grade_scale->id + 1);
+        $this->assertTrue(!empty($grade_scale->timecreated));
+        $this->assertTrue(!empty($grade_scale->timemodified));
+    }
+
+    protected function sub_test_grade_scale_update() {
+        global $DB;
+        $grade_scale = new grade_scale($this->scale[1]);
+        $this->assertTrue(method_exists($grade_scale, 'update'));
+
+        $grade_scale->name = 'Updated info for this unittest grade_scale';
+        $this->assertTrue($grade_scale->update());
+        $name = $DB->get_field('scale', 'name', array('id' => $this->scale[1]->id));
+        $this->assertEquals($grade_scale->name, $name);
+    }
+
+    protected function sub_test_grade_scale_delete() {
+        global $DB;
+        $grade_scale = new grade_scale($this->scale[4]);//choose one we're not using elsewhere
+        $this->assertTrue(method_exists($grade_scale, 'delete'));
+
+        $this->assertTrue($grade_scale->delete());
+        $this->assertFalse($DB->get_record('scale', array('id' => $grade_scale->id)));
+
+        //keep the reference collection the same as what is in the database
+        unset($this->scale[4]);
+    }
+
+    protected function sub_test_grade_scale_fetch() {
+        $grade_scale = new grade_scale();
+        $this->assertTrue(method_exists($grade_scale, 'fetch'));
+
+        $grade_scale = grade_scale::fetch(array('id'=>$this->scale[0]->id));
+        $this->assertEquals($this->scale[0]->id, $grade_scale->id);
+        $this->assertEquals($this->scale[0]->name, $grade_scale->name);
+    }
+
+    protected function sub_test_scale_load_items() {
+        $scale = new grade_scale($this->scale[0]);
+        $this->assertTrue(method_exists($scale, 'load_items'));
+
+        $scale->load_items();
+        $this->assertEquals(7, count($scale->scale_items));
+        $this->assertEquals('Fairly neutral', $scale->scale_items[2]);
+
+    }
+
+    protected function sub_test_scale_compact_items() {
+        $scale = new grade_scale($this->scale[0]);
+        $this->assertTrue(method_exists($scale, 'compact_items'));
+
+        $scale->load_items();
+        $scale->scale = null;
+        $scale->compact_items();
+
+        // The original string and the new string may have differences in whitespace around the delimiter, and that's OK
+        $this->assertEquals(preg_replace('/\s*,\s*/', ',', $this->scale[0]->scale), $scale->scale);
+    }
+}
index c70d907..b847fdd 100644 (file)
             <directory suffix="_test.php">lib/ajax/tests</directory>
             <directory suffix="_test.php">lib/form/tests</directory>
         </testsuite>
+        <testsuite name="core_grade">
+            <directory suffix="_test.php">lib/grade/tests</directory>
+            <directory suffix="_test.php">grade/tests</directory>
+            <directory suffix="_test.php">grade/grading/tests</directory>
+        </testsuite>
         <testsuite name="core_backup">
             <directory suffix="_test.php">backup/controller/tests</directory>
             <directory suffix="_test.php">backup/converter/moodle1/tests</directory>
         <testsuite name="core_course">
             <directory suffix="_test.php">course/tests</directory>
         </testsuite>
-        <testsuite name="core_grade">
-            <directory suffix="_test.php">grade/tests</directory>
-            <directory suffix="_test.php">grade/grading/tests</directory>
-        </testsuite>
 
         <!--Plugin suites: use admin/tool/phpunit/cli/util.php to build phpunit.xml from phpunit.xml.dist with up-to-date list of plugins in current install-->
 <!--@plugin_suites_start@-->