1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Unit tests for core_grades\component_gradeitems;
19 *
20 * @package   gradingform_rubric
21 * @category  test
22 * @copyright 2019 Mathew May <mathew.solutions>
23 * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
24 */
25
26declare(strict_types = 1);
27
28namespace gradingform_rubric\grades\grader\gradingpanel\external;
29
30use advanced_testcase;
31use coding_exception;
32use core_grades\component_gradeitem;
33use external_api;
34use mod_forum\local\entities\forum as forum_entity;
35use moodle_exception;
36
37/**
38 * Unit tests for core_grades\component_gradeitems;
39 *
40 * @package   gradingform_rubric
41 * @category  test
42 * @copyright 2019 Mathew May <mathew.solutions>
43 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 */
45class store_test extends advanced_testcase {
46
47    public static function setupBeforeClass(): void {
48        global $CFG;
49        require_once("{$CFG->libdir}/externallib.php");
50    }
51
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);
59
60        $this->expectException(coding_exception::class);
61        $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_invalid' component");
62        store::execute('mod_invalid', 1, 'foo', 2, false, 'formdata');
63    }
64
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);
72
73        $this->expectException(coding_exception::class);
74        $this->expectExceptionMessage("The 'foo' item is not valid for the 'mod_forum' component");
75        store::execute('mod_forum', 1, 'foo', 2, false, 'formdata');
76    }
77
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();
83
84        $forum = $this->get_forum_instance([
85            'grade_forum' => 5,
86        ]);
87        $course = $forum->get_course_record();
88        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
89        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
90        $this->setUser($teacher);
91
92        $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
93
94        $this->expectException(moodle_exception::class);
95        store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata');
96    }
97
98    /**
99     * Ensure that an execute against a different grading method is rejected.
100     */
101    public function test_execute_disabled(): void {
102        $this->resetAfterTest();
103
104        $forum = $this->get_forum_instance();
105        $course = $forum->get_course_record();
106        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
107        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
108        $this->setUser($teacher);
109
110        $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
111
112        $this->expectException(moodle_exception::class);
113        $this->expectExceptionMessage("Grading is not enabled");
114        store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, 'formdata');
115    }
116
117    /**
118     * Ensure that an execute against the correct grading method returns the current state of the user.
119     */
120    public function test_execute_store_graded(): void {
121        $this->resetAfterTest();
122        $generator = \testing_util::get_data_generator();
123        $rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
124
125        [
126            'forum' => $forum,
127            'controller' => $controller,
128            'definition' => $definition,
129            'student' => $student,
130            'teacher' => $teacher,
131        ] = $this->get_test_data();
132
133        $this->setUser($teacher);
134
135        $gradeitem = component_gradeitem::instance('mod_forum', $forum->get_context(), 'forum');
136        $grade = $gradeitem->get_grade_for_user($student, $teacher);
137        $instance = $gradeitem->get_advanced_grading_instance($teacher, $grade);
138
139        $submissiondata = $rubricgenerator->get_test_form_data($controller, (int) $student->id,
140            0, 'Too many mistakes. Please try again.',
141            2, 'Great number of pictures. Well done.'
142        );
143
144        $formdata = http_build_query((object) [
145            'instanceid' => $instance->get_id(),
146            'advancedgrading' => $submissiondata,
147        ], '', '&');
148
149        $result = store::execute('mod_forum', (int) $forum->get_context()->id, 'forum', (int) $student->id, false, $formdata);
150        $result = external_api::clean_returnvalue(store::execute_returns(), $result);
151
152        $this->assertIsArray($result);
153        $this->assertArrayHasKey('templatename', $result);
154
155        $this->assertEquals('gradingform_rubric/grades/grader/gradingpanel', $result['templatename']);
156
157        $this->assertArrayHasKey('warnings', $result);
158        $this->assertIsArray($result['warnings']);
159        $this->assertEmpty($result['warnings']);
160
161        // Test the grade array items.
162        $this->assertArrayHasKey('grade', $result);
163        $this->assertIsArray($result['grade']);
164        $this->assertIsInt($result['grade']['timecreated']);
165
166        $this->assertArrayHasKey('timemodified', $result['grade']);
167        $this->assertIsInt($result['grade']['timemodified']);
168
169        $this->assertArrayHasKey('usergrade', $result['grade']);
170        $this->assertEquals('1.00 / 2.00', $result['grade']['usergrade']);
171
172        $this->assertArrayHasKey('maxgrade', $result['grade']);
173        $this->assertIsInt($result['grade']['maxgrade']);
174        $this->assertEquals(2, $result['grade']['maxgrade']);
175
176        $this->assertArrayHasKey('gradedby', $result['grade']);
177        $this->assertEquals(fullname($teacher), $result['grade']['gradedby']);
178
179        $this->assertArrayHasKey('criteria', $result['grade']);
180        $criteria = $result['grade']['criteria'];
181        $this->assertCount(count($definition->rubric_criteria), $criteria);
182        foreach ($criteria as $criterion) {
183            $this->assertArrayHasKey('id', $criterion);
184            $criterionid = $criterion['id'];
185            $sourcecriterion = $definition->rubric_criteria[$criterionid];
186
187            $this->assertArrayHasKey('description', $criterion);
188            $this->assertEquals($sourcecriterion['description'], $criterion['description']);
189
190            $this->assertArrayHasKey('remark', $criterion);
191
192            $this->assertArrayHasKey('levels', $criterion);
193
194            $levels = $criterion['levels'];
195            foreach ($levels as $level) {
196                $levelid = $level['id'];
197                if (!isset($levelid)) {
198                    continue;
199                }
200                $sourcelevel = $sourcecriterion['levels'][$levelid];
201
202                $this->assertArrayHasKey('criterionid', $level);
203                $this->assertEquals($criterionid, $level['criterionid']);
204
205                $this->assertArrayHasKey('checked', $level);
206
207                $this->assertArrayHasKey('definition', $level);
208                $this->assertEquals($sourcelevel['definition'], $level['definition']);
209
210                $this->assertArrayHasKey('score', $level);
211                $this->assertEquals($sourcelevel['score'], $level['score']);
212            }
213
214        }
215
216        $this->assertEquals(1, $criteria[0]['levels'][1]['checked']);
217        $this->assertEquals('Too many mistakes. Please try again.', $criteria[0]['remark']);
218        $this->assertEquals(1, $criteria[1]['levels'][3]['checked']);
219        $this->assertEquals('Great number of pictures. Well done.', $criteria[1]['remark']);
220    }
221
222    /**
223     * Get a forum instance.
224     *
225     * @param array $config
226     * @return forum_entity
227     */
228    protected function get_forum_instance(array $config = []): forum_entity {
229        $this->resetAfterTest();
230
231        $datagenerator = $this->getDataGenerator();
232        $course = $datagenerator->create_course();
233        $forum = $datagenerator->create_module('forum', array_merge($config, ['course' => $course->id]));
234
235        $vaultfactory = \mod_forum\local\container::get_vault_factory();
236        $vault = $vaultfactory->get_forum_vault();
237
238        return $vault->get_from_id((int) $forum->id);
239    }
240
241    /**
242     * Get test data for forums graded using a rubric.
243     *
244     * @return array
245     */
246    protected function get_test_data(): array {
247        global $DB;
248
249        $this->resetAfterTest();
250
251        $generator = \testing_util::get_data_generator();
252        $rubricgenerator = $generator->get_plugin_generator('gradingform_rubric');
253
254        $forum = $this->get_forum_instance();
255        $course = $forum->get_course_record();
256        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
257        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
258
259        $this->setUser($teacher);
260        $controller = $rubricgenerator->get_test_rubric($forum->get_context(), 'forum', 'forum');
261        $definition = $controller->get_definition();
262
263        $DB->set_field('forum', 'grade_forum', count($definition->rubric_criteria), ['id' => $forum->get_id()]);
264        return [
265            'forum' => $forum,
266            'controller' => $controller,
267            'definition' => $definition,
268            'student' => $student,
269            'teacher' => $teacher,
270        ];
271    }
272}
273