MDL-19702 rewritten context caching by Sam Marshall + tweaks for potential problems...
[moodle.git] / lib / simpletest / testaccesslib.php
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  */
11 if (!defined('MOODLE_INTERNAL')) {
12     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
13 }
15 class accesslib_test extends UnitTestCaseUsingDatabase {
17     public static $includecoverage = array('lib/accesslib.php');
19     function test_get_parent_contexts() {
20         $context = get_context_instance(CONTEXT_SYSTEM);
21         $this->assertEqual(get_parent_contexts($context), array());
23         $context = new stdClass;
24         $context->path = '/1/25';
25         $this->assertEqual(get_parent_contexts($context), array(1));
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     }
32     function test_get_parent_contextid() {
33         $context = get_context_instance(CONTEXT_SYSTEM);
34         $this->assertFalse(get_parent_contextid($context));
36         $context = new stdClass;
37         $context->path = '/1/25';
38         $this->assertEqual(get_parent_contextid($context), 1);
40         $context = new stdClass;
41         $context->path = '/1/123/234/345/456';
42         $this->assertEqual(get_parent_contextid($context), 345);
43     }
45     function test_get_users_by_capability() {
46         global $CFG;
48         $tablenames = array('capabilities', 'context', 'role', 'role_capabilities',
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');
53         accesslib_clear_all_caches_for_unit_testing();
54         $this->switch_to_test_db();
55         $this->switch_to_test_cfg();
57         $course = new stdClass();
58         $course->category = 0;
59         $this->testdb->insert_record('course', $course);
60         $syscontext = get_system_context(false);
62     /// Install the roles system.
63         $coursecreatorrole  = create_role(get_string('coursecreators'), 'coursecreator',
64                                           get_string('coursecreatorsdescription'), 'coursecreator');
65         $editteacherrole    = create_role(get_string('defaultcourseteacher'), 'editingteacher',
66                                           get_string('defaultcourseteacherdescription'), 'editingteacher');
67         $noneditteacherrole = create_role(get_string('noneditingteacher'), 'teacher',
68                                           get_string('noneditingteacherdescription'), 'teacher');
69         $studentrole        = create_role(get_string('defaultcoursestudent'), 'student',
70                                           get_string('defaultcoursestudentdescription'), 'student');
71         $guestrole          = create_role(get_string('guest'), 'guest',
72                                           get_string('guestdescription'), 'guest');
73         $userrole           = create_role(get_string('authenticateduser'), 'user',
74                                           get_string('authenticateduserdescription'), 'user');
76         /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
77         update_capabilities('moodle');
78         update_capabilities('mod_forum');
79         update_capabilities('mod_quiz');
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         ));
90         $contexts[0] = $syscontext;
91         $contexts[1]->path = $contexts[0]->path . '/' . $contexts[1]->id;
92         $this->testdb->set_field('context', 'path', $contexts[1]->path, array('id' => $contexts[1]->id));
93         $contexts[2]->path = $contexts[1]->path . '/' . $contexts[2]->id;
94         $this->testdb->set_field('context', 'path', $contexts[2]->path, array('id' => $contexts[2]->id));
95         $contexts[3]->path = $contexts[2]->path . '/' . $contexts[3]->id;
96         $this->testdb->set_field('context', 'path', $contexts[3]->path, array('id' => $contexts[3]->id));
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         ));
110         // Get some of the standard roles.
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'));
116         // And some role assignments.
117         $ras = $this->load_test_data('role_assignments',
118                 array('userid', 'roleid', 'contextid'), array(
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         ));
125         // And make user a into admin
126         $CFG->siteadmins = $users['a']->id;
127         $CFG->defaultuserroleid = $userrole;
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         ));
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');
140             // note: admin accounts are never returned, so no admin return here
141             $this->assert(new ArraysHaveSameValuesExpectation(
142                     array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
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));
150             // $doanything = false (ignored now)
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(
162                     array($users['s1']->id, $users['s2']->id)),
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(
176                     array($users['s1']->id, $users['s2']->id)),
177                     array_map(create_function('$o', 'return $o->id;'),
178                     get_users_by_capability($contexts[$conindex], array('mod/quiz:attempt', 'mod/quiz:reviewmyattempts'))));
179         }
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
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),
195         ));
197         // Now test the overridden cases.
198         // Students prevented at category level, with and without doanything.
199         $this->assert(new ArraysHaveSameValuesExpectation(
200                 array($users['t1']->id)),
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(
213                 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
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(
218                 array($users['t1']->id)),
219                 array_map(create_function('$o', 'return $o->id;'),
220                 get_users_by_capability($contexts[2], 'mod/forum:startdiscussion')));
221         $this->assert(new ArraysHaveSameValuesExpectation(
222                 array($users['t1']->id)),
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(
227                 array($users['t1']->id, $users['s1']->id, $users['s2']->id)),
228                 array_map(create_function('$o', 'return $o->id;'),
229                 get_users_by_capability($contexts[3], 'mod/forum:createattachment')));
231         // Prohibit on logged-in user should trump student/teacher allow.
232         $this->assert(new ArraysHaveSameValuesExpectation(
233                 array()),
234                 array_map(create_function('$o', 'return $o->id;'),
235                 get_users_by_capability($contexts[3], 'mod/forum:viewrating')));
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)));
242     }
244     function test_get_switchable_roles() {
245         global $USER;
247         $tablenames = array('role' , 'role_capabilities', 'role_assignments', 'role_allow_switch',
248                 'capabilities', 'context', 'role_names');
249         $this->create_test_tables($tablenames, 'lib');
251         $this->switch_to_test_db();
252         $this->switch_to_test_cfg();
254         // Ensure SYSCONTEXTID is set.
255         get_context_instance(CONTEXT_SYSTEM);
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;
271         $roles = $this->load_test_data('role',
272                    array( 'name', 'shortname', 'description', 'sortorder'), array(
273         'r1' =>    array(   'r1',        'r1',    'not null',          2),
274         'r2' =>    array(   'r2',        'r2',    'not null',          3),
275         'funny' => array('funny',     'funny',    'not null',          4)));
276         $r1id = $roles['r1']->id;
277         $r2id = $roles['r2']->id;
278         $funnyid = $roles['funny']->id; // strange role
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         ));
290         $this->load_test_data('role_assignments',
291                 array('userid', 'contextid',   'roleid'), array(
292                 array(      2, SYSCONTEXTID + 1 , $r1id),
293                 array(      3, SYSCONTEXTID + 2 , $r2id)));
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)));
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();
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).
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)));
316         $this->assert(new ArraysHaveSameValuesExpectation(array($r2id, $r1id, $funnyid)), array_keys(get_switchable_roles($context)));
317     }
319     function test_context_cache() {
320         // Create cache, empty
321         $cache = new context_cache();
322         $this->assertEqual(0, $cache->get_approx_count());
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());
329         // Get context out of cache
330         $this->assertEqual($context, $cache->get(50, 13));
331         $this->assertEqual($context, $cache->get_by_id(37));
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));
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));
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());
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));
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());
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));
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));
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     }