MDL-63102 core_block: Reduced spacing between blocks
[moodle.git] / admin / tool / dataprivacy / tests / api_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  * API tests.
19  *
20  * @package    tool_dataprivacy
21  * @copyright  2018 Jun Pataleta
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 use core\invalid_persistent_exception;
26 use core\task\manager;
27 use tool_dataprivacy\context_instance;
28 use tool_dataprivacy\api;
29 use tool_dataprivacy\data_registry;
30 use tool_dataprivacy\expired_context;
31 use tool_dataprivacy\data_request;
32 use tool_dataprivacy\local\helper;
33 use tool_dataprivacy\task\initiate_data_request_task;
34 use tool_dataprivacy\task\process_data_request_task;
36 defined('MOODLE_INTERNAL') || die();
37 global $CFG;
39 /**
40  * API tests.
41  *
42  * @package    tool_dataprivacy
43  * @copyright  2018 Jun Pataleta
44  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45  */
46 class tool_dataprivacy_api_testcase extends advanced_testcase {
48     /**
49      * setUp.
50      */
51     public function setUp() {
52         $this->resetAfterTest();
53     }
55     /**
56      * Test for api::update_request_status().
57      */
58     public function test_update_request_status() {
59         $generator = new testing_data_generator();
60         $s1 = $generator->create_user();
61         $this->setUser($s1);
63         // Create the sample data request.
64         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
66         $requestid = $datarequest->get('id');
68         // Update with a valid status.
69         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_DOWNLOAD_READY);
70         $this->assertTrue($result);
72         // Fetch the request record again.
73         $datarequest = new data_request($requestid);
74         $this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $datarequest->get('status'));
76         // Update with an invalid status.
77         $this->expectException(invalid_persistent_exception::class);
78         api::update_request_status($requestid, -1);
79     }
81     /**
82      * Test for api::get_site_dpos() when there are no users with the DPO role.
83      */
84     public function test_get_site_dpos_no_dpos() {
85         $admin = get_admin();
87         $dpos = api::get_site_dpos();
88         $this->assertCount(1, $dpos);
89         $dpo = reset($dpos);
90         $this->assertEquals($admin->id, $dpo->id);
91     }
93     /**
94      * Test for api::get_site_dpos() when there are no users with the DPO role.
95      */
96     public function test_get_site_dpos() {
97         global $DB;
98         $generator = new testing_data_generator();
99         $u1 = $generator->create_user();
100         $u2 = $generator->create_user();
102         $context = context_system::instance();
104         // Give the manager role with the capability to manage data requests.
105         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
106         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
107         // Assign u1 as a manager.
108         role_assign($managerroleid, $u1->id, $context->id);
110         // Give the editing teacher role with the capability to manage data requests.
111         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
112         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
113         // Assign u1 as an editing teacher as well.
114         role_assign($editingteacherroleid, $u1->id, $context->id);
115         // Assign u2 as an editing teacher.
116         role_assign($editingteacherroleid, $u2->id, $context->id);
118         // Only map the manager role to the DPO role.
119         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
121         $dpos = api::get_site_dpos();
122         $this->assertCount(1, $dpos);
123         $dpo = reset($dpos);
124         $this->assertEquals($u1->id, $dpo->id);
125     }
127     /**
128      * Test for \tool_dataprivacy\api::get_assigned_privacy_officer_roles().
129      */
130     public function test_get_assigned_privacy_officer_roles() {
131         global $DB;
133         // Erroneously set the manager roles as the PO, even if it doesn't have the managedatarequests capability yet.
134         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
135         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
136         // Get the assigned PO roles when nothing has been set yet.
137         $roleids = api::get_assigned_privacy_officer_roles();
138         // Confirm that the returned list is empty.
139         $this->assertEmpty($roleids);
141         $context = context_system::instance();
143         // Give the manager role with the capability to manage data requests.
144         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
146         // Give the editing teacher role with the capability to manage data requests.
147         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
148         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
150         // Get the non-editing teacher role ID.
151         $teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
153         // Erroneously map the manager and the non-editing teacher roles to the PO role.
154         $badconfig = $managerroleid . ',' . $teacherroleid;
155         set_config('dporoles', $badconfig, 'tool_dataprivacy');
157         // Get the assigned PO roles.
158         $roleids = api::get_assigned_privacy_officer_roles();
160         // There should only be one PO role.
161         $this->assertCount(1, $roleids);
162         // Confirm it contains the manager role.
163         $this->assertContains($managerroleid, $roleids);
164         // And it does not contain the editing teacher role.
165         $this->assertNotContains($editingteacherroleid, $roleids);
166     }
168     /**
169      * Test for api::approve_data_request().
170      */
171     public function test_approve_data_request() {
172         global $DB;
174         $generator = new testing_data_generator();
175         $s1 = $generator->create_user();
176         $u1 = $generator->create_user();
178         $context = context_system::instance();
180         // Manager role.
181         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
182         // Give the manager role with the capability to manage data requests.
183         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
184         // Assign u1 as a manager.
185         role_assign($managerroleid, $u1->id, $context->id);
187         // Map the manager role to the DPO role.
188         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
190         // Create the sample data request.
191         $this->setUser($s1);
192         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
193         $requestid = $datarequest->get('id');
195         // Make this ready for approval.
196         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
198         $this->setUser($u1);
199         $result = api::approve_data_request($requestid);
200         $this->assertTrue($result);
201         $datarequest = new data_request($requestid);
202         $this->assertEquals($u1->id, $datarequest->get('dpo'));
203         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
205         // Test adhoc task creation.
206         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
207         $this->assertCount(1, $adhoctasks);
208     }
210     /**
211      * Test for api::approve_data_request() with the request not yet waiting for approval.
212      */
213     public function test_approve_data_request_not_yet_ready() {
214         global $DB;
216         $generator = new testing_data_generator();
217         $s1 = $generator->create_user();
218         $u1 = $generator->create_user();
220         $context = context_system::instance();
222         // Manager role.
223         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
224         // Give the manager role with the capability to manage data requests.
225         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
226         // Assign u1 as a manager.
227         role_assign($managerroleid, $u1->id, $context->id);
229         // Map the manager role to the DPO role.
230         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
232         // Create the sample data request.
233         $this->setUser($s1);
234         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
235         $requestid = $datarequest->get('id');
237         $this->setUser($u1);
238         $this->expectException(moodle_exception::class);
239         api::approve_data_request($requestid);
240     }
242     /**
243      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
244      */
245     public function test_approve_data_request_non_dpo_user() {
246         $generator = new testing_data_generator();
247         $student = $generator->create_user();
248         $teacher = $generator->create_user();
250         // Create the sample data request.
251         $this->setUser($student);
252         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
254         $requestid = $datarequest->get('id');
256         // Login as a user without DPO role.
257         $this->setUser($teacher);
258         $this->expectException(required_capability_exception::class);
259         api::approve_data_request($requestid);
260     }
262     /**
263      * Test for api::can_contact_dpo()
264      */
265     public function test_can_contact_dpo() {
266         // Default ('contactdataprotectionofficer' is disabled by default).
267         $this->assertFalse(api::can_contact_dpo());
269         // Enable.
270         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
271         $this->assertTrue(api::can_contact_dpo());
273         // Disable again.
274         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
275         $this->assertFalse(api::can_contact_dpo());
276     }
278     /**
279      * Test for api::can_manage_data_requests()
280      */
281     public function test_can_manage_data_requests() {
282         global $DB;
284         // No configured site DPOs yet.
285         $admin = get_admin();
286         $this->assertTrue(api::can_manage_data_requests($admin->id));
288         $generator = new testing_data_generator();
289         $dpo = $generator->create_user();
290         $nondpocapable = $generator->create_user();
291         $nondpoincapable = $generator->create_user();
293         $context = context_system::instance();
295         // Manager role.
296         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
297         // Give the manager role with the capability to manage data requests.
298         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
299         // Assign u1 as a manager.
300         role_assign($managerroleid, $dpo->id, $context->id);
302         // Editing teacher role.
303         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
304         // Give the editing teacher role with the capability to manage data requests.
305         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
306         // Assign u2 as an editing teacher.
307         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
309         // Map only the manager role to the DPO role.
310         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
312         // User with capability and has DPO role.
313         $this->assertTrue(api::can_manage_data_requests($dpo->id));
314         // User with capability but has no DPO role.
315         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
316         // User without the capability and has no DPO role.
317         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
318     }
320     /**
321      * Test for api::can_download_data_request_for_user()
322      */
323     public function test_can_download_data_request_for_user() {
324         $generator = $this->getDataGenerator();
326         // Three victims.
327         $victim1 = $generator->create_user();
328         $victim2 = $generator->create_user();
329         $victim3 = $generator->create_user();
331         // Assign a user as victim 1's parent.
332         $systemcontext = \context_system::instance();
333         $parentrole = $generator->create_role();
334         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
335         $parent = $generator->create_user();
336         role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
338         // Assign another user as data access wonder woman.
339         $wonderrole = $generator->create_role();
340         assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
341         $staff = $generator->create_user();
342         role_assign($wonderrole, $staff->id, $systemcontext);
344         // Finally, victim 3 has been naughty; stop them accessing their own data.
345         $naughtyrole = $generator->create_role();
346         assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
347         role_assign($naughtyrole, $victim3->id, $systemcontext);
349         // Victims 1 and 2 can access their own data, regardless of who requested it.
350         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
351         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
353         // Victim 3 cannot access his own data.
354         $this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
356         // Victims 1 and 2 cannot access another victim's data.
357         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
358         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
360         // Staff can access everyone's data.
361         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
362         $this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
363         $this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
365         // Parent can access victim 1's data only if they requested it.
366         $this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
367         $this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
368         $this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
369     }
371     /**
372      * Test for api::create_data_request()
373      */
374     public function test_create_data_request() {
375         $generator = new testing_data_generator();
376         $user = $generator->create_user();
377         $comment = 'sample comment';
379         // Login as user.
380         $this->setUser($user->id);
382         // Test data request creation.
383         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
384         $this->assertEquals($user->id, $datarequest->get('userid'));
385         $this->assertEquals($user->id, $datarequest->get('requestedby'));
386         $this->assertEquals(0, $datarequest->get('dpo'));
387         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
388         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
389         $this->assertEquals($comment, $datarequest->get('comments'));
391         // Test adhoc task creation.
392         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
393         $this->assertCount(1, $adhoctasks);
394     }
396     /**
397      * Test for api::create_data_request() made by DPO.
398      */
399     public function test_create_data_request_by_dpo() {
400         global $USER;
402         $generator = new testing_data_generator();
403         $user = $generator->create_user();
404         $comment = 'sample comment';
406         // Login as DPO (Admin is DPO by default).
407         $this->setAdminUser();
409         // Test data request creation.
410         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
411         $this->assertEquals($user->id, $datarequest->get('userid'));
412         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
413         $this->assertEquals($USER->id, $datarequest->get('dpo'));
414         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
415         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
416         $this->assertEquals($comment, $datarequest->get('comments'));
418         // Test adhoc task creation.
419         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
420         $this->assertCount(1, $adhoctasks);
421     }
423     /**
424      * Test for api::create_data_request() made by a parent.
425      */
426     public function test_create_data_request_by_parent() {
427         global $DB;
429         $generator = new testing_data_generator();
430         $user = $generator->create_user();
431         $parent = $generator->create_user();
432         $comment = 'sample comment';
434         // Get the teacher role pretend it's the parent roles ;).
435         $systemcontext = context_system::instance();
436         $usercontext = context_user::instance($user->id);
437         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
438         // Give the manager role with the capability to manage data requests.
439         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
440         // Assign the parent to user.
441         role_assign($parentroleid, $parent->id, $usercontext->id);
443         // Login as the user's parent.
444         $this->setUser($parent);
446         // Test data request creation.
447         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
448         $this->assertEquals($user->id, $datarequest->get('userid'));
449         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
450         $this->assertEquals(0, $datarequest->get('dpo'));
451         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
452         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
453         $this->assertEquals($comment, $datarequest->get('comments'));
455         // Test adhoc task creation.
456         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
457         $this->assertCount(1, $adhoctasks);
458     }
460     /**
461      * Test for api::deny_data_request()
462      */
463     public function test_deny_data_request() {
464         $generator = new testing_data_generator();
465         $user = $generator->create_user();
466         $comment = 'sample comment';
468         // Login as user.
469         $this->setUser($user->id);
471         // Test data request creation.
472         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
474         // Login as the admin (default DPO when no one is set).
475         $this->setAdminUser();
477         // Make this ready for approval.
478         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
480         // Deny the data request.
481         $result = api::deny_data_request($datarequest->get('id'));
482         $this->assertTrue($result);
483     }
485     /**
486      * Test for api::deny_data_request()
487      */
488     public function test_deny_data_request_without_permissions() {
489         $generator = new testing_data_generator();
490         $user = $generator->create_user();
491         $comment = 'sample comment';
493         // Login as user.
494         $this->setUser($user->id);
496         // Test data request creation.
497         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
499         // Login as a non-DPO user and try to call deny_data_request.
500         $user2 = $generator->create_user();
501         $this->setUser($user2);
502         $this->expectException(required_capability_exception::class);
503         api::deny_data_request($datarequest->get('id'));
504     }
506     /**
507      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
508      *
509      * @return array
510      */
511     public function get_data_requests_provider() {
512         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
513         $completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
515         return [
516             // Own data requests.
517             ['user', false, $completeonly],
518             // Non-DPO fetching all requets.
519             ['user', true, $completeonly],
520             // Admin fetching all completed and cancelled requests.
521             ['dpo', true, $completeandcancelled],
522             // Admin fetching all completed requests.
523             ['dpo', true, $completeonly],
524             // Guest fetching all requests.
525             ['guest', true, $completeonly],
526         ];
527     }
529     /**
530      * Test for api::get_data_requests()
531      *
532      * @dataProvider get_data_requests_provider
533      * @param string $usertype The type of the user logging in.
534      * @param boolean $fetchall Whether to fetch all records.
535      * @param int[] $statuses Status filters.
536      */
537     public function test_get_data_requests($usertype, $fetchall, $statuses) {
538         $generator = new testing_data_generator();
539         $user1 = $generator->create_user();
540         $user2 = $generator->create_user();
541         $user3 = $generator->create_user();
542         $user4 = $generator->create_user();
543         $user5 = $generator->create_user();
544         $users = [$user1, $user2, $user3, $user4, $user5];
546         switch ($usertype) {
547             case 'user':
548                 $loggeduser = $user1;
549                 break;
550             case 'dpo':
551                 $loggeduser = get_admin();
552                 break;
553             case 'guest':
554                 $loggeduser = guest_user();
555                 break;
556         }
558         $comment = 'Data %s request comment by user %d';
559         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
560         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
561         // Make a data requests for the users.
562         foreach ($users as $user) {
563             $this->setUser($user);
564             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
565             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
566         }
568         // Log in as the target user.
569         $this->setUser($loggeduser);
570         // Get records count based on the filters.
571         $userid = $loggeduser->id;
572         if ($fetchall) {
573             $userid = 0;
574         }
575         $count = api::get_data_requests_count($userid);
576         if (api::is_site_dpo($loggeduser->id)) {
577             // DPOs should see all the requests.
578             $this->assertEquals(count($users) * 2, $count);
579         } else {
580             if (empty($userid)) {
581                 // There should be no data requests for this user available.
582                 $this->assertEquals(0, $count);
583             } else {
584                 // There should be only one (request with pending status).
585                 $this->assertEquals(2, $count);
586             }
587         }
588         // Get data requests.
589         $requests = api::get_data_requests($userid);
590         // The number of requests should match the count.
591         $this->assertCount($count, $requests);
593         // Test filtering by status.
594         if ($count && !empty($statuses)) {
595             $filteredcount = api::get_data_requests_count($userid, $statuses);
596             // There should be none as they are all pending.
597             $this->assertEquals(0, $filteredcount);
598             $filteredrequests = api::get_data_requests($userid, $statuses);
599             $this->assertCount($filteredcount, $filteredrequests);
601             $statuscounts = [];
602             foreach ($statuses as $stat) {
603                 $statuscounts[$stat] = 0;
604             }
605             $numstatus = count($statuses);
606             // Get all requests with status filter and update statuses, randomly.
607             foreach ($requests as $request) {
608                 if (rand(0, 1)) {
609                     continue;
610                 }
612                 if ($numstatus > 1) {
613                     $index = rand(0, $numstatus - 1);
614                     $status = $statuses[$index];
615                 } else {
616                     $status = reset($statuses);
617                 }
618                 $statuscounts[$status]++;
619                 api::update_request_status($request->get('id'), $status);
620             }
621             $total = array_sum($statuscounts);
622             $filteredcount = api::get_data_requests_count($userid, $statuses);
623             $this->assertEquals($total, $filteredcount);
624             $filteredrequests = api::get_data_requests($userid, $statuses);
625             $this->assertCount($filteredcount, $filteredrequests);
626             // Confirm the filtered requests match the status filter(s).
627             foreach ($filteredrequests as $request) {
628                 $this->assertContains($request->get('status'), $statuses);
629             }
631             if ($numstatus > 1) {
632                 // Fetch by individual status to check the numbers match.
633                 foreach ($statuses as $status) {
634                     $filteredcount = api::get_data_requests_count($userid, [$status]);
635                     $this->assertEquals($statuscounts[$status], $filteredcount);
636                     $filteredrequests = api::get_data_requests($userid, [$status]);
637                     $this->assertCount($filteredcount, $filteredrequests);
638                 }
639             }
640         }
641     }
643     /**
644      * Data provider for test_has_ongoing_request.
645      */
646     public function status_provider() {
647         return [
648             [api::DATAREQUEST_STATUS_PENDING, true],
649             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
650             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
651             [api::DATAREQUEST_STATUS_APPROVED, true],
652             [api::DATAREQUEST_STATUS_PROCESSING, true],
653             [api::DATAREQUEST_STATUS_COMPLETE, false],
654             [api::DATAREQUEST_STATUS_CANCELLED, false],
655             [api::DATAREQUEST_STATUS_REJECTED, false],
656             [api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
657             [api::DATAREQUEST_STATUS_EXPIRED, false],
658             [api::DATAREQUEST_STATUS_DELETED, false],
659         ];
660     }
662     /**
663      * Test for api::has_ongoing_request()
664      *
665      * @dataProvider status_provider
666      * @param int $status The request status.
667      * @param bool $expected The expected result.
668      */
669     public function test_has_ongoing_request($status, $expected) {
670         $generator = new testing_data_generator();
671         $user1 = $generator->create_user();
673         // Make a data request as user 1.
674         $this->setUser($user1);
675         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
676         // Set the status.
677         api::update_request_status($request->get('id'), $status);
679         // Check if this request is ongoing.
680         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
681         $this->assertEquals($expected, $result);
682     }
684     /**
685      * Test for api::is_active()
686      *
687      * @dataProvider status_provider
688      * @param int $status The request status
689      * @param bool $expected The expected result
690      */
691     public function test_is_active($status, $expected) {
692         // Check if this request is ongoing.
693         $result = api::is_active($status);
694         $this->assertEquals($expected, $result);
695     }
697     /**
698      * Test for api::is_site_dpo()
699      */
700     public function test_is_site_dpo() {
701         global $DB;
703         // No configured site DPOs yet.
704         $admin = get_admin();
705         $this->assertTrue(api::is_site_dpo($admin->id));
707         $generator = new testing_data_generator();
708         $dpo = $generator->create_user();
709         $nondpo = $generator->create_user();
711         $context = context_system::instance();
713         // Manager role.
714         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
715         // Give the manager role with the capability to manage data requests.
716         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
717         // Assign u1 as a manager.
718         role_assign($managerroleid, $dpo->id, $context->id);
720         // Map only the manager role to the DPO role.
721         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
723         // User is a DPO.
724         $this->assertTrue(api::is_site_dpo($dpo->id));
725         // User is not a DPO.
726         $this->assertFalse(api::is_site_dpo($nondpo->id));
727     }
729     /**
730      * Data provider function for test_notify_dpo
731      *
732      * @return array
733      */
734     public function notify_dpo_provider() {
735         return [
736             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
737             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
738             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
739             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
740         ];
741     }
743     /**
744      * Test for api::notify_dpo()
745      *
746      * @dataProvider notify_dpo_provider
747      * @param bool $byadmin Whether the admin requests data on behalf of the user
748      * @param int $type The request type
749      * @param string $typestringid The request lang string identifier
750      * @param string $comments The requestor's message to the DPO.
751      */
752     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
753         $generator = new testing_data_generator();
754         $user1 = $generator->create_user();
755         // Let's just use admin as DPO (It's the default if not set).
756         $dpo = get_admin();
757         if ($byadmin) {
758             $this->setAdminUser();
759             $requestedby = $dpo;
760         } else {
761             $this->setUser($user1);
762             $requestedby = $user1;
763         }
765         // Make a data request for user 1.
766         $request = api::create_data_request($user1->id, $type, $comments);
768         $sink = $this->redirectMessages();
769         $messageid = api::notify_dpo($dpo, $request);
770         $this->assertNotFalse($messageid);
771         $messages = $sink->get_messages();
772         $this->assertCount(1, $messages);
773         $message = reset($messages);
775         // Check some of the message properties.
776         $this->assertEquals($requestedby->id, $message->useridfrom);
777         $this->assertEquals($dpo->id, $message->useridto);
778         $typestring = get_string($typestringid, 'tool_dataprivacy');
779         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
780         $this->assertEquals($subject, $message->subject);
781         $this->assertEquals('tool_dataprivacy', $message->component);
782         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
783         $this->assertContains(fullname($dpo), $message->fullmessage);
784         $this->assertContains(fullname($user1), $message->fullmessage);
785     }
787     /**
788      * Test of creating purpose as a user without privileges.
789      */
790     public function test_create_purpose_non_dpo_user() {
791         $pleb = $this->getDataGenerator()->create_user();
793         $this->setUser($pleb);
794         $this->expectException(required_capability_exception::class);
795         api::create_purpose((object)[
796             'name' => 'aaa',
797             'description' => '<b>yeah</b>',
798             'descriptionformat' => 1,
799             'retentionperiod' => 'PT1M'
800         ]);
801     }
803     /**
804      * Test fetching of purposes as a user without privileges.
805      */
806     public function test_get_purposes_non_dpo_user() {
807         $pleb = $this->getDataGenerator()->create_user();
808         $this->setAdminUser();
809         api::create_purpose((object)[
810             'name' => 'bbb',
811             'description' => '<b>yeah</b>',
812             'descriptionformat' => 1,
813             'retentionperiod' => 'PT1M',
814             'lawfulbases' => 'gdpr_art_6_1_a'
815         ]);
817         $this->setUser($pleb);
818         $this->expectException(required_capability_exception::class);
819         api::get_purposes();
820     }
822     /**
823      * Test updating of purpose as a user without privileges.
824      */
825     public function test_update_purposes_non_dpo_user() {
826         $pleb = $this->getDataGenerator()->create_user();
827         $this->setAdminUser();
828         $purpose = api::create_purpose((object)[
829             'name' => 'bbb',
830             'description' => '<b>yeah</b>',
831             'descriptionformat' => 1,
832             'retentionperiod' => 'PT1M',
833             'lawfulbases' => 'gdpr_art_6_1_a'
834         ]);
836         $this->setUser($pleb);
837         $this->expectException(required_capability_exception::class);
838         $purpose->set('retentionperiod', 'PT2M');
839         api::update_purpose($purpose->to_record());
840     }
842     /**
843      * Test purpose deletion as a user without privileges.
844      */
845     public function test_delete_purpose_non_dpo_user() {
846         $pleb = $this->getDataGenerator()->create_user();
847         $this->setAdminUser();
848         $purpose = api::create_purpose((object)[
849             'name' => 'bbb',
850             'description' => '<b>yeah</b>',
851             'descriptionformat' => 1,
852             'retentionperiod' => 'PT1M',
853             'lawfulbases' => 'gdpr_art_6_1_a'
854         ]);
856         $this->setUser($pleb);
857         $this->expectException(required_capability_exception::class);
858         api::delete_purpose($purpose->get('id'));
859     }
861     /**
862      * Test data purposes CRUD actions.
863      *
864      * @return null
865      */
866     public function test_purpose_crud() {
868         $this->setAdminUser();
870         // Add.
871         $purpose = api::create_purpose((object)[
872             'name' => 'bbb',
873             'description' => '<b>yeah</b>',
874             'descriptionformat' => 1,
875             'retentionperiod' => 'PT1M',
876             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
877         ]);
878         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
879         $this->assertEquals('bbb', $purpose->get('name'));
880         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
881         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
883         // Update.
884         $purpose->set('retentionperiod', 'PT2M');
885         $purpose = api::update_purpose($purpose->to_record());
886         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
888         // Retrieve.
889         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
890         $purposes = api::get_purposes();
891         $this->assertCount(2, $purposes);
892         $this->assertEquals('aaa', $purposes[0]->get('name'));
893         $this->assertEquals('bbb', $purposes[1]->get('name'));
895         // Delete.
896         api::delete_purpose($purposes[0]->get('id'));
897         $this->assertCount(1, api::get_purposes());
898         api::delete_purpose($purposes[1]->get('id'));
899         $this->assertCount(0, api::get_purposes());
900     }
902     /**
903      * Test creation of data categories as a user without privileges.
904      */
905     public function test_create_category_non_dpo_user() {
906         $pleb = $this->getDataGenerator()->create_user();
908         $this->setUser($pleb);
909         $this->expectException(required_capability_exception::class);
910         api::create_category((object)[
911             'name' => 'bbb',
912             'description' => '<b>yeah</b>',
913             'descriptionformat' => 1
914         ]);
915     }
917     /**
918      * Test fetching of data categories as a user without privileges.
919      */
920     public function test_get_categories_non_dpo_user() {
921         $pleb = $this->getDataGenerator()->create_user();
923         $this->setAdminUser();
924         api::create_category((object)[
925             'name' => 'bbb',
926             'description' => '<b>yeah</b>',
927             'descriptionformat' => 1
928         ]);
930         // Back to a regular user.
931         $this->setUser($pleb);
932         $this->expectException(required_capability_exception::class);
933         api::get_categories();
934     }
936     /**
937      * Test updating of data category as a user without privileges.
938      */
939     public function test_update_category_non_dpo_user() {
940         $pleb = $this->getDataGenerator()->create_user();
942         $this->setAdminUser();
943         $category = api::create_category((object)[
944             'name' => 'bbb',
945             'description' => '<b>yeah</b>',
946             'descriptionformat' => 1
947         ]);
949         // Back to a regular user.
950         $this->setUser($pleb);
951         $this->expectException(required_capability_exception::class);
952         $category->set('name', 'yeah');
953         api::update_category($category->to_record());
954     }
956     /**
957      * Test deletion of data category as a user without privileges.
958      */
959     public function test_delete_category_non_dpo_user() {
960         $pleb = $this->getDataGenerator()->create_user();
962         $this->setAdminUser();
963         $category = api::create_category((object)[
964             'name' => 'bbb',
965             'description' => '<b>yeah</b>',
966             'descriptionformat' => 1
967         ]);
969         // Back to a regular user.
970         $this->setUser($pleb);
971         $this->expectException(required_capability_exception::class);
972         api::delete_category($category->get('id'));
973         $this->fail('Users shouldn\'t be allowed to manage categories by default');
974     }
976     /**
977      * Test data categories CRUD actions.
978      *
979      * @return null
980      */
981     public function test_category_crud() {
983         $this->setAdminUser();
985         // Add.
986         $category = api::create_category((object)[
987             'name' => 'bbb',
988             'description' => '<b>yeah</b>',
989             'descriptionformat' => 1
990         ]);
991         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
992         $this->assertEquals('bbb', $category->get('name'));
994         // Update.
995         $category->set('name', 'bcd');
996         $category = api::update_category($category->to_record());
997         $this->assertEquals('bcd', $category->get('name'));
999         // Retrieve.
1000         $category = api::create_category((object)['name' => 'aaa']);
1001         $categories = api::get_categories();
1002         $this->assertCount(2, $categories);
1003         $this->assertEquals('aaa', $categories[0]->get('name'));
1004         $this->assertEquals('bcd', $categories[1]->get('name'));
1006         // Delete.
1007         api::delete_category($categories[0]->get('id'));
1008         $this->assertCount(1, api::get_categories());
1009         api::delete_category($categories[1]->get('id'));
1010         $this->assertCount(0, api::get_categories());
1011     }
1013     /**
1014      * Test context instances.
1015      *
1016      * @return null
1017      */
1018     public function test_context_instances() {
1019         global $DB;
1021         $this->setAdminUser();
1023         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1025         $coursecontext1 = \context_course::instance($courses[0]->id);
1026         $coursecontext2 = \context_course::instance($courses[1]->id);
1028         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
1029             'categoryid' => $categories[0]->get('id')];
1030         $contextinstance1 = api::set_context_instance($record1);
1032         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
1033             'categoryid' => $categories[1]->get('id')];
1034         $contextinstance2 = api::set_context_instance($record2);
1036         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
1038         api::unset_context_instance($contextinstance1);
1039         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1041         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
1042             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
1043         $contextinstance2 = api::set_context_instance($update);
1044         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
1045     }
1047     /**
1048      * Test contextlevel.
1049      *
1050      * @return null
1051      */
1052     public function test_contextlevel() {
1053         global $DB;
1055         $this->setAdminUser();
1056         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1058         $record = (object)[
1059             'purposeid' => $purposes[0]->get('id'),
1060             'categoryid' => $categories[0]->get('id'),
1061             'contextlevel' => CONTEXT_SYSTEM,
1062         ];
1063         $contextlevel = api::set_contextlevel($record);
1064         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
1065         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1066         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1067         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
1069         // Now update it.
1070         $record->purposeid = $purposes[1]->get('id');
1071         $contextlevel = api::set_contextlevel($record);
1072         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
1073         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
1074         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
1076         $record->contextlevel = CONTEXT_USER;
1077         $contextlevel = api::set_contextlevel($record);
1078         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
1079     }
1081     /**
1082      * Test effective context levels purpose and category defaults.
1083      *
1084      * @return null
1085      */
1086     public function test_effective_contextlevel_defaults() {
1087         $this->setAdminUser();
1089         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1091         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1092         $this->assertEquals(false, $purposeid);
1093         $this->assertEquals(false, $categoryid);
1095         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1096             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1097         );
1098         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1100         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1101         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1102         $this->assertEquals(false, $categoryid);
1104         // Course inherits from system if not defined.
1105         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1106         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1107         $this->assertEquals(false, $categoryid);
1109         // Course defined values should have preference.
1110         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1111             \context_helper::get_class_for_level(CONTEXT_COURSE)
1112         );
1113         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1114         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1116         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1117         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1118         $this->assertEquals($categories[0]->get('id'), $categoryid);
1120         // Context level defaults are also allowed to be set to 'inherit'.
1121         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1123         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1124         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1125         $this->assertEquals($categories[0]->get('id'), $categoryid);
1127         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
1128         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1129         $this->assertEquals($categories[0]->get('id'), $categoryid);
1130     }
1132     /**
1133      * Test effective contextlevel return.
1134      *
1135      * @return null
1136      */
1137     public function test_effective_contextlevel() {
1138         $this->setAdminUser();
1140         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1142         // Set the system context level to purpose 1.
1143         $record = (object)[
1144             'contextlevel' => CONTEXT_SYSTEM,
1145             'purposeid' => $purposes[1]->get('id'),
1146             'categoryid' => $categories[1]->get('id'),
1147         ];
1148         api::set_contextlevel($record);
1150         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
1151         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1153         // Value 'not set' will get the default value for the context level. For context level defaults
1154         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
1155         // will be retrieved.
1156         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1157         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1159         // The behaviour forcing an inherit from context system should result in the same effective
1160         // purpose.
1161         $record->purposeid = context_instance::INHERIT;
1162         $record->contextlevel = CONTEXT_USER;
1163         api::set_contextlevel($record);
1164         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1165         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1167         $record->purposeid = $purposes[2]->get('id');
1168         $record->contextlevel = CONTEXT_USER;
1169         api::set_contextlevel($record);
1171         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1172         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
1174         // Only system and user allowed.
1175         $this->expectException(coding_exception::class);
1176         $record->contextlevel = CONTEXT_COURSE;
1177         $record->purposeid = $purposes[1]->get('id');
1178         api::set_contextlevel($record);
1179     }
1181     /**
1182      * Test effective context purposes and categories.
1183      *
1184      * @return null
1185      */
1186     public function test_effective_context() {
1187         $this->setAdminUser();
1189         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1191         // Define system defaults (all context levels below will inherit).
1192         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1193             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1194         );
1195         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1196         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1198         // Define course defaults.
1199         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1200             \context_helper::get_class_for_level(CONTEXT_COURSE)
1201         );
1202         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1203         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
1205         $course0context = \context_course::instance($courses[0]->id);
1206         $course1context = \context_course::instance($courses[1]->id);
1207         $mod0context = \context_module::instance($modules[0]->cmid);
1208         $mod1context = \context_module::instance($modules[1]->cmid);
1210         // Set course instance values.
1211         $record = (object)[
1212             'contextid' => $course0context->id,
1213             'purposeid' => $purposes[1]->get('id'),
1214             'categoryid' => $categories[2]->get('id'),
1215         ];
1216         api::set_context_instance($record);
1217         $category = api::get_effective_context_category($course0context);
1218         $this->assertEquals($record->categoryid, $category->get('id'));
1220         // Module instances get the context level default if nothing specified.
1221         $category = api::get_effective_context_category($mod0context);
1222         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1224         // Module instances get the parent context category if they inherit.
1225         $record->contextid = $mod0context->id;
1226         $record->categoryid = context_instance::INHERIT;
1227         api::set_context_instance($record);
1228         $category = api::get_effective_context_category($mod0context);
1229         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
1231         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
1232         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
1233         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1234         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
1235         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
1237         // Module instances get the parent context category if they inherit; in
1238         // this case the parent context category is not set so it should use the
1239         // context level default (see 'Define course defaults' above).
1240         $record->contextid = $mod1context->id;
1241         $record->categoryid = context_instance::INHERIT;
1242         api::set_context_instance($record);
1243         $category = api::get_effective_context_category($mod1context);
1244         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1246         // User instances use the value set at user context level instead of the user default.
1248         // User defaults to cat 0 and user context level to 1.
1249         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1250             \context_helper::get_class_for_level(CONTEXT_USER)
1251         );
1252         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1253         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1254         $usercontextlevel = (object)[
1255             'contextlevel' => CONTEXT_USER,
1256             'purposeid' => $purposes[1]->get('id'),
1257             'categoryid' => $categories[1]->get('id'),
1258         ];
1259         api::set_contextlevel($usercontextlevel);
1261         $newuser = $this->getDataGenerator()->create_user();
1262         $usercontext = \context_user::instance($newuser->id);
1263         $category = api::get_effective_context_category($usercontext);
1264         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1265     }
1267     /**
1268      * Tests the deletion of expired contexts.
1269      *
1270      * @return null
1271      */
1272     public function test_expired_context_deletion() {
1273         global $DB;
1275         $this->setAdminUser();
1277         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1279         $course0context = \context_course::instance($courses[0]->id);
1280         $course1context = \context_course::instance($courses[1]->id);
1282         $expiredcontext0 = api::create_expired_context($course0context->id);
1283         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1284         $expiredcontext1 = api::create_expired_context($course1context->id);
1285         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
1287         api::delete_expired_context($expiredcontext0->get('id'));
1288         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1289     }
1291     /**
1292      * Tests the status of expired contexts.
1293      *
1294      * @return null
1295      */
1296     public function test_expired_context_status() {
1297         global $DB;
1299         $this->setAdminUser();
1301         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1303         $course0context = \context_course::instance($courses[0]->id);
1305         $expiredcontext = api::create_expired_context($course0context->id);
1307         // Default status.
1308         $this->assertEquals(expired_context::STATUS_EXPIRED, $expiredcontext->get('status'));
1310         api::set_expired_context_status($expiredcontext, expired_context::STATUS_APPROVED);
1311         $this->assertEquals(expired_context::STATUS_APPROVED, $expiredcontext->get('status'));
1312     }
1314     /**
1315      * Creates test purposes and categories.
1316      *
1317      * @return null
1318      */
1319     protected function add_purposes_and_categories() {
1321         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1322         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1323         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1325         $cat1 = api::create_category((object)['name' => 'a']);
1326         $cat2 = api::create_category((object)['name' => 'b']);
1327         $cat3 = api::create_category((object)['name' => 'c']);
1329         $course1 = $this->getDataGenerator()->create_course();
1330         $course2 = $this->getDataGenerator()->create_course();
1332         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1333         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1335         return [
1336             [$purpose1, $purpose2, $purpose3],
1337             [$cat1, $cat2, $cat3],
1338             [$course1, $course2],
1339             [$module1, $module2]
1340         ];
1341     }
1343     /**
1344      * Test that delete requests filter out protected purpose contexts.
1345      */
1346     public function test_add_request_contexts_with_status_delete() {
1347         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1348         $contextids = $data->list->get_contextids();
1350         $this->assertCount(1, $contextids);
1351         $this->assertEquals($data->contexts->unprotected, $contextids);
1352     }
1354     /**
1355      * Test that export requests don't filter out protected purpose contexts.
1356      */
1357     public function test_add_request_contexts_with_status_export() {
1358         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1359         $contextids = $data->list->get_contextids();
1361         $this->assertCount(2, $contextids);
1362         $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1363     }
1365     /**
1366      * Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
1367      */
1368     public function set_context_defaults_provider() {
1369         $contextlevels = [
1370             [CONTEXT_COURSECAT],
1371             [CONTEXT_COURSE],
1372             [CONTEXT_MODULE],
1373             [CONTEXT_BLOCK],
1374         ];
1375         $paramsets = [
1376             [true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
1377             [true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
1378             [false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
1379             [false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
1380             [false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
1381         ];
1382         $data = [];
1383         foreach ($contextlevels as $level) {
1384             foreach ($paramsets as $set) {
1385                 $data[] = array_merge($level, $set);
1386             }
1387             if ($level == CONTEXT_MODULE) {
1388                 // Add a combination where defaults for activity is being set.
1389                 $data[] = [CONTEXT_MODULE, false, false, true, false];
1390                 $data[] = [CONTEXT_MODULE, false, false, true, true];
1391             }
1392         }
1393         return $data;
1394     }
1396     /**
1397      * Test for \tool_dataprivacy\api::set_context_defaults()
1398      *
1399      * @dataProvider set_context_defaults_provider
1400      * @param int $contextlevel The context level
1401      * @param bool $inheritcategory Whether to set category value as INHERIT.
1402      * @param bool $inheritpurpose Whether to set purpose value as INHERIT.
1403      * @param bool $foractivity Whether to set defaults for an activity.
1404      * @param bool $override Whether to override instances.
1405      */
1406     public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
1407         $this->setAdminUser();
1409         $generator = $this->getDataGenerator();
1411         // Generate course cat, course, block, assignment, forum instances.
1412         $coursecat = $generator->create_category();
1413         $course = $generator->create_course(['category' => $coursecat->id]);
1414         $block = $generator->create_block('online_users');
1415         $assign = $generator->create_module('assign', ['course' => $course->id]);
1416         $forum = $generator->create_module('forum', ['course' => $course->id]);
1418         $coursecatcontext = context_coursecat::instance($coursecat->id);
1419         $coursecontext = context_course::instance($course->id);
1420         $blockcontext = context_block::instance($block->id);
1422         list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
1423         list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
1424         $assigncontext = context_module::instance($assigncm->id);
1425         $forumcontext = context_module::instance($forumcm->id);
1427         // Generate purposes and categories.
1428         $category1 = api::create_category((object)['name' => 'Test category 1']);
1429         $category2 = api::create_category((object)['name' => 'Test category 2']);
1430         $purpose1 = api::create_purpose((object)[
1431             'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1432         ]);
1433         $purpose2 = api::create_purpose((object)[
1434             'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
1435         ]);
1437         // Assign purposes and categories to contexts.
1438         $coursecatctxinstance = api::set_context_instance((object) [
1439             'contextid' => $coursecatcontext->id,
1440             'purposeid' => $purpose1->get('id'),
1441             'categoryid' => $category1->get('id'),
1442         ]);
1443         $coursectxinstance = api::set_context_instance((object) [
1444             'contextid' => $coursecontext->id,
1445             'purposeid' => $purpose1->get('id'),
1446             'categoryid' => $category1->get('id'),
1447         ]);
1448         $blockctxinstance = api::set_context_instance((object) [
1449             'contextid' => $blockcontext->id,
1450             'purposeid' => $purpose1->get('id'),
1451             'categoryid' => $category1->get('id'),
1452         ]);
1453         $assignctxinstance = api::set_context_instance((object) [
1454             'contextid' => $assigncontext->id,
1455             'purposeid' => $purpose1->get('id'),
1456             'categoryid' => $category1->get('id'),
1457         ]);
1458         $forumctxinstance = api::set_context_instance((object) [
1459             'contextid' => $forumcontext->id,
1460             'purposeid' => $purpose1->get('id'),
1461             'categoryid' => $category1->get('id'),
1462         ]);
1464         $categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
1465         $purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
1466         $activity = '';
1467         if ($contextlevel == CONTEXT_MODULE && $foractivity) {
1468             $activity = 'assign';
1469         }
1470         $result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
1471         $this->assertTrue($result);
1473         $targetctxinstance = false;
1474         switch ($contextlevel) {
1475             case CONTEXT_COURSECAT:
1476                 $targetctxinstance = $coursecatctxinstance;
1477                 break;
1478             case CONTEXT_COURSE:
1479                 $targetctxinstance = $coursectxinstance;
1480                 break;
1481             case CONTEXT_MODULE:
1482                 $targetctxinstance = $assignctxinstance;
1483                 break;
1484             case CONTEXT_BLOCK:
1485                 $targetctxinstance = $blockctxinstance;
1486                 break;
1487         }
1488         $this->assertNotFalse($targetctxinstance);
1490         // Check the context instances.
1491         $instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
1492         if ($override) {
1493             // If overridden, context instances on this context level would have been deleted.
1494             $this->assertFalse($instanceexists);
1496             // Check forum context instance.
1497             $forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
1498             if ($contextlevel != CONTEXT_MODULE || $foractivity) {
1499                 // The forum context instance won't be affected in this test if:
1500                 // - The overridden defaults are not for context modules.
1501                 // - Only the defaults for assign have been set.
1502                 $this->assertTrue($forumctxexists);
1503             } else {
1504                 // If we're overriding for the whole course module context level,
1505                 // then this forum context instance will be deleted as well.
1506                 $this->assertFalse($forumctxexists);
1507             }
1508         } else {
1509             // Otherwise, the context instance record remains.
1510             $this->assertTrue($instanceexists);
1511         }
1513         // Check defaults.
1514         list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
1515         if (!$inheritpurpose) {
1516             $this->assertEquals($purposeid, $defaultpurpose);
1517         }
1518         if (!$inheritcategory) {
1519             $this->assertEquals($categoryid, $defaultcategory);
1520         }
1521     }
1523     /**
1524      * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1525      *
1526      * @param       int $type The type of request to create
1527      * @return      \stdClass
1528      */
1529     protected function setup_test_add_request_contexts_with_status($type) {
1530         $this->setAdminUser();
1532         // User under test.
1533         $s1 = $this->getDataGenerator()->create_user();
1535         // Create three sample contexts.
1536         // 1 which should not be returned; and
1537         // 1 which will be returned and is not protected; and
1538         // 1 which will be returned and is protected.
1540         $c1 = $this->getDataGenerator()->create_course();
1541         $c2 = $this->getDataGenerator()->create_course();
1542         $c3 = $this->getDataGenerator()->create_course();
1544         $ctx1 = \context_course::instance($c1->id);
1545         $ctx2 = \context_course::instance($c2->id);
1546         $ctx3 = \context_course::instance($c3->id);
1548         $unprotected = api::create_purpose((object)[
1549             'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1550         $protected = api::create_purpose((object) [
1551             'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1553         $cat1 = api::create_category((object)['name' => 'a']);
1555         // Set the defaults.
1556         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1557             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1558         );
1559         set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1560         set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1562         $contextinstance1 = api::set_context_instance((object) [
1563                 'contextid' => $ctx1->id,
1564                 'purposeid' => $unprotected->get('id'),
1565                 'categoryid' => $cat1->get('id'),
1566             ]);
1568         $contextinstance2 = api::set_context_instance((object) [
1569                 'contextid' => $ctx2->id,
1570                 'purposeid' => $unprotected->get('id'),
1571                 'categoryid' => $cat1->get('id'),
1572             ]);
1574         $contextinstance3 = api::set_context_instance((object) [
1575                 'contextid' => $ctx3->id,
1576                 'purposeid' => $protected->get('id'),
1577                 'categoryid' => $cat1->get('id'),
1578             ]);
1580         $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1581         $contextlist = new \core_privacy\local\request\contextlist();
1582         $contextlist->set_component('tool_dataprivacy');
1583         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1584                 'ctx2' => $ctx2->id,
1585                 'ctx3' => $ctx3->id,
1586             ]);
1588         $collection->add_contextlist($contextlist);
1590         // Create the sample data request.
1591         $datarequest = api::create_data_request($s1->id, $type);
1592         $requestid = $datarequest->get('id');
1594         // Add the full collection with contexts 2, and 3.
1595         api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1597         // Mark it as approved.
1598         api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1600         // Fetch the list.
1601         $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1603         return (object) [
1604             'contexts' => (object) [
1605                 'unused' => [
1606                     $ctx1->id,
1607                 ],
1608                 'used' => [
1609                     $ctx2->id,
1610                     $ctx3->id,
1611                 ],
1612                 'unprotected' => [
1613                     $ctx2->id,
1614                 ],
1615                 'protected' => [
1616                     $ctx3->id,
1617                 ],
1618             ],
1619             'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1620         ];
1621     }