MDL-66809 core_grades: Implement scale-based marking
[moodle.git] / grade / tests / grades_grader_gradingpanel_scale_external_fetch_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for core_grades\component_gradeitems;
19  *
20  * @package   core_grades
21  * @category  test
22  * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
24  */
26 declare(strict_types = 1);
28 namespace core_grades\grades\grader\gradingpanel\scale\external;
30 use advanced_testcase;
31 use coding_exception;
32 use core_grades\component_gradeitem;
33 use external_api;
34 use mod_forum\local\entities\forum as forum_entity;
35 use moodle_exception;
37 /**
38  * Unit tests for core_grades\component_gradeitems;
39  *
40  * @package   core_grades
41  * @category  test
42  * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
43  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44  */
45 class fetch_test extends advanced_testcase {
47     public static function setupBeforeClass(): void {
48         global $CFG;
49         require_once("{$CFG->libdir}/externallib.php");
50     }
52     /**
53      * Ensure that an execute with an invalid component is rejected.
54      */
55     public function test_execute_invalid_component(): void {
56         $this->resetAfterTest();
57         $user = $this->getDataGenerator()->create_user();
58         $this->setUser($user);
60         $this->expectException(coding_exception::class);
61         $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component");
62         fetch::execute('mod_invalid', 1, 'foo', 2);
63     }
65     /**
66      * Ensure that an execute with an invalid itemname on a valid component is rejected.
67      */
68     public function test_execute_invalid_itemname(): void {
69         $this->resetAfterTest();
70         $user = $this->getDataGenerator()->create_user();
71         $this->setUser($user);
73         $this->expectException(coding_exception::class);
74         $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component");
75         fetch::execute('mod_forum', 1, 'foo', 2);
76     }
78     /**
79      * Ensure that an execute against a different grading method is rejected.
80      */
81     public function test_execute_incorrect_type(): void {
82         $this->resetAfterTest();
84         $forum = $this->get_forum_instance([
85             // Negative numbers mean a scale.
86             'grade_forum' => 5,
87         ]);
88         $course = $forum->get_course_record();
89         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
90         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
91         $this->setUser($teacher);
93         $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
95         $this->expectException(moodle_exception::class);
96         $this->expectExceptionMessage("not configured for grading with scales");
97         fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
98     }
100     /**
101      * Ensure that an execute against the correct grading method returns the current state of the user.
102      */
103     public function test_execute_fetch_empty(): void {
104         $this->resetAfterTest();
106         $options = [
107             'A',
108             'B',
109             'C'
110         ];
111         $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
113         $forum = $this->get_forum_instance([
114             // Negative numbers mean a scale.
115             'grade_forum' => -1 * $scale->id
116         ]);
117         $course = $forum->get_course_record();
118         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
119         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
120         $this->setUser($teacher);
122         $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
124         $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
125         $result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
127         $this->assertIsArray($result);
128         $this->assertArrayHasKey('templatename', $result);
130         $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
132         $this->assertArrayHasKey('grade', $result);
133         $this->assertIsArray($result['grade']);
135         $this->assertArrayHasKey('options', $result['grade']);
136         $this->assertCount(count($options), $result['grade']['options']);
137         rsort($options);
138         foreach ($options as $index => $option) {
139             $this->assertArrayHasKey($index, $result['grade']['options']);
141             $returnedoption = $result['grade']['options'][$index];
142             $this->assertArrayHasKey('value', $returnedoption);
143             $this->assertEquals(3 - $index, $returnedoption['value']);
145             $this->assertArrayHasKey('title', $returnedoption);
146             $this->assertEquals($option, $returnedoption['title']);
148             $this->assertArrayHasKey('selected', $returnedoption);
149             $this->assertFalse($returnedoption['selected']);
150         }
152         $this->assertIsInt($result['grade']['timecreated']);
153         $this->assertArrayHasKey('timemodified', $result['grade']);
154         $this->assertIsInt($result['grade']['timemodified']);
156         $this->assertArrayHasKey('warnings', $result);
157         $this->assertIsArray($result['warnings']);
158         $this->assertEmpty($result['warnings']);
159     }
161     /**
162      * Ensure that an execute against the correct grading method returns the current state of the user.
163      */
164     public function test_execute_fetch_graded(): void {
165         $this->resetAfterTest();
167         $options = [
168             'A',
169             'B',
170             'C'
171         ];
172         $scale = $this->getDataGenerator()->create_scale(['scale' => implode(',', $options)]);
174         $forum = $this->get_forum_instance([
175             // Negative numbers mean a scale.
176             'grade_forum' => -1 * $scale->id
177         ]);
178         $course = $forum->get_course_record();
179         $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
180         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
181         $this->setUser($teacher);
183         $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
184         $gradeitem->store_grade_from_formdata($student, $teacher, (object) [
185             'grade' => 2,
186         ]);
188         $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
189         $result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
191         $this->assertIsArray($result);
192         $this->assertArrayHasKey('templatename', $result);
194         $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
196         $result = fetch::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id);
197         $result = external_api::clean_returnvalue(fetch::execute_returns(), $result);
199         $this->assertIsArray($result);
200         $this->assertArrayHasKey('templatename', $result);
202         $this->assertEquals('core_grades/grades/grader/gradingpanel/scale', $result['templatename']);
204         $this->assertArrayHasKey('grade', $result);
205         $this->assertIsArray($result['grade']);
207         $this->assertArrayHasKey('options', $result['grade']);
208         $this->assertCount(count($options), $result['grade']['options']);
209         rsort($options);
210         foreach ($options as $index => $option) {
211             $this->assertArrayHasKey($index, $result['grade']['options']);
213             $returnedoption = $result['grade']['options'][$index];
214             $this->assertArrayHasKey('value', $returnedoption);
215             $this->assertEquals(3 - $index, $returnedoption['value']);
217             $this->assertArrayHasKey('title', $returnedoption);
218             $this->assertEquals($option, $returnedoption['title']);
220             $this->assertArrayHasKey('selected', $returnedoption);
221         }
223         // The grade was 2, which relates to the middle option.
224         $this->assertFalse($result['grade']['options'][0]['selected']);
225         $this->assertTrue($result['grade']['options'][1]['selected']);
226         $this->assertFalse($result['grade']['options'][2]['selected']);
228         $this->assertIsInt($result['grade']['timecreated']);
229         $this->assertArrayHasKey('timemodified', $result['grade']);
230         $this->assertIsInt($result['grade']['timemodified']);
232         $this->assertArrayHasKey('warnings', $result);
233         $this->assertIsArray($result['warnings']);
234         $this->assertEmpty($result['warnings']);
235     }
237     /**
238      * Get a forum instance.
239      *
240      * @param array $config
241      * @return forum_entity
242      */
243     protected function get_forum_instance(array $config = []): forum_entity {
244         $this->resetAfterTest();
246         $datagenerator = $this->getDataGenerator();
247         $course = $datagenerator->create_course();
248         $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id]));
250         $vaultfactory = \mod_forum\local\container::get_vault_factory();
251         $vault = $vaultfactory->get_forum_vault();
253         return $vault->get_from_id((int) $forum->id);
254     }