MDL-19702 rewritten context caching by Sam Marshall + tweaks for potential problems...
[moodle.git] / lib / simpletest / testaccesslib.php
CommitLineData
d33725bd 1<?php
2/**
3 * Unit tests for (some of) ../accesslib.php.
4 *
5 * @copyright &copy; 2006 The Open University
6 * @author T.J.Hunt@open.ac.uk
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package moodlecore
9 */
10
11if (!defined('MOODLE_INTERNAL')) {
12 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
13}
14
b695b4e1 15class accesslib_test extends UnitTestCaseUsingDatabase {
081a63a9 16
17 public static $includecoverage = array('lib/accesslib.php');
18
d33725bd 19 function test_get_parent_contexts() {
20 $context = get_context_instance(CONTEXT_SYSTEM);
21 $this->assertEqual(get_parent_contexts($context), array());
22
23 $context = new stdClass;
24 $context->path = '/1/25';
25 $this->assertEqual(get_parent_contexts($context), array(1));
26
27 $context = new stdClass;
28 $context->path = '/1/123/234/345/456';
29 $this->assertEqual(get_parent_contexts($context), array(345, 234, 123, 1));
30 }
31
32 function test_get_parent_contextid() {
33 $context = get_context_instance(CONTEXT_SYSTEM);
34 $this->assertFalse(get_parent_contextid($context));
35
36 $context = new stdClass;
37 $context->path = '/1/25';
38 $this->assertEqual(get_parent_contextid($context), 1);
39
40 $context = new stdClass;
41 $context->path = '/1/123/234/345/456';
42 $this->assertEqual(get_parent_contextid($context), 345);
43 }
212235d3 44
45 function test_get_users_by_capability() {
2cc9bae5
SM
46 global $CFG;
47
48 $tablenames = array('capabilities', 'context', 'role', 'role_capabilities',
b695b4e1 49 'role_allow_assign', 'role_allow_override', 'role_assignments', 'role_context_levels',
50 'user', 'groups_members', 'cache_flags', 'events_handlers', 'user_lastaccess', 'course');
51 $this->create_test_tables($tablenames, 'lib');
52
53 accesslib_clear_all_caches_for_unit_testing();
54 $this->switch_to_test_db();
2cc9bae5 55 $this->switch_to_test_cfg();
b695b4e1 56
365a5941 57 $course = new stdClass();
b695b4e1 58 $course->category = 0;
59 $this->testdb->insert_record('course', $course);
60 $syscontext = get_system_context(false);
61
62 /// Install the roles system.
b695b4e1 63 $coursecreatorrole = create_role(get_string('coursecreators'), 'coursecreator',
4f0c2d00 64 get_string('coursecreatorsdescription'), 'coursecreator');
b695b4e1 65 $editteacherrole = create_role(get_string('defaultcourseteacher'), 'editingteacher',
4f0c2d00 66 get_string('defaultcourseteacherdescription'), 'editingteacher');
b695b4e1 67 $noneditteacherrole = create_role(get_string('noneditingteacher'), 'teacher',
4f0c2d00 68 get_string('noneditingteacherdescription'), 'teacher');
b695b4e1 69 $studentrole = create_role(get_string('defaultcoursestudent'), 'student',
4f0c2d00 70 get_string('defaultcoursestudentdescription'), 'student');
b695b4e1 71 $guestrole = create_role(get_string('guest'), 'guest',
4f0c2d00 72 get_string('guestdescription'), 'guest');
b695b4e1 73 $userrole = create_role(get_string('authenticateduser'), 'user',
4f0c2d00 74 get_string('authenticateduserdescription'), 'user');
b695b4e1 75
76 /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
b695b4e1 77 update_capabilities('moodle');
b44b6c45 78 update_capabilities('mod_forum');
79 update_capabilities('mod_quiz');
212235d3 80
81 // Create some nested contexts. instanceid does not matter for this. Just
82 // ensure we don't violate any unique keys by using an unlikely number.
83 // We will fix paths in a second.
84 $contexts = $this->load_test_data('context',
85 array('contextlevel', 'instanceid', 'path', 'depth'), array(
86 1 => array(40, 666, '', 2),
87 2 => array(50, 666, '', 3),
88 3 => array(70, 666, '', 4),
89 ));
b695b4e1 90 $contexts[0] = $syscontext;
212235d3 91 $contexts[1]->path = $contexts[0]->path . '/' . $contexts[1]->id;
b695b4e1 92 $this->testdb->set_field('context', 'path', $contexts[1]->path, array('id' => $contexts[1]->id));
212235d3 93 $contexts[2]->path = $contexts[1]->path . '/' . $contexts[2]->id;
b695b4e1 94 $this->testdb->set_field('context', 'path', $contexts[2]->path, array('id' => $contexts[2]->id));
212235d3 95 $contexts[3]->path = $contexts[2]->path . '/' . $contexts[3]->id;
b695b4e1 96 $this->testdb->set_field('context', 'path', $contexts[3]->path, array('id' => $contexts[3]->id));
212235d3 97
98 // Now make some test users.
99 $users = $this->load_test_data('user',
100 array('username', 'confirmed', 'deleted'), array(
101 'a' => array('a', 1, 0),
102 'cc' => array('cc', 1, 0),
103 't1' => array('t1', 1, 0),
104 's1' => array('s1', 1, 0),
105 's2' => array('s2', 1, 0),
106 'del' => array('del', 1, 1),
107 'unc' => array('unc', 0, 0),
108 ));
109
110 // Get some of the standard roles.
b695b4e1 111 $creator = $this->testdb->get_record('role', array('shortname' => 'coursecreator'));
112 $teacher = $this->testdb->get_record('role', array('shortname' => 'editingteacher'));
113 $student = $this->testdb->get_record('role', array('shortname' => 'student'));
114 $authuser = $this->testdb->get_record('role', array('shortname' => 'user'));
212235d3 115
116 // And some role assignments.
117 $ras = $this->load_test_data('role_assignments',
118 array('userid', 'roleid', 'contextid'), array(
212235d3 119 'cc' => array($users['cc']->id, $creator->id, $contexts[1]->id),
120 't1' => array($users['t1']->id, $teacher->id, $contexts[2]->id),
121 's1' => array($users['s1']->id, $student->id, $contexts[2]->id),
122 's2' => array($users['s2']->id, $student->id, $contexts[2]->id),
123 ));
124
2cc9bae5
SM
125 // And make user a into admin
126 $CFG->siteadmins = $users['a']->id;
f026fd1c 127 $CFG->defaultuserroleid = $userrole;
2cc9bae5 128
212235d3 129 // And some group memebership.
130 $gms = $this->load_test_data('groups_members',
131 array('userid', 'groupid'), array(
132 array($users['t1']->id, 666),
133 array($users['s1']->id, 666),
134 array($users['s2']->id, 667),
135 ));
136
137 // Test some simple cases - check that looking in coruse and module contextlevel gives the same answer.
138 foreach (array(2, 3) as $conindex) {
139 $results = get_users_by_capability($contexts[$conindex], 'mod/forum:replypost');
2cc9bae5 140 // note: admin accounts are never returned, so no admin return here
212235d3 141 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 142 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
212235d3 143 array_map(create_function('$o', 'return $o->id;'),
144 $results));
145 // Paging.
146 $firstuser = reset($results);
147 $this->assertEqual(array($firstuser->id => $firstuser), get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', 0, 1));
148 $seconduser = next($results);
149 $this->assertEqual(array($seconduser->id => $seconduser), get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', 1, 1));
2cc9bae5 150 // $doanything = false (ignored now)
212235d3 151 $this->assert(new ArraysHaveSameValuesExpectation(
152 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
153 array_map(create_function('$o', 'return $o->id;'),
154 get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', '', '', '', '', false)));
155 // group
156 $this->assert(new ArraysHaveSameValuesExpectation(
157 array($users['t1']->id, $users['s1']->id)),
158 array_map(create_function('$o', 'return $o->id;'),
159 get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', '', '', 666)));
160 // exceptions
161 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 162 array($users['s1']->id, $users['s2']->id)),
212235d3 163 array_map(create_function('$o', 'return $o->id;'),
164 get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', '', '', '', array($users['t1']->id))));
165 $this->assert(new ArraysHaveSameValuesExpectation(
166 array($users['s1']->id)),
167 array_map(create_function('$o', 'return $o->id;'),
168 get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', '', '', 666, array($users['t1']->id))));
169 // $useviewallgroups
170 $this->assert(new ArraysHaveSameValuesExpectation(
171 array($users['t1']->id, $users['s2']->id)),
172 array_map(create_function('$o', 'return $o->id;'),
173 get_users_by_capability($contexts[$conindex], 'mod/forum:replypost', '', '', '', '', 667, '', false, false, true)));
174 // More than one capability.
175 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 176 array($users['s1']->id, $users['s2']->id)),
212235d3 177 array_map(create_function('$o', 'return $o->id;'),
178 get_users_by_capability($contexts[$conindex], array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'))));
179 }
212235d3 180
181// For reference: get_users_by_capability argument order:
182// $context, $capability, $fields='', $sort='', $limitfrom='', $limitnum='',
183// $groups='', $exceptions='', $doanything=true, $view=false, $useviewallgroups=false
184
185 // Now add some role overrides.
186 $rcs = $this->load_test_data('role_capabilities',
187 array('capability', 'roleid', 'contextid', 'permission'), array(
188 array('mod/forum:replypost', $student->id, $contexts[1]->id, CAP_PREVENT),
189 array('mod/forum:replypost', $student->id, $contexts[3]->id, CAP_ALLOW),
190 array('mod/quiz:attempt', $student->id, $contexts[2]->id, CAP_PREVENT),
191 array('mod/forum:startdiscussion', $student->id, $contexts[1]->id, CAP_PROHIBIT),
192 array('mod/forum:startdiscussion', $student->id, $contexts[3]->id, CAP_ALLOW),
193 array('mod/forum:viewrating', $authuser->id, $contexts[1]->id, CAP_PROHIBIT),
194 array('mod/forum:createattachment', $authuser->id, $contexts[3]->id, CAP_PREVENT),
212235d3 195 ));
196
197 // Now test the overridden cases.
198 // Students prevented at category level, with and without doanything.
199 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 200 array($users['t1']->id)),
212235d3 201 array_map(create_function('$o', 'return $o->id;'),
202 get_users_by_capability($contexts[2], 'mod/forum:replypost')));
203 $this->assert(new ArraysHaveSameValuesExpectation(
204 array($users['t1']->id)),
205 array_map(create_function('$o', 'return $o->id;'),
206 get_users_by_capability($contexts[2], 'mod/forum:replypost', '', '', '', '', '', '', false)));
207 // Students prevented at category level, but re-allowed at module level, with and without doanything.
208 $this->assert(new ArraysHaveSameValuesExpectation(
209 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
210 array_map(create_function('$o', 'return $o->id;'),
211 get_users_by_capability($contexts[3], 'mod/forum:replypost', '', '', '', '', '', '', false)));
212 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 213 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
212235d3 214 array_map(create_function('$o', 'return $o->id;'),
215 get_users_by_capability($contexts[3], 'mod/forum:replypost')));
216 // Students prohibited at category level, re-allowed at module level should have no effect.
217 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 218 array($users['t1']->id)),
212235d3 219 array_map(create_function('$o', 'return $o->id;'),
220 get_users_by_capability($contexts[2], 'mod/forum:startdiscussion')));
221 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 222 array($users['t1']->id)),
212235d3 223 array_map(create_function('$o', 'return $o->id;'),
224 get_users_by_capability($contexts[3], 'mod/forum:startdiscussion')));
225 // Prevent on logged-in user should be overridden by student allow.
226 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 227 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
212235d3 228 array_map(create_function('$o', 'return $o->id;'),
229 get_users_by_capability($contexts[3], 'mod/forum:createattachment')));
230
231 // Prohibit on logged-in user should trump student/teacher allow.
232 $this->assert(new ArraysHaveSameValuesExpectation(
2cc9bae5 233 array()),
212235d3 234 array_map(create_function('$o', 'return $o->id;'),
235 get_users_by_capability($contexts[3], 'mod/forum:viewrating')));
236
237 // More than one capability, where students have one, but not the other.
238 $this->assert(new ArraysHaveSameValuesExpectation(
239 array($users['s1']->id, $users['s2']->id)),
240 array_map(create_function('$o', 'return $o->id;'),
241 get_users_by_capability($contexts[3], array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'), '', '', '', '', '', '', false)));
212235d3 242 }
82701e24 243
244 function test_get_switchable_roles() {
245 global $USER;
246
247 $tablenames = array('role' , 'role_capabilities', 'role_assignments', 'role_allow_switch',
248 'capabilities', 'context', 'role_names');
249 $this->create_test_tables($tablenames, 'lib');
250
251 $this->switch_to_test_db();
2cc9bae5 252 $this->switch_to_test_cfg();
82701e24 253
254 // Ensure SYSCONTEXTID is set.
255 get_context_instance(CONTEXT_SYSTEM);
256
257 $contexts = $this->load_test_data('context',
258 array('contextlevel', 'instanceid', 'path', 'depth'), array(
259 'sys' => array(CONTEXT_SYSTEM, 0, '/' . SYSCONTEXTID, 1),
260 'cat' => array(CONTEXT_COURSECAT, 66, '/' . SYSCONTEXTID . '/' . (SYSCONTEXTID + 1), 2),
261 'cou' => array(CONTEXT_COURSE, 666, '/' . SYSCONTEXTID . '/' . (SYSCONTEXTID + 1) . '/' . (SYSCONTEXTID + 2), 3),
262 'fp' => array(CONTEXT_COURSE, SITEID, '/' . SYSCONTEXTID . '/' . SITEID, 2)));
263 $this->testdb->set_field('context', 'id', SYSCONTEXTID, array('id' => $contexts['sys']->id));
264 $this->testdb->set_field('context', 'id', SYSCONTEXTID + 1, array('id' => $contexts['cat']->id));
265 $this->testdb->set_field('context', 'id', SYSCONTEXTID + 2, array('id' => $contexts['cou']->id));
266 $syscontext = $contexts['sys'];
267 $syscontext->id = SYSCONTEXTID;
268 $context = $contexts['cou'];
269 $context->id = SYSCONTEXTID + 2;
270
82701e24 271 $roles = $this->load_test_data('role',
272 array( 'name', 'shortname', 'description', 'sortorder'), array(
82701e24 273 'r1' => array( 'r1', 'r1', 'not null', 2),
274 'r2' => array( 'r2', 'r2', 'not null', 3),
275 'funny' => array('funny', 'funny', 'not null', 4)));
82701e24 276 $r1id = $roles['r1']->id;
277 $r2id = $roles['r2']->id;
df997f84 278 $funnyid = $roles['funny']->id; // strange role
82701e24 279
2cc9bae5
SM
280 // Note that get_switchable_roles requires at least one capability for
281 // each role. I am not really clear why it was implemented that way
282 // but this makes the test work.
283 $roles = $this->load_test_data('role_capabilities',
284 array('roleid', 'capability'), array(
285 array($r1id, 'moodle/say:hello'),
286 array($r2id, 'moodle/say:hello'),
287 array($funnyid, 'moodle/say:hello'),
288 ));
289
82701e24 290 $this->load_test_data('role_assignments',
291 array('userid', 'contextid', 'roleid'), array(
82701e24 292 array( 2, SYSCONTEXTID + 1 , $r1id),
293 array( 3, SYSCONTEXTID + 2 , $r2id)));
294
295 $this->load_test_data('role_allow_switch',
296 array('roleid', 'allowswitch'), array(
297 array( $r1id , $r2id),
298 array( $r2id , $r1id),
299 array( $r2id , $r2id),
300 array( $r2id , $funnyid)));
301
82701e24 302 // r1 should be able to switch to r2, but this user only has r1 in $context, not $syscontext.
303 $this->switch_global_user_id(2);
304 accesslib_clear_all_caches_for_unit_testing();
305 $this->assert(new ArraysHaveSameValuesExpectation(array()), array_keys(get_switchable_roles($syscontext)));
306 $this->assert(new ArraysHaveSameValuesExpectation(array($r2id)), array_keys(get_switchable_roles($context)));
307 $this->revert_global_user_id();
308
2cc9bae5
SM
309 // The table says r2 should be able to switch to all of r1, r2 and funny;
310 // this used to be restricted further beyond the switch table (for example
311 // to prevent you switching to roles with doanything) but is not any more
312 // (for example because doanything does not exist).
82701e24 313 $this->switch_global_user_id(3);
314 accesslib_clear_all_caches_for_unit_testing();
315 $this->assert(new ArraysHaveSameValuesExpectation(array()), array_keys(get_switchable_roles($syscontext)));
2cc9bae5 316 $this->assert(new ArraysHaveSameValuesExpectation(array($r2id, $r1id, $funnyid)), array_keys(get_switchable_roles($context)));
82701e24 317 }
c468795c 318
d0e538ba
PS
319 function test_context_cache() {
320 // Create cache, empty
321 $cache = new context_cache();
322 $this->assertEqual(0, $cache->get_approx_count());
323
324 // Put context in cache
325 $context = (object)array('id'=>37,'contextlevel'=>50,'instanceid'=>13);
326 $cache->add($context);
327 $this->assertEqual(1, $cache->get_approx_count());
328
329 // Get context out of cache
330 $this->assertEqual($context, $cache->get(50, 13));
331 $this->assertEqual($context, $cache->get_by_id(37));
332
333 // Try to get context that isn't there
334 $this->assertEqual(false, $cache->get(50, 99));
335 $this->assertEqual(false, $cache->get(99, 13));
336 $this->assertEqual(false, $cache->get_by_id(99));
337
338 // Remove context from cache
339 $cache->remove($context);
340 $this->assertEqual(0, $cache->get_approx_count());
341 $this->assertEqual(false, $cache->get(50, 13));
342 $this->assertEqual(false, $cache->get_by_id(37));
343
344 // Add a stack of contexts to cache
345 for($i=0; $i<context_cache::MAX_SIZE; $i++) {
346 $context = (object)array('id'=>$i, 'contextlevel'=>50,
347 'instanceid'=>$i+1);
348 $cache->add($context);
349 }
350 $this->assertEqual(context_cache::MAX_SIZE, $cache->get_approx_count());
351
352 // Get a random sample from the middle
353 $sample = (object)array(
354 'id'=>174, 'contextlevel'=>50, 'instanceid'=>175);
355 $this->assertEqual($sample, $cache->get(50, 175));
356 $this->assertEqual($sample, $cache->get_by_id(174));
357
358 // Now add one more; should result in size being reduced
359 $context = (object)array('id'=>99999, 'contextlevel'=>50,
360 'instanceid'=>99999);
361 $cache->add($context);
362 $this->assertEqual(context_cache::MAX_SIZE - context_cache::REDUCE_SIZE + 1,
363 $cache->get_approx_count());
364
365 // Check that the first ones are no longer there
366 $this->assertEqual(false, $cache->get(50, 175));
367 $this->assertEqual(false, $cache->get_by_id(174));
368
369 // Check that the last ones are still there
370 $bigid = context_cache::MAX_SIZE - 10;
371 $sample = (object)array(
372 'id'=>$bigid, 'contextlevel'=>50, 'instanceid'=>$bigid+1);
373 $this->assertEqual($sample, $cache->get(50, $bigid+1));
374 $this->assertEqual($sample, $cache->get_by_id($bigid));
375
376 // Reset the cache
377 $cache->reset();
378 $this->assertEqual(0, $cache->get_approx_count());
379 $this->assertEqual(false, $cache->get(50, $bigid+1));
380 }
d33725bd 381}
8926f844 382