MDL-62962 tool_dataprivacy: Move user generation within the test
[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_COMPLETE);
70         $this->assertTrue($result);
72         // Fetch the request record again.
73         $datarequest = new data_request($requestid);
74         $this->assertEquals(api::DATAREQUEST_STATUS_COMPLETE, $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 api::approve_data_request().
129      */
130     public function test_approve_data_request() {
131         global $DB;
133         $generator = new testing_data_generator();
134         $s1 = $generator->create_user();
135         $u1 = $generator->create_user();
137         $context = context_system::instance();
139         // Manager role.
140         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
141         // Give the manager role with the capability to manage data requests.
142         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
143         // Assign u1 as a manager.
144         role_assign($managerroleid, $u1->id, $context->id);
146         // Map the manager role to the DPO role.
147         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
149         // Create the sample data request.
150         $this->setUser($s1);
151         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
152         $requestid = $datarequest->get('id');
154         // Make this ready for approval.
155         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
157         $this->setUser($u1);
158         $result = api::approve_data_request($requestid);
159         $this->assertTrue($result);
160         $datarequest = new data_request($requestid);
161         $this->assertEquals($u1->id, $datarequest->get('dpo'));
162         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
164         // Test adhoc task creation.
165         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
166         $this->assertCount(1, $adhoctasks);
167     }
169     /**
170      * Test for api::approve_data_request() with the request not yet waiting for approval.
171      */
172     public function test_approve_data_request_not_yet_ready() {
173         global $DB;
175         $generator = new testing_data_generator();
176         $s1 = $generator->create_user();
177         $u1 = $generator->create_user();
179         $context = context_system::instance();
181         // Manager role.
182         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
183         // Give the manager role with the capability to manage data requests.
184         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
185         // Assign u1 as a manager.
186         role_assign($managerroleid, $u1->id, $context->id);
188         // Map the manager role to the DPO role.
189         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
191         // Create the sample data request.
192         $this->setUser($s1);
193         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
194         $requestid = $datarequest->get('id');
196         $this->setUser($u1);
197         $this->expectException(moodle_exception::class);
198         api::approve_data_request($requestid);
199     }
201     /**
202      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
203      */
204     public function test_approve_data_request_non_dpo_user() {
205         $generator = new testing_data_generator();
206         $student = $generator->create_user();
207         $teacher = $generator->create_user();
209         // Create the sample data request.
210         $this->setUser($student);
211         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
213         $requestid = $datarequest->get('id');
215         // Login as a user without DPO role.
216         $this->setUser($teacher);
217         $this->expectException(required_capability_exception::class);
218         api::approve_data_request($requestid);
219     }
221     /**
222      * Test for api::can_contact_dpo()
223      */
224     public function test_can_contact_dpo() {
225         // Default ('contactdataprotectionofficer' is disabled by default).
226         $this->assertFalse(api::can_contact_dpo());
228         // Enable.
229         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
230         $this->assertTrue(api::can_contact_dpo());
232         // Disable again.
233         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
234         $this->assertFalse(api::can_contact_dpo());
235     }
237     /**
238      * Test for api::can_manage_data_requests()
239      */
240     public function test_can_manage_data_requests() {
241         global $DB;
243         // No configured site DPOs yet.
244         $admin = get_admin();
245         $this->assertTrue(api::can_manage_data_requests($admin->id));
247         $generator = new testing_data_generator();
248         $dpo = $generator->create_user();
249         $nondpocapable = $generator->create_user();
250         $nondpoincapable = $generator->create_user();
252         $context = context_system::instance();
254         // Manager role.
255         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
256         // Give the manager role with the capability to manage data requests.
257         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
258         // Assign u1 as a manager.
259         role_assign($managerroleid, $dpo->id, $context->id);
261         // Editing teacher role.
262         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
263         // Give the editing teacher role with the capability to manage data requests.
264         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
265         // Assign u2 as an editing teacher.
266         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
268         // Map only the manager role to the DPO role.
269         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
271         // User with capability and has DPO role.
272         $this->assertTrue(api::can_manage_data_requests($dpo->id));
273         // User with capability but has no DPO role.
274         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
275         // User without the capability and has no DPO role.
276         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
277     }
279     /**
280      * Test for api::create_data_request()
281      */
282     public function test_create_data_request() {
283         $generator = new testing_data_generator();
284         $user = $generator->create_user();
285         $comment = 'sample comment';
287         // Login as user.
288         $this->setUser($user->id);
290         // Test data request creation.
291         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
292         $this->assertEquals($user->id, $datarequest->get('userid'));
293         $this->assertEquals($user->id, $datarequest->get('requestedby'));
294         $this->assertEquals(0, $datarequest->get('dpo'));
295         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
296         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
297         $this->assertEquals($comment, $datarequest->get('comments'));
299         // Test adhoc task creation.
300         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
301         $this->assertCount(1, $adhoctasks);
302     }
304     /**
305      * Test for api::create_data_request() made by DPO.
306      */
307     public function test_create_data_request_by_dpo() {
308         global $USER;
310         $generator = new testing_data_generator();
311         $user = $generator->create_user();
312         $comment = 'sample comment';
314         // Login as DPO (Admin is DPO by default).
315         $this->setAdminUser();
317         // Test data request creation.
318         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
319         $this->assertEquals($user->id, $datarequest->get('userid'));
320         $this->assertEquals($USER->id, $datarequest->get('requestedby'));
321         $this->assertEquals($USER->id, $datarequest->get('dpo'));
322         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
323         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
324         $this->assertEquals($comment, $datarequest->get('comments'));
326         // Test adhoc task creation.
327         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
328         $this->assertCount(1, $adhoctasks);
329     }
331     /**
332      * Test for api::create_data_request() made by a parent.
333      */
334     public function test_create_data_request_by_parent() {
335         global $DB;
337         $generator = new testing_data_generator();
338         $user = $generator->create_user();
339         $parent = $generator->create_user();
340         $comment = 'sample comment';
342         // Get the teacher role pretend it's the parent roles ;).
343         $systemcontext = context_system::instance();
344         $usercontext = context_user::instance($user->id);
345         $parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
346         // Give the manager role with the capability to manage data requests.
347         assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
348         // Assign the parent to user.
349         role_assign($parentroleid, $parent->id, $usercontext->id);
351         // Login as the user's parent.
352         $this->setUser($parent);
354         // Test data request creation.
355         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
356         $this->assertEquals($user->id, $datarequest->get('userid'));
357         $this->assertEquals($parent->id, $datarequest->get('requestedby'));
358         $this->assertEquals(0, $datarequest->get('dpo'));
359         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
360         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
361         $this->assertEquals($comment, $datarequest->get('comments'));
363         // Test adhoc task creation.
364         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
365         $this->assertCount(1, $adhoctasks);
366     }
368     /**
369      * Test for api::deny_data_request()
370      */
371     public function test_deny_data_request() {
372         $generator = new testing_data_generator();
373         $user = $generator->create_user();
374         $comment = 'sample comment';
376         // Login as user.
377         $this->setUser($user->id);
379         // Test data request creation.
380         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
382         // Login as the admin (default DPO when no one is set).
383         $this->setAdminUser();
385         // Make this ready for approval.
386         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
388         // Deny the data request.
389         $result = api::deny_data_request($datarequest->get('id'));
390         $this->assertTrue($result);
391     }
393     /**
394      * Test for api::deny_data_request()
395      */
396     public function test_deny_data_request_without_permissions() {
397         $generator = new testing_data_generator();
398         $user = $generator->create_user();
399         $comment = 'sample comment';
401         // Login as user.
402         $this->setUser($user->id);
404         // Test data request creation.
405         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
407         // Login as a non-DPO user and try to call deny_data_request.
408         $user2 = $generator->create_user();
409         $this->setUser($user2);
410         $this->expectException(required_capability_exception::class);
411         api::deny_data_request($datarequest->get('id'));
412     }
414     /**
415      * Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
416      *
417      * @return array
418      */
419     public function get_data_requests_provider() {
420         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
421         $completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
423         return [
424             // Own data requests.
425             ['user', false, $completeonly],
426             // Non-DPO fetching all requets.
427             ['user', true, $completeonly],
428             // Admin fetching all completed and cancelled requests.
429             ['dpo', true, $completeandcancelled],
430             // Admin fetching all completed requests.
431             ['dpo', true, $completeonly],
432             // Guest fetching all requests.
433             ['guest', true, $completeonly],
434         ];
435     }
437     /**
438      * Test for api::get_data_requests()
439      *
440      * @dataProvider get_data_requests_provider
441      * @param string $usertype The type of the user logging in.
442      * @param boolean $fetchall Whether to fetch all records.
443      * @param int[] $statuses Status filters.
444      */
445     public function test_get_data_requests($usertype, $fetchall, $statuses) {
446         $generator = new testing_data_generator();
447         $user1 = $generator->create_user();
448         $user2 = $generator->create_user();
449         $user3 = $generator->create_user();
450         $user4 = $generator->create_user();
451         $user5 = $generator->create_user();
452         $users = [$user1, $user2, $user3, $user4, $user5];
454         switch ($usertype) {
455             case 'user':
456                 $loggeduser = $user1;
457                 break;
458             case 'dpo':
459                 $loggeduser = get_admin();
460                 break;
461             case 'guest':
462                 $loggeduser = guest_user();
463                 break;
464         }
466         $comment = 'Data %s request comment by user %d';
467         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
468         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
469         // Make a data requests for the users.
470         foreach ($users as $user) {
471             $this->setUser($user);
472             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
473             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
474         }
476         // Log in as the target user.
477         $this->setUser($loggeduser);
478         // Get records count based on the filters.
479         $userid = $loggeduser->id;
480         if ($fetchall) {
481             $userid = 0;
482         }
483         $count = api::get_data_requests_count($userid);
484         if (api::is_site_dpo($loggeduser->id)) {
485             // DPOs should see all the requests.
486             $this->assertEquals(count($users) * 2, $count);
487         } else {
488             if (empty($userid)) {
489                 // There should be no data requests for this user available.
490                 $this->assertEquals(0, $count);
491             } else {
492                 // There should be only one (request with pending status).
493                 $this->assertEquals(2, $count);
494             }
495         }
496         // Get data requests.
497         $requests = api::get_data_requests($userid);
498         // The number of requests should match the count.
499         $this->assertCount($count, $requests);
501         // Test filtering by status.
502         if ($count && !empty($statuses)) {
503             $filteredcount = api::get_data_requests_count($userid, $statuses);
504             // There should be none as they are all pending.
505             $this->assertEquals(0, $filteredcount);
506             $filteredrequests = api::get_data_requests($userid, $statuses);
507             $this->assertCount($filteredcount, $filteredrequests);
509             $statuscounts = [];
510             foreach ($statuses as $stat) {
511                 $statuscounts[$stat] = 0;
512             }
513             $numstatus = count($statuses);
514             // Get all requests with status filter and update statuses, randomly.
515             foreach ($requests as $request) {
516                 if (rand(0, 1)) {
517                     continue;
518                 }
520                 if ($numstatus > 1) {
521                     $index = rand(0, $numstatus - 1);
522                     $status = $statuses[$index];
523                 } else {
524                     $status = reset($statuses);
525                 }
526                 $statuscounts[$status]++;
527                 api::update_request_status($request->get('id'), $status);
528             }
529             $total = array_sum($statuscounts);
530             $filteredcount = api::get_data_requests_count($userid, $statuses);
531             $this->assertEquals($total, $filteredcount);
532             $filteredrequests = api::get_data_requests($userid, $statuses);
533             $this->assertCount($filteredcount, $filteredrequests);
534             // Confirm the filtered requests match the status filter(s).
535             foreach ($filteredrequests as $request) {
536                 $this->assertContains($request->get('status'), $statuses);
537             }
539             if ($numstatus > 1) {
540                 // Fetch by individual status to check the numbers match.
541                 foreach ($statuses as $status) {
542                     $filteredcount = api::get_data_requests_count($userid, [$status]);
543                     $this->assertEquals($statuscounts[$status], $filteredcount);
544                     $filteredrequests = api::get_data_requests($userid, [$status]);
545                     $this->assertCount($filteredcount, $filteredrequests);
546                 }
547             }
548         }
549     }
551     /**
552      * Data provider for test_has_ongoing_request.
553      */
554     public function status_provider() {
555         return [
556             [api::DATAREQUEST_STATUS_PENDING, true],
557             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
558             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
559             [api::DATAREQUEST_STATUS_APPROVED, true],
560             [api::DATAREQUEST_STATUS_PROCESSING, true],
561             [api::DATAREQUEST_STATUS_COMPLETE, false],
562             [api::DATAREQUEST_STATUS_CANCELLED, false],
563             [api::DATAREQUEST_STATUS_REJECTED, false],
564         ];
565     }
567     /**
568      * Test for api::has_ongoing_request()
569      *
570      * @dataProvider status_provider
571      * @param int $status The request status.
572      * @param bool $expected The expected result.
573      */
574     public function test_has_ongoing_request($status, $expected) {
575         $generator = new testing_data_generator();
576         $user1 = $generator->create_user();
578         // Make a data request as user 1.
579         $this->setUser($user1);
580         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
581         // Set the status.
582         api::update_request_status($request->get('id'), $status);
584         // Check if this request is ongoing.
585         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
586         $this->assertEquals($expected, $result);
587     }
589     /**
590      * Test for api::is_active()
591      *
592      * @dataProvider status_provider
593      * @param int $status The request status
594      * @param bool $expected The expected result
595      */
596     public function test_is_active($status, $expected) {
597         // Check if this request is ongoing.
598         $result = api::is_active($status);
599         $this->assertEquals($expected, $result);
600     }
602     /**
603      * Test for api::is_site_dpo()
604      */
605     public function test_is_site_dpo() {
606         global $DB;
608         // No configured site DPOs yet.
609         $admin = get_admin();
610         $this->assertTrue(api::is_site_dpo($admin->id));
612         $generator = new testing_data_generator();
613         $dpo = $generator->create_user();
614         $nondpo = $generator->create_user();
616         $context = context_system::instance();
618         // Manager role.
619         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
620         // Give the manager role with the capability to manage data requests.
621         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
622         // Assign u1 as a manager.
623         role_assign($managerroleid, $dpo->id, $context->id);
625         // Map only the manager role to the DPO role.
626         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
628         // User is a DPO.
629         $this->assertTrue(api::is_site_dpo($dpo->id));
630         // User is not a DPO.
631         $this->assertFalse(api::is_site_dpo($nondpo->id));
632     }
634     /**
635      * Data provider function for test_notify_dpo
636      *
637      * @return array
638      */
639     public function notify_dpo_provider() {
640         return [
641             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
642             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
643             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
644             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
645         ];
646     }
648     /**
649      * Test for api::notify_dpo()
650      *
651      * @dataProvider notify_dpo_provider
652      * @param bool $byadmin Whether the admin requests data on behalf of the user
653      * @param int $type The request type
654      * @param string $typestringid The request lang string identifier
655      * @param string $comments The requestor's message to the DPO.
656      */
657     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
658         $generator = new testing_data_generator();
659         $user1 = $generator->create_user();
660         // Let's just use admin as DPO (It's the default if not set).
661         $dpo = get_admin();
662         if ($byadmin) {
663             $this->setAdminUser();
664             $requestedby = $dpo;
665         } else {
666             $this->setUser($user1);
667             $requestedby = $user1;
668         }
670         // Make a data request for user 1.
671         $request = api::create_data_request($user1->id, $type, $comments);
673         $sink = $this->redirectMessages();
674         $messageid = api::notify_dpo($dpo, $request);
675         $this->assertNotFalse($messageid);
676         $messages = $sink->get_messages();
677         $this->assertCount(1, $messages);
678         $message = reset($messages);
680         // Check some of the message properties.
681         $this->assertEquals($requestedby->id, $message->useridfrom);
682         $this->assertEquals($dpo->id, $message->useridto);
683         $typestring = get_string($typestringid, 'tool_dataprivacy');
684         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
685         $this->assertEquals($subject, $message->subject);
686         $this->assertEquals('tool_dataprivacy', $message->component);
687         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
688         $this->assertContains(fullname($dpo), $message->fullmessage);
689         $this->assertContains(fullname($user1), $message->fullmessage);
690     }
692     /**
693      * Test of creating purpose as a user without privileges.
694      */
695     public function test_create_purpose_non_dpo_user() {
696         $pleb = $this->getDataGenerator()->create_user();
698         $this->setUser($pleb);
699         $this->expectException(required_capability_exception::class);
700         api::create_purpose((object)[
701             'name' => 'aaa',
702             'description' => '<b>yeah</b>',
703             'descriptionformat' => 1,
704             'retentionperiod' => 'PT1M'
705         ]);
706     }
708     /**
709      * Test fetching of purposes as a user without privileges.
710      */
711     public function test_get_purposes_non_dpo_user() {
712         $pleb = $this->getDataGenerator()->create_user();
713         $this->setAdminUser();
714         api::create_purpose((object)[
715             'name' => 'bbb',
716             'description' => '<b>yeah</b>',
717             'descriptionformat' => 1,
718             'retentionperiod' => 'PT1M',
719             'lawfulbases' => 'gdpr_art_6_1_a'
720         ]);
722         $this->setUser($pleb);
723         $this->expectException(required_capability_exception::class);
724         api::get_purposes();
725     }
727     /**
728      * Test updating of purpose as a user without privileges.
729      */
730     public function test_update_purposes_non_dpo_user() {
731         $pleb = $this->getDataGenerator()->create_user();
732         $this->setAdminUser();
733         $purpose = api::create_purpose((object)[
734             'name' => 'bbb',
735             'description' => '<b>yeah</b>',
736             'descriptionformat' => 1,
737             'retentionperiod' => 'PT1M',
738             'lawfulbases' => 'gdpr_art_6_1_a'
739         ]);
741         $this->setUser($pleb);
742         $this->expectException(required_capability_exception::class);
743         $purpose->set('retentionperiod', 'PT2M');
744         api::update_purpose($purpose->to_record());
745     }
747     /**
748      * Test purpose deletion as a user without privileges.
749      */
750     public function test_delete_purpose_non_dpo_user() {
751         $pleb = $this->getDataGenerator()->create_user();
752         $this->setAdminUser();
753         $purpose = api::create_purpose((object)[
754             'name' => 'bbb',
755             'description' => '<b>yeah</b>',
756             'descriptionformat' => 1,
757             'retentionperiod' => 'PT1M',
758             'lawfulbases' => 'gdpr_art_6_1_a'
759         ]);
761         $this->setUser($pleb);
762         $this->expectException(required_capability_exception::class);
763         api::delete_purpose($purpose->get('id'));
764     }
766     /**
767      * Test data purposes CRUD actions.
768      *
769      * @return null
770      */
771     public function test_purpose_crud() {
773         $this->setAdminUser();
775         // Add.
776         $purpose = api::create_purpose((object)[
777             'name' => 'bbb',
778             'description' => '<b>yeah</b>',
779             'descriptionformat' => 1,
780             'retentionperiod' => 'PT1M',
781             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
782         ]);
783         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
784         $this->assertEquals('bbb', $purpose->get('name'));
785         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
786         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
788         // Update.
789         $purpose->set('retentionperiod', 'PT2M');
790         $purpose = api::update_purpose($purpose->to_record());
791         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
793         // Retrieve.
794         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
795         $purposes = api::get_purposes();
796         $this->assertCount(2, $purposes);
797         $this->assertEquals('aaa', $purposes[0]->get('name'));
798         $this->assertEquals('bbb', $purposes[1]->get('name'));
800         // Delete.
801         api::delete_purpose($purposes[0]->get('id'));
802         $this->assertCount(1, api::get_purposes());
803         api::delete_purpose($purposes[1]->get('id'));
804         $this->assertCount(0, api::get_purposes());
805     }
807     /**
808      * Test creation of data categories as a user without privileges.
809      */
810     public function test_create_category_non_dpo_user() {
811         $pleb = $this->getDataGenerator()->create_user();
813         $this->setUser($pleb);
814         $this->expectException(required_capability_exception::class);
815         api::create_category((object)[
816             'name' => 'bbb',
817             'description' => '<b>yeah</b>',
818             'descriptionformat' => 1
819         ]);
820     }
822     /**
823      * Test fetching of data categories as a user without privileges.
824      */
825     public function test_get_categories_non_dpo_user() {
826         $pleb = $this->getDataGenerator()->create_user();
828         $this->setAdminUser();
829         api::create_category((object)[
830             'name' => 'bbb',
831             'description' => '<b>yeah</b>',
832             'descriptionformat' => 1
833         ]);
835         // Back to a regular user.
836         $this->setUser($pleb);
837         $this->expectException(required_capability_exception::class);
838         api::get_categories();
839     }
841     /**
842      * Test updating of data category as a user without privileges.
843      */
844     public function test_update_category_non_dpo_user() {
845         $pleb = $this->getDataGenerator()->create_user();
847         $this->setAdminUser();
848         $category = api::create_category((object)[
849             'name' => 'bbb',
850             'description' => '<b>yeah</b>',
851             'descriptionformat' => 1
852         ]);
854         // Back to a regular user.
855         $this->setUser($pleb);
856         $this->expectException(required_capability_exception::class);
857         $category->set('name', 'yeah');
858         api::update_category($category->to_record());
859     }
861     /**
862      * Test deletion of data category as a user without privileges.
863      */
864     public function test_delete_category_non_dpo_user() {
865         $pleb = $this->getDataGenerator()->create_user();
867         $this->setAdminUser();
868         $category = api::create_category((object)[
869             'name' => 'bbb',
870             'description' => '<b>yeah</b>',
871             'descriptionformat' => 1
872         ]);
874         // Back to a regular user.
875         $this->setUser($pleb);
876         $this->expectException(required_capability_exception::class);
877         api::delete_category($category->get('id'));
878         $this->fail('Users shouldn\'t be allowed to manage categories by default');
879     }
881     /**
882      * Test data categories CRUD actions.
883      *
884      * @return null
885      */
886     public function test_category_crud() {
888         $this->setAdminUser();
890         // Add.
891         $category = api::create_category((object)[
892             'name' => 'bbb',
893             'description' => '<b>yeah</b>',
894             'descriptionformat' => 1
895         ]);
896         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
897         $this->assertEquals('bbb', $category->get('name'));
899         // Update.
900         $category->set('name', 'bcd');
901         $category = api::update_category($category->to_record());
902         $this->assertEquals('bcd', $category->get('name'));
904         // Retrieve.
905         $category = api::create_category((object)['name' => 'aaa']);
906         $categories = api::get_categories();
907         $this->assertCount(2, $categories);
908         $this->assertEquals('aaa', $categories[0]->get('name'));
909         $this->assertEquals('bcd', $categories[1]->get('name'));
911         // Delete.
912         api::delete_category($categories[0]->get('id'));
913         $this->assertCount(1, api::get_categories());
914         api::delete_category($categories[1]->get('id'));
915         $this->assertCount(0, api::get_categories());
916     }
918     /**
919      * Test context instances.
920      *
921      * @return null
922      */
923     public function test_context_instances() {
924         global $DB;
926         $this->setAdminUser();
928         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
930         $coursecontext1 = \context_course::instance($courses[0]->id);
931         $coursecontext2 = \context_course::instance($courses[1]->id);
933         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
934             'categoryid' => $categories[0]->get('id')];
935         $contextinstance1 = api::set_context_instance($record1);
937         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
938             'categoryid' => $categories[1]->get('id')];
939         $contextinstance2 = api::set_context_instance($record2);
941         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
943         api::unset_context_instance($contextinstance1);
944         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
946         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
947             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
948         $contextinstance2 = api::set_context_instance($update);
949         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
950     }
952     /**
953      * Test contextlevel.
954      *
955      * @return null
956      */
957     public function test_contextlevel() {
958         global $DB;
960         $this->setAdminUser();
961         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
963         $record = (object)[
964             'purposeid' => $purposes[0]->get('id'),
965             'categoryid' => $categories[0]->get('id'),
966             'contextlevel' => CONTEXT_SYSTEM,
967         ];
968         $contextlevel = api::set_contextlevel($record);
969         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
970         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
971         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
972         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
974         // Now update it.
975         $record->purposeid = $purposes[1]->get('id');
976         $contextlevel = api::set_contextlevel($record);
977         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
978         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
979         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
981         $record->contextlevel = CONTEXT_USER;
982         $contextlevel = api::set_contextlevel($record);
983         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
984     }
986     /**
987      * Test effective context levels purpose and category defaults.
988      *
989      * @return null
990      */
991     public function test_effective_contextlevel_defaults() {
992         $this->setAdminUser();
994         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
996         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
997         $this->assertEquals(false, $purposeid);
998         $this->assertEquals(false, $categoryid);
1000         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1001             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1002         );
1003         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1005         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
1006         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1007         $this->assertEquals(false, $categoryid);
1009         // Course inherits from system if not defined.
1010         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1011         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1012         $this->assertEquals(false, $categoryid);
1014         // Course defined values should have preference.
1015         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1016             \context_helper::get_class_for_level(CONTEXT_COURSE)
1017         );
1018         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1019         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1021         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1022         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1023         $this->assertEquals($categories[0]->get('id'), $categoryid);
1025         // Context level defaults are also allowed to be set to 'inherit'.
1026         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1028         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1029         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1030         $this->assertEquals($categories[0]->get('id'), $categoryid);
1032         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
1033         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1034         $this->assertEquals($categories[0]->get('id'), $categoryid);
1035     }
1037     /**
1038      * Test effective contextlevel return.
1039      *
1040      * @return null
1041      */
1042     public function test_effective_contextlevel() {
1043         $this->setAdminUser();
1045         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1047         // Set the system context level to purpose 1.
1048         $record = (object)[
1049             'contextlevel' => CONTEXT_SYSTEM,
1050             'purposeid' => $purposes[1]->get('id'),
1051             'categoryid' => $categories[1]->get('id'),
1052         ];
1053         api::set_contextlevel($record);
1055         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
1056         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1058         // Value 'not set' will get the default value for the context level. For context level defaults
1059         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
1060         // will be retrieved.
1061         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1062         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1064         // The behaviour forcing an inherit from context system should result in the same effective
1065         // purpose.
1066         $record->purposeid = context_instance::INHERIT;
1067         $record->contextlevel = CONTEXT_USER;
1068         api::set_contextlevel($record);
1069         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1070         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1072         $record->purposeid = $purposes[2]->get('id');
1073         $record->contextlevel = CONTEXT_USER;
1074         api::set_contextlevel($record);
1076         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1077         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
1079         // Only system and user allowed.
1080         $this->expectException(coding_exception::class);
1081         $record->contextlevel = CONTEXT_COURSE;
1082         $record->purposeid = $purposes[1]->get('id');
1083         api::set_contextlevel($record);
1084     }
1086     /**
1087      * Test effective context purposes and categories.
1088      *
1089      * @return null
1090      */
1091     public function test_effective_context() {
1092         $this->setAdminUser();
1094         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1096         // Define system defaults (all context levels below will inherit).
1097         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1098             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1099         );
1100         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1101         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1103         // Define course defaults.
1104         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1105             \context_helper::get_class_for_level(CONTEXT_COURSE)
1106         );
1107         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1108         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
1110         $course0context = \context_course::instance($courses[0]->id);
1111         $course1context = \context_course::instance($courses[1]->id);
1112         $mod0context = \context_module::instance($modules[0]->cmid);
1113         $mod1context = \context_module::instance($modules[1]->cmid);
1115         // Set course instance values.
1116         $record = (object)[
1117             'contextid' => $course0context->id,
1118             'purposeid' => $purposes[1]->get('id'),
1119             'categoryid' => $categories[2]->get('id'),
1120         ];
1121         api::set_context_instance($record);
1122         $category = api::get_effective_context_category($course0context);
1123         $this->assertEquals($record->categoryid, $category->get('id'));
1125         // Module instances get the context level default if nothing specified.
1126         $category = api::get_effective_context_category($mod0context);
1127         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1129         // Module instances get the parent context category if they inherit.
1130         $record->contextid = $mod0context->id;
1131         $record->categoryid = context_instance::INHERIT;
1132         api::set_context_instance($record);
1133         $category = api::get_effective_context_category($mod0context);
1134         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
1136         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
1137         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
1138         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1139         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
1140         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
1142         // Module instances get the parent context category if they inherit; in
1143         // this case the parent context category is not set so it should use the
1144         // context level default (see 'Define course defaults' above).
1145         $record->contextid = $mod1context->id;
1146         $record->categoryid = context_instance::INHERIT;
1147         api::set_context_instance($record);
1148         $category = api::get_effective_context_category($mod1context);
1149         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1151         // User instances use the value set at user context level instead of the user default.
1153         // User defaults to cat 0 and user context level to 1.
1154         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1155             \context_helper::get_class_for_level(CONTEXT_USER)
1156         );
1157         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1158         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1159         $usercontextlevel = (object)[
1160             'contextlevel' => CONTEXT_USER,
1161             'purposeid' => $purposes[1]->get('id'),
1162             'categoryid' => $categories[1]->get('id'),
1163         ];
1164         api::set_contextlevel($usercontextlevel);
1166         $newuser = $this->getDataGenerator()->create_user();
1167         $usercontext = \context_user::instance($newuser->id);
1168         $category = api::get_effective_context_category($usercontext);
1169         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1170     }
1172     /**
1173      * Tests the deletion of expired contexts.
1174      *
1175      * @return null
1176      */
1177     public function test_expired_context_deletion() {
1178         global $DB;
1180         $this->setAdminUser();
1182         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1184         $course0context = \context_course::instance($courses[0]->id);
1185         $course1context = \context_course::instance($courses[1]->id);
1187         $expiredcontext0 = api::create_expired_context($course0context->id);
1188         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1189         $expiredcontext1 = api::create_expired_context($course1context->id);
1190         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
1192         api::delete_expired_context($expiredcontext0->get('id'));
1193         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1194     }
1196     /**
1197      * Tests the status of expired contexts.
1198      *
1199      * @return null
1200      */
1201     public function test_expired_context_status() {
1202         global $DB;
1204         $this->setAdminUser();
1206         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1208         $course0context = \context_course::instance($courses[0]->id);
1210         $expiredcontext = api::create_expired_context($course0context->id);
1212         // Default status.
1213         $this->assertEquals(expired_context::STATUS_EXPIRED, $expiredcontext->get('status'));
1215         api::set_expired_context_status($expiredcontext, expired_context::STATUS_APPROVED);
1216         $this->assertEquals(expired_context::STATUS_APPROVED, $expiredcontext->get('status'));
1217     }
1219     /**
1220      * Creates test purposes and categories.
1221      *
1222      * @return null
1223      */
1224     protected function add_purposes_and_categories() {
1226         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1227         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1228         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1230         $cat1 = api::create_category((object)['name' => 'a']);
1231         $cat2 = api::create_category((object)['name' => 'b']);
1232         $cat3 = api::create_category((object)['name' => 'c']);
1234         $course1 = $this->getDataGenerator()->create_course();
1235         $course2 = $this->getDataGenerator()->create_course();
1237         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1238         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1240         return [
1241             [$purpose1, $purpose2, $purpose3],
1242             [$cat1, $cat2, $cat3],
1243             [$course1, $course2],
1244             [$module1, $module2]
1245         ];
1246     }
1248     /**
1249      * Test that delete requests filter out protected purpose contexts.
1250      */
1251     public function test_add_request_contexts_with_status_delete() {
1252         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1253         $contextids = $data->list->get_contextids();
1255         $this->assertCount(1, $contextids);
1256         $this->assertEquals($data->contexts->unprotected, $contextids);
1257     }
1259     /**
1260      * Test that export requests don't filter out protected purpose contexts.
1261      */
1262     public function test_add_request_contexts_with_status_export() {
1263         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1264         $contextids = $data->list->get_contextids();
1266         $this->assertCount(2, $contextids);
1267         $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1268     }
1270     /**
1271      * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1272      *
1273      * @param       int $type The type of request to create
1274      * @return      \stdClass
1275      */
1276     protected function setup_test_add_request_contexts_with_status($type) {
1277         $this->setAdminUser();
1279         // User under test.
1280         $s1 = $this->getDataGenerator()->create_user();
1282         // Create three sample contexts.
1283         // 1 which should not be returned; and
1284         // 1 which will be returned and is not protected; and
1285         // 1 which will be returned and is protected.
1287         $c1 = $this->getDataGenerator()->create_course();
1288         $c2 = $this->getDataGenerator()->create_course();
1289         $c3 = $this->getDataGenerator()->create_course();
1291         $ctx1 = \context_course::instance($c1->id);
1292         $ctx2 = \context_course::instance($c2->id);
1293         $ctx3 = \context_course::instance($c3->id);
1295         $unprotected = api::create_purpose((object)[
1296             'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1297         $protected = api::create_purpose((object) [
1298             'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1300         $cat1 = api::create_category((object)['name' => 'a']);
1302         // Set the defaults.
1303         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1304             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1305         );
1306         set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1307         set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1309         $contextinstance1 = api::set_context_instance((object) [
1310                 'contextid' => $ctx1->id,
1311                 'purposeid' => $unprotected->get('id'),
1312                 'categoryid' => $cat1->get('id'),
1313             ]);
1315         $contextinstance2 = api::set_context_instance((object) [
1316                 'contextid' => $ctx2->id,
1317                 'purposeid' => $unprotected->get('id'),
1318                 'categoryid' => $cat1->get('id'),
1319             ]);
1321         $contextinstance3 = api::set_context_instance((object) [
1322                 'contextid' => $ctx3->id,
1323                 'purposeid' => $protected->get('id'),
1324                 'categoryid' => $cat1->get('id'),
1325             ]);
1327         $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1328         $contextlist = new \core_privacy\local\request\contextlist();
1329         $contextlist->set_component('tool_dataprivacy');
1330         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1331                 'ctx2' => $ctx2->id,
1332                 'ctx3' => $ctx3->id,
1333             ]);
1335         $collection->add_contextlist($contextlist);
1337         // Create the sample data request.
1338         $datarequest = api::create_data_request($s1->id, $type);
1339         $requestid = $datarequest->get('id');
1341         // Add the full collection with contexts 2, and 3.
1342         api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1344         // Mark it as approved.
1345         api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1347         // Fetch the list.
1348         $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1350         return (object) [
1351             'contexts' => (object) [
1352                 'unused' => [
1353                     $ctx1->id,
1354                 ],
1355                 'used' => [
1356                     $ctx2->id,
1357                     $ctx3->id,
1358                 ],
1359                 'unprotected' => [
1360                     $ctx2->id,
1361                 ],
1362                 'protected' => [
1363                     $ctx3->id,
1364                 ],
1365             ],
1366             'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1367         ];
1368     }