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 the condition.
19 *
20 * @package availability_grouping
21 * @copyright 2014 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27use availability_grouping\condition;
28
29/**
30 * Unit tests for the condition.
31 *
32 * @package availability_grouping
33 * @copyright 2014 The Open University
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
36class availability_grouping_condition_testcase extends advanced_testcase {
37    /**
38     * Load required classes.
39     */
40    public function setUp() {
41        // Load the mock info class so that it can be used.
42        global $CFG;
43        require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php');
44    }
45
46    /**
47     * Tests constructing and using condition.
48     */
49    public function test_usage() {
50        global $CFG, $USER;
51        $this->resetAfterTest();
52        $CFG->enableavailability = true;
53
54        // Erase static cache before test.
55        condition::wipe_static_cache();
56
57        // Make a test course and user.
58        $generator = $this->getDataGenerator();
59        $course = $generator->create_course();
60        $user = $generator->create_user();
61        $generator->enrol_user($user->id, $course->id);
62        $info = new \core_availability\mock_info($course, $user->id);
63
64        // Make a test grouping and group.
65        $grouping = $generator->create_grouping(array('courseid' => $course->id,
66                'name' => 'Grouping!'));
67        $group = $generator->create_group(array('courseid' => $course->id));
68        groups_assign_grouping($grouping->id, $group->id);
69
70        // Do test (not in grouping).
71        $structure = (object)array('type' => 'grouping', 'id' => (int)$grouping->id);
72        $cond = new condition($structure);
73
74        // Check if available (when not available).
75        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
76        $information = $cond->get_description(false, false, $info);
77        $this->assertRegExp('~belong to a group in.*Grouping!~', $information);
78        $this->assertTrue($cond->is_available(true, $info, true, $user->id));
79
80        // Add user to grouping and refresh cache.
81        groups_add_member($group, $user);
82        get_fast_modinfo($course->id, 0, true);
83
84        // Recheck.
85        $this->assertTrue($cond->is_available(false, $info, true, $user->id));
86        $this->assertFalse($cond->is_available(true, $info, true, $user->id));
87        $information = $cond->get_description(false, true, $info);
88        $this->assertRegExp('~do not belong to a group in.*Grouping!~', $information);
89
90        // Admin user doesn't belong to the grouping, but they can access it
91        // either way (positive or NOT) because of accessallgroups.
92        $this->setAdminUser();
93        $infoadmin = new \core_availability\mock_info($course, $USER->id);
94        $this->assertTrue($cond->is_available(false, $infoadmin, true, $USER->id));
95        $this->assertTrue($cond->is_available(true, $infoadmin, true, $USER->id));
96
97        // Grouping that doesn't exist uses 'missing' text.
98        $cond = new condition((object)array('id' => $grouping->id + 1000));
99        $this->assertFalse($cond->is_available(false, $info, true, $user->id));
100        $information = $cond->get_description(false, false, $info);
101        $this->assertRegExp('~belong to a group in.*(Missing grouping)~', $information);
102
103        // We need an actual cm object to test the 'grouping from cm' option.
104        $pagegen = $generator->get_plugin_generator('mod_page');
105        $page = $pagegen->create_instance(array('course' => $course->id,
106                'groupingid' => $grouping->id, 'availability' =>
107                '{"op":"|","show":true,"c":[{"type":"grouping","activity":true}]}'));
108        rebuild_course_cache($course->id, true);
109
110        // Check if available using the 'from course-module' grouping option.
111        $modinfo = get_fast_modinfo($course, $user->id);
112        $cm = $modinfo->get_cm($page->cmid);
113        $info = new \core_availability\info_module($cm);
114        $information = '';
115        $this->assertTrue($info->is_available($information, false, $user->id));
116
117        // Remove user from grouping again and recheck.
118        groups_remove_member($group, $user);
119        get_fast_modinfo($course->id, 0, true);
120        $this->assertFalse($info->is_available($information, false, $user->id));
121        $this->assertRegExp('~belong to a group in.*Grouping!~', $information);
122    }
123
124    /**
125     * Tests the constructor including error conditions. Also tests the
126     * string conversion feature (intended for debugging only).
127     */
128    public function test_constructor() {
129        // No parameters.
130        $structure = new stdClass();
131        try {
132            $cond = new condition($structure);
133            $this->fail();
134        } catch (coding_exception $e) {
135            $this->assertContains('Missing ->id / ->activity', $e->getMessage());
136        }
137
138        // Invalid id (not int).
139        $structure->id = 'bourne';
140        try {
141            $cond = new condition($structure);
142            $this->fail();
143        } catch (coding_exception $e) {
144            $this->assertContains('Invalid ->id', $e->getMessage());
145        }
146
147        // Invalid activity option (not bool).
148        unset($structure->id);
149        $structure->activity = 42;
150        try {
151            $cond = new condition($structure);
152            $this->fail();
153        } catch (coding_exception $e) {
154            $this->assertContains('Invalid ->activity', $e->getMessage());
155        }
156
157        // Invalid activity option (false).
158        $structure->activity = false;
159        try {
160            $cond = new condition($structure);
161            $this->fail();
162        } catch (coding_exception $e) {
163            $this->assertContains('Invalid ->activity', $e->getMessage());
164        }
165
166        // Valid with id.
167        $structure->id = 123;
168        $cond = new condition($structure);
169        $this->assertEquals('{grouping:#123}', (string)$cond);
170
171        // Valid with activity.
172        unset($structure->id);
173        $structure->activity = true;
174        $cond = new condition($structure);
175        $this->assertEquals('{grouping:CM}', (string)$cond);
176    }
177
178    /**
179     * Tests the save() function.
180     */
181    public function test_save() {
182        $structure = (object)array('id' => 123);
183        $cond = new condition($structure);
184        $structure->type = 'grouping';
185        $this->assertEquals($structure, $cond->save());
186
187        $structure = (object)array('activity' => true);
188        $cond = new condition($structure);
189        $structure->type = 'grouping';
190        $this->assertEquals($structure, $cond->save());
191    }
192
193    /**
194     * Tests the update_dependency_id() function.
195     */
196    public function test_update_dependency_id() {
197        $cond = new condition((object)array('id' => 123));
198        $this->assertFalse($cond->update_dependency_id('frogs', 123, 456));
199        $this->assertFalse($cond->update_dependency_id('groupings', 12, 34));
200        $this->assertTrue($cond->update_dependency_id('groupings', 123, 456));
201        $after = $cond->save();
202        $this->assertEquals(456, $after->id);
203
204        $cond = new condition((object)array('activity' => true));
205        $this->assertFalse($cond->update_dependency_id('frogs', 123, 456));
206    }
207
208    /**
209     * Tests the filter_users (bulk checking) function. Also tests the SQL
210     * variant get_user_list_sql.
211     */
212    public function test_filter_users() {
213        global $DB, $CFG;
214        $this->resetAfterTest();
215        $CFG->enableavailability = true;
216
217        // Erase static cache before test.
218        condition::wipe_static_cache();
219
220        // Make a test course and some users.
221        $generator = $this->getDataGenerator();
222        $course = $generator->create_course();
223        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
224        $teacher = $generator->create_user();
225        $generator->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
226        $allusers = array($teacher->id => $teacher);
227        $students = array();
228        for ($i = 0; $i < 3; $i++) {
229            $student = $generator->create_user();
230            $students[$i] = $student;
231            $generator->enrol_user($student->id, $course->id, $roleids['student']);
232            $allusers[$student->id] = $student;
233        }
234        $info = new \core_availability\mock_info($course);
235        $checker = new \core_availability\capability_checker($info->get_context());
236
237        // Make test groups.
238        $group1 = $generator->create_group(array('courseid' => $course->id));
239        $group2 = $generator->create_group(array('courseid' => $course->id));
240        $grouping1 = $generator->create_grouping(array('courseid' => $course->id));
241        $grouping2 = $generator->create_grouping(array('courseid' => $course->id));
242        groups_assign_grouping($grouping1->id, $group1->id);
243        groups_assign_grouping($grouping2->id, $group2->id);
244
245        // Make page in grouping 2.
246        $pagegen = $generator->get_plugin_generator('mod_page');
247        $page = $pagegen->create_instance(array('course' => $course->id,
248                'groupingid' => $grouping2->id, 'availability' =>
249                '{"op":"|","show":true,"c":[{"type":"grouping","activity":true}]}'));
250
251        // Assign students to groups as follows (teacher is not in a group):
252        // 0: no groups.
253        // 1: in group 1/grouping 1.
254        // 2: in group 2/grouping 2.
255        groups_add_member($group1, $students[1]);
256        groups_add_member($group2, $students[2]);
257
258        // Test specific grouping.
259        $cond = new condition((object)array('id' => (int)$grouping1->id));
260        $result = array_keys($cond->filter_user_list($allusers, false, $info, $checker));
261        ksort($result);
262        $expected = array($teacher->id, $students[1]->id);
263        $this->assertEquals($expected, $result);
264
265        // Test it with get_user_list_sql.
266        list ($sql, $params) = $cond->get_user_list_sql(false, $info, true);
267        $result = $DB->get_fieldset_sql($sql, $params);
268        sort($result);
269        $this->assertEquals($expected, $result);
270
271        // NOT test.
272        $result = array_keys($cond->filter_user_list($allusers, true, $info, $checker));
273        ksort($result);
274        $expected = array($teacher->id, $students[0]->id, $students[2]->id);
275        $this->assertEquals($expected, $result);
276
277        // NOT with get_user_list_sql.
278        list ($sql, $params) = $cond->get_user_list_sql(true, $info, true);
279        $result = $DB->get_fieldset_sql($sql, $params);
280        sort($result);
281        $this->assertEquals($expected, $result);
282
283        // Test course-module grouping.
284        $modinfo = get_fast_modinfo($course);
285        $cm = $modinfo->get_cm($page->cmid);
286        $info = new \core_availability\info_module($cm);
287        $result = array_keys($info->filter_user_list($allusers, $course));
288        $expected = array($teacher->id, $students[2]->id);
289        $this->assertEquals($expected, $result);
290
291        // With get_user_list_sql.
292        list ($sql, $params) = $info->get_user_list_sql(true);
293        $result = $DB->get_fieldset_sql($sql, $params);
294        sort($result);
295        $this->assertEquals($expected, $result);
296    }
297}
298