f4a7a66e2f77aafc4abe001200ccf6fe89114527
[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         $generator = new testing_data_generator();
421         $user1 = $generator->create_user();
422         $user2 = $generator->create_user();
423         $user3 = $generator->create_user();
424         $user4 = $generator->create_user();
425         $user5 = $generator->create_user();
426         $users = [$user1, $user2, $user3, $user4, $user5];
427         $completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
428         $completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
430         return [
431             // Own data requests.
432             [$users, $user1, false, $completeonly],
433             // Non-DPO fetching all requets.
434             [$users, $user2, true, $completeonly],
435             // Admin fetching all completed and cancelled requests.
436             [$users, get_admin(), true, $completeandcancelled],
437             // Admin fetching all completed requests.
438             [$users, get_admin(), true, $completeonly],
439             // Guest fetching all requests.
440             [$users, guest_user(), true, $completeonly],
441         ];
442     }
444     /**
445      * Test for api::get_data_requests()
446      *
447      * @dataProvider get_data_requests_provider
448      * @param stdClass[] $users Array of users to create data requests for.
449      * @param stdClass $loggeduser The user logging in.
450      * @param boolean $fetchall Whether to fetch all records.
451      * @param int[] $statuses Status filters.
452      */
453     public function test_get_data_requests($users, $loggeduser, $fetchall, $statuses) {
454         $comment = 'Data %s request comment by user %d';
455         $exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
456         $deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
457         // Make a data requests for the users.
458         foreach ($users as $user) {
459             $this->setUser($user);
460             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
461             api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
462         }
464         // Log in as the target user.
465         $this->setUser($loggeduser);
466         // Get records count based on the filters.
467         $userid = $loggeduser->id;
468         if ($fetchall) {
469             $userid = 0;
470         }
471         $count = api::get_data_requests_count($userid);
472         if (api::is_site_dpo($loggeduser->id)) {
473             // DPOs should see all the requests.
474             $this->assertEquals(count($users) * 2, $count);
475         } else {
476             if (empty($userid)) {
477                 // There should be no data requests for this user available.
478                 $this->assertEquals(0, $count);
479             } else {
480                 // There should be only one (request with pending status).
481                 $this->assertEquals(2, $count);
482             }
483         }
484         // Get data requests.
485         $requests = api::get_data_requests($userid);
486         // The number of requests should match the count.
487         $this->assertCount($count, $requests);
489         // Test filtering by status.
490         if ($count && !empty($statuses)) {
491             $filteredcount = api::get_data_requests_count($userid, $statuses);
492             // There should be none as they are all pending.
493             $this->assertEquals(0, $filteredcount);
494             $filteredrequests = api::get_data_requests($userid, $statuses);
495             $this->assertCount($filteredcount, $filteredrequests);
497             $statuscounts = [];
498             foreach ($statuses as $stat) {
499                 $statuscounts[$stat] = 0;
500             }
501             $numstatus = count($statuses);
502             // Get all requests with status filter and update statuses, randomly.
503             foreach ($requests as $request) {
504                 if (rand(0, 1)) {
505                     continue;
506                 }
508                 if ($numstatus > 1) {
509                     $index = rand(0, $numstatus - 1);
510                     $status = $statuses[$index];
511                 } else {
512                     $status = reset($statuses);
513                 }
514                 $statuscounts[$status]++;
515                 api::update_request_status($request->get('id'), $status);
516             }
517             $total = array_sum($statuscounts);
518             $filteredcount = api::get_data_requests_count($userid, $statuses);
519             $this->assertEquals($total, $filteredcount);
520             $filteredrequests = api::get_data_requests($userid, $statuses);
521             $this->assertCount($filteredcount, $filteredrequests);
522             // Confirm the filtered requests match the status filter(s).
523             foreach ($filteredrequests as $request) {
524                 $this->assertContains($request->get('status'), $statuses);
525             }
527             if ($numstatus > 1) {
528                 // Fetch by individual status to check the numbers match.
529                 foreach ($statuses as $status) {
530                     $filteredcount = api::get_data_requests_count($userid, [$status]);
531                     $this->assertEquals($statuscounts[$status], $filteredcount);
532                     $filteredrequests = api::get_data_requests($userid, [$status]);
533                     $this->assertCount($filteredcount, $filteredrequests);
534                 }
535             }
536         }
537     }
539     /**
540      * Data provider for test_has_ongoing_request.
541      */
542     public function status_provider() {
543         return [
544             [api::DATAREQUEST_STATUS_PENDING, true],
545             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
546             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
547             [api::DATAREQUEST_STATUS_APPROVED, true],
548             [api::DATAREQUEST_STATUS_PROCESSING, true],
549             [api::DATAREQUEST_STATUS_COMPLETE, false],
550             [api::DATAREQUEST_STATUS_CANCELLED, false],
551             [api::DATAREQUEST_STATUS_REJECTED, false],
552         ];
553     }
555     /**
556      * Test for api::has_ongoing_request()
557      *
558      * @dataProvider status_provider
559      * @param int $status The request status.
560      * @param bool $expected The expected result.
561      */
562     public function test_has_ongoing_request($status, $expected) {
563         $generator = new testing_data_generator();
564         $user1 = $generator->create_user();
566         // Make a data request as user 1.
567         $this->setUser($user1);
568         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
569         // Set the status.
570         api::update_request_status($request->get('id'), $status);
572         // Check if this request is ongoing.
573         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
574         $this->assertEquals($expected, $result);
575     }
577     /**
578      * Test for api::is_active()
579      *
580      * @dataProvider status_provider
581      * @param int $status The request status
582      * @param bool $expected The expected result
583      */
584     public function test_is_active($status, $expected) {
585         // Check if this request is ongoing.
586         $result = api::is_active($status);
587         $this->assertEquals($expected, $result);
588     }
590     /**
591      * Test for api::is_site_dpo()
592      */
593     public function test_is_site_dpo() {
594         global $DB;
596         // No configured site DPOs yet.
597         $admin = get_admin();
598         $this->assertTrue(api::is_site_dpo($admin->id));
600         $generator = new testing_data_generator();
601         $dpo = $generator->create_user();
602         $nondpo = $generator->create_user();
604         $context = context_system::instance();
606         // Manager role.
607         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
608         // Give the manager role with the capability to manage data requests.
609         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
610         // Assign u1 as a manager.
611         role_assign($managerroleid, $dpo->id, $context->id);
613         // Map only the manager role to the DPO role.
614         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
616         // User is a DPO.
617         $this->assertTrue(api::is_site_dpo($dpo->id));
618         // User is not a DPO.
619         $this->assertFalse(api::is_site_dpo($nondpo->id));
620     }
622     /**
623      * Data provider function for test_notify_dpo
624      *
625      * @return array
626      */
627     public function notify_dpo_provider() {
628         return [
629             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
630             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
631             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
632             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
633         ];
634     }
636     /**
637      * Test for api::notify_dpo()
638      *
639      * @dataProvider notify_dpo_provider
640      * @param bool $byadmin Whether the admin requests data on behalf of the user
641      * @param int $type The request type
642      * @param string $typestringid The request lang string identifier
643      * @param string $comments The requestor's message to the DPO.
644      */
645     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
646         $generator = new testing_data_generator();
647         $user1 = $generator->create_user();
648         // Let's just use admin as DPO (It's the default if not set).
649         $dpo = get_admin();
650         if ($byadmin) {
651             $this->setAdminUser();
652             $requestedby = $dpo;
653         } else {
654             $this->setUser($user1);
655             $requestedby = $user1;
656         }
658         // Make a data request for user 1.
659         $request = api::create_data_request($user1->id, $type, $comments);
661         $sink = $this->redirectMessages();
662         $messageid = api::notify_dpo($dpo, $request);
663         $this->assertNotFalse($messageid);
664         $messages = $sink->get_messages();
665         $this->assertCount(1, $messages);
666         $message = reset($messages);
668         // Check some of the message properties.
669         $this->assertEquals($requestedby->id, $message->useridfrom);
670         $this->assertEquals($dpo->id, $message->useridto);
671         $typestring = get_string($typestringid, 'tool_dataprivacy');
672         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
673         $this->assertEquals($subject, $message->subject);
674         $this->assertEquals('tool_dataprivacy', $message->component);
675         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
676         $this->assertContains(fullname($dpo), $message->fullmessage);
677         $this->assertContains(fullname($user1), $message->fullmessage);
678     }
680     /**
681      * Test of creating purpose as a user without privileges.
682      */
683     public function test_create_purpose_non_dpo_user() {
684         $pleb = $this->getDataGenerator()->create_user();
686         $this->setUser($pleb);
687         $this->expectException(required_capability_exception::class);
688         api::create_purpose((object)[
689             'name' => 'aaa',
690             'description' => '<b>yeah</b>',
691             'descriptionformat' => 1,
692             'retentionperiod' => 'PT1M'
693         ]);
694     }
696     /**
697      * Test fetching of purposes as a user without privileges.
698      */
699     public function test_get_purposes_non_dpo_user() {
700         $pleb = $this->getDataGenerator()->create_user();
701         $this->setAdminUser();
702         api::create_purpose((object)[
703             'name' => 'bbb',
704             'description' => '<b>yeah</b>',
705             'descriptionformat' => 1,
706             'retentionperiod' => 'PT1M',
707             'lawfulbases' => 'gdpr_art_6_1_a'
708         ]);
710         $this->setUser($pleb);
711         $this->expectException(required_capability_exception::class);
712         api::get_purposes();
713     }
715     /**
716      * Test updating of purpose as a user without privileges.
717      */
718     public function test_update_purposes_non_dpo_user() {
719         $pleb = $this->getDataGenerator()->create_user();
720         $this->setAdminUser();
721         $purpose = api::create_purpose((object)[
722             'name' => 'bbb',
723             'description' => '<b>yeah</b>',
724             'descriptionformat' => 1,
725             'retentionperiod' => 'PT1M',
726             'lawfulbases' => 'gdpr_art_6_1_a'
727         ]);
729         $this->setUser($pleb);
730         $this->expectException(required_capability_exception::class);
731         $purpose->set('retentionperiod', 'PT2M');
732         api::update_purpose($purpose->to_record());
733     }
735     /**
736      * Test purpose deletion as a user without privileges.
737      */
738     public function test_delete_purpose_non_dpo_user() {
739         $pleb = $this->getDataGenerator()->create_user();
740         $this->setAdminUser();
741         $purpose = api::create_purpose((object)[
742             'name' => 'bbb',
743             'description' => '<b>yeah</b>',
744             'descriptionformat' => 1,
745             'retentionperiod' => 'PT1M',
746             'lawfulbases' => 'gdpr_art_6_1_a'
747         ]);
749         $this->setUser($pleb);
750         $this->expectException(required_capability_exception::class);
751         api::delete_purpose($purpose->get('id'));
752     }
754     /**
755      * Test data purposes CRUD actions.
756      *
757      * @return null
758      */
759     public function test_purpose_crud() {
761         $this->setAdminUser();
763         // Add.
764         $purpose = api::create_purpose((object)[
765             'name' => 'bbb',
766             'description' => '<b>yeah</b>',
767             'descriptionformat' => 1,
768             'retentionperiod' => 'PT1M',
769             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
770         ]);
771         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
772         $this->assertEquals('bbb', $purpose->get('name'));
773         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
774         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
776         // Update.
777         $purpose->set('retentionperiod', 'PT2M');
778         $purpose = api::update_purpose($purpose->to_record());
779         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
781         // Retrieve.
782         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
783         $purposes = api::get_purposes();
784         $this->assertCount(2, $purposes);
785         $this->assertEquals('aaa', $purposes[0]->get('name'));
786         $this->assertEquals('bbb', $purposes[1]->get('name'));
788         // Delete.
789         api::delete_purpose($purposes[0]->get('id'));
790         $this->assertCount(1, api::get_purposes());
791         api::delete_purpose($purposes[1]->get('id'));
792         $this->assertCount(0, api::get_purposes());
793     }
795     /**
796      * Test creation of data categories as a user without privileges.
797      */
798     public function test_create_category_non_dpo_user() {
799         $pleb = $this->getDataGenerator()->create_user();
801         $this->setUser($pleb);
802         $this->expectException(required_capability_exception::class);
803         api::create_category((object)[
804             'name' => 'bbb',
805             'description' => '<b>yeah</b>',
806             'descriptionformat' => 1
807         ]);
808     }
810     /**
811      * Test fetching of data categories as a user without privileges.
812      */
813     public function test_get_categories_non_dpo_user() {
814         $pleb = $this->getDataGenerator()->create_user();
816         $this->setAdminUser();
817         api::create_category((object)[
818             'name' => 'bbb',
819             'description' => '<b>yeah</b>',
820             'descriptionformat' => 1
821         ]);
823         // Back to a regular user.
824         $this->setUser($pleb);
825         $this->expectException(required_capability_exception::class);
826         api::get_categories();
827     }
829     /**
830      * Test updating of data category as a user without privileges.
831      */
832     public function test_update_category_non_dpo_user() {
833         $pleb = $this->getDataGenerator()->create_user();
835         $this->setAdminUser();
836         $category = api::create_category((object)[
837             'name' => 'bbb',
838             'description' => '<b>yeah</b>',
839             'descriptionformat' => 1
840         ]);
842         // Back to a regular user.
843         $this->setUser($pleb);
844         $this->expectException(required_capability_exception::class);
845         $category->set('name', 'yeah');
846         api::update_category($category->to_record());
847     }
849     /**
850      * Test deletion of data category as a user without privileges.
851      */
852     public function test_delete_category_non_dpo_user() {
853         $pleb = $this->getDataGenerator()->create_user();
855         $this->setAdminUser();
856         $category = api::create_category((object)[
857             'name' => 'bbb',
858             'description' => '<b>yeah</b>',
859             'descriptionformat' => 1
860         ]);
862         // Back to a regular user.
863         $this->setUser($pleb);
864         $this->expectException(required_capability_exception::class);
865         api::delete_category($category->get('id'));
866         $this->fail('Users shouldn\'t be allowed to manage categories by default');
867     }
869     /**
870      * Test data categories CRUD actions.
871      *
872      * @return null
873      */
874     public function test_category_crud() {
876         $this->setAdminUser();
878         // Add.
879         $category = api::create_category((object)[
880             'name' => 'bbb',
881             'description' => '<b>yeah</b>',
882             'descriptionformat' => 1
883         ]);
884         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
885         $this->assertEquals('bbb', $category->get('name'));
887         // Update.
888         $category->set('name', 'bcd');
889         $category = api::update_category($category->to_record());
890         $this->assertEquals('bcd', $category->get('name'));
892         // Retrieve.
893         $category = api::create_category((object)['name' => 'aaa']);
894         $categories = api::get_categories();
895         $this->assertCount(2, $categories);
896         $this->assertEquals('aaa', $categories[0]->get('name'));
897         $this->assertEquals('bcd', $categories[1]->get('name'));
899         // Delete.
900         api::delete_category($categories[0]->get('id'));
901         $this->assertCount(1, api::get_categories());
902         api::delete_category($categories[1]->get('id'));
903         $this->assertCount(0, api::get_categories());
904     }
906     /**
907      * Test context instances.
908      *
909      * @return null
910      */
911     public function test_context_instances() {
912         global $DB;
914         $this->setAdminUser();
916         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
918         $coursecontext1 = \context_course::instance($courses[0]->id);
919         $coursecontext2 = \context_course::instance($courses[1]->id);
921         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
922             'categoryid' => $categories[0]->get('id')];
923         $contextinstance1 = api::set_context_instance($record1);
925         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
926             'categoryid' => $categories[1]->get('id')];
927         $contextinstance2 = api::set_context_instance($record2);
929         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
931         api::unset_context_instance($contextinstance1);
932         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
934         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
935             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
936         $contextinstance2 = api::set_context_instance($update);
937         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
938     }
940     /**
941      * Test contextlevel.
942      *
943      * @return null
944      */
945     public function test_contextlevel() {
946         global $DB;
948         $this->setAdminUser();
949         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
951         $record = (object)[
952             'purposeid' => $purposes[0]->get('id'),
953             'categoryid' => $categories[0]->get('id'),
954             'contextlevel' => CONTEXT_SYSTEM,
955         ];
956         $contextlevel = api::set_contextlevel($record);
957         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
958         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
959         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
960         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
962         // Now update it.
963         $record->purposeid = $purposes[1]->get('id');
964         $contextlevel = api::set_contextlevel($record);
965         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
966         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
967         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
969         $record->contextlevel = CONTEXT_USER;
970         $contextlevel = api::set_contextlevel($record);
971         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
972     }
974     /**
975      * Test effective context levels purpose and category defaults.
976      *
977      * @return null
978      */
979     public function test_effective_contextlevel_defaults() {
980         $this->setAdminUser();
982         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
984         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
985         $this->assertEquals(false, $purposeid);
986         $this->assertEquals(false, $categoryid);
988         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
989             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
990         );
991         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
993         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
994         $this->assertEquals($purposes[0]->get('id'), $purposeid);
995         $this->assertEquals(false, $categoryid);
997         // Course inherits from system if not defined.
998         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
999         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1000         $this->assertEquals(false, $categoryid);
1002         // Course defined values should have preference.
1003         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1004             \context_helper::get_class_for_level(CONTEXT_COURSE)
1005         );
1006         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1007         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1009         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1010         $this->assertEquals($purposes[1]->get('id'), $purposeid);
1011         $this->assertEquals($categories[0]->get('id'), $categoryid);
1013         // Context level defaults are also allowed to be set to 'inherit'.
1014         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
1016         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
1017         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1018         $this->assertEquals($categories[0]->get('id'), $categoryid);
1020         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
1021         $this->assertEquals($purposes[0]->get('id'), $purposeid);
1022         $this->assertEquals($categories[0]->get('id'), $categoryid);
1023     }
1025     /**
1026      * Test effective contextlevel return.
1027      *
1028      * @return null
1029      */
1030     public function test_effective_contextlevel() {
1031         $this->setAdminUser();
1033         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1035         // Set the system context level to purpose 1.
1036         $record = (object)[
1037             'contextlevel' => CONTEXT_SYSTEM,
1038             'purposeid' => $purposes[1]->get('id'),
1039             'categoryid' => $categories[1]->get('id'),
1040         ];
1041         api::set_contextlevel($record);
1043         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
1044         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1046         // Value 'not set' will get the default value for the context level. For context level defaults
1047         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
1048         // will be retrieved.
1049         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1050         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1052         // The behaviour forcing an inherit from context system should result in the same effective
1053         // purpose.
1054         $record->purposeid = context_instance::INHERIT;
1055         $record->contextlevel = CONTEXT_USER;
1056         api::set_contextlevel($record);
1057         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1058         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
1060         $record->purposeid = $purposes[2]->get('id');
1061         $record->contextlevel = CONTEXT_USER;
1062         api::set_contextlevel($record);
1064         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
1065         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
1067         // Only system and user allowed.
1068         $this->expectException(coding_exception::class);
1069         $record->contextlevel = CONTEXT_COURSE;
1070         $record->purposeid = $purposes[1]->get('id');
1071         api::set_contextlevel($record);
1072     }
1074     /**
1075      * Test effective context purposes and categories.
1076      *
1077      * @return null
1078      */
1079     public function test_effective_context() {
1080         $this->setAdminUser();
1082         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1084         // Define system defaults (all context levels below will inherit).
1085         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1086             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1087         );
1088         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1089         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1091         // Define course defaults.
1092         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1093             \context_helper::get_class_for_level(CONTEXT_COURSE)
1094         );
1095         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
1096         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
1098         $course0context = \context_course::instance($courses[0]->id);
1099         $course1context = \context_course::instance($courses[1]->id);
1100         $mod0context = \context_module::instance($modules[0]->cmid);
1101         $mod1context = \context_module::instance($modules[1]->cmid);
1103         // Set course instance values.
1104         $record = (object)[
1105             'contextid' => $course0context->id,
1106             'purposeid' => $purposes[1]->get('id'),
1107             'categoryid' => $categories[2]->get('id'),
1108         ];
1109         api::set_context_instance($record);
1110         $category = api::get_effective_context_category($course0context);
1111         $this->assertEquals($record->categoryid, $category->get('id'));
1113         // Module instances get the context level default if nothing specified.
1114         $category = api::get_effective_context_category($mod0context);
1115         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1117         // Module instances get the parent context category if they inherit.
1118         $record->contextid = $mod0context->id;
1119         $record->categoryid = context_instance::INHERIT;
1120         api::set_context_instance($record);
1121         $category = api::get_effective_context_category($mod0context);
1122         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
1124         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
1125         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
1126         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1127         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
1128         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
1130         // Module instances get the parent context category if they inherit; in
1131         // this case the parent context category is not set so it should use the
1132         // context level default (see 'Define course defaults' above).
1133         $record->contextid = $mod1context->id;
1134         $record->categoryid = context_instance::INHERIT;
1135         api::set_context_instance($record);
1136         $category = api::get_effective_context_category($mod1context);
1137         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1139         // User instances use the value set at user context level instead of the user default.
1141         // User defaults to cat 0 and user context level to 1.
1142         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1143             \context_helper::get_class_for_level(CONTEXT_USER)
1144         );
1145         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
1146         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
1147         $usercontextlevel = (object)[
1148             'contextlevel' => CONTEXT_USER,
1149             'purposeid' => $purposes[1]->get('id'),
1150             'categoryid' => $categories[1]->get('id'),
1151         ];
1152         api::set_contextlevel($usercontextlevel);
1154         $newuser = $this->getDataGenerator()->create_user();
1155         $usercontext = \context_user::instance($newuser->id);
1156         $category = api::get_effective_context_category($usercontext);
1157         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
1158     }
1160     /**
1161      * Tests the deletion of expired contexts.
1162      *
1163      * @return null
1164      */
1165     public function test_expired_context_deletion() {
1166         global $DB;
1168         $this->setAdminUser();
1170         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1172         $course0context = \context_course::instance($courses[0]->id);
1173         $course1context = \context_course::instance($courses[1]->id);
1175         $expiredcontext0 = api::create_expired_context($course0context->id);
1176         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1177         $expiredcontext1 = api::create_expired_context($course1context->id);
1178         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
1180         api::delete_expired_context($expiredcontext0->get('id'));
1181         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1182     }
1184     /**
1185      * Tests the status of expired contexts.
1186      *
1187      * @return null
1188      */
1189     public function test_expired_context_status() {
1190         global $DB;
1192         $this->setAdminUser();
1194         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1196         $course0context = \context_course::instance($courses[0]->id);
1198         $expiredcontext = api::create_expired_context($course0context->id);
1200         // Default status.
1201         $this->assertEquals(expired_context::STATUS_EXPIRED, $expiredcontext->get('status'));
1203         api::set_expired_context_status($expiredcontext, expired_context::STATUS_APPROVED);
1204         $this->assertEquals(expired_context::STATUS_APPROVED, $expiredcontext->get('status'));
1205     }
1207     /**
1208      * Creates test purposes and categories.
1209      *
1210      * @return null
1211      */
1212     protected function add_purposes_and_categories() {
1214         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1215         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1216         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1218         $cat1 = api::create_category((object)['name' => 'a']);
1219         $cat2 = api::create_category((object)['name' => 'b']);
1220         $cat3 = api::create_category((object)['name' => 'c']);
1222         $course1 = $this->getDataGenerator()->create_course();
1223         $course2 = $this->getDataGenerator()->create_course();
1225         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1226         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1228         return [
1229             [$purpose1, $purpose2, $purpose3],
1230             [$cat1, $cat2, $cat3],
1231             [$course1, $course2],
1232             [$module1, $module2]
1233         ];
1234     }
1236     /**
1237      * Test that delete requests filter out protected purpose contexts.
1238      */
1239     public function test_add_request_contexts_with_status_delete() {
1240         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1241         $contextids = $data->list->get_contextids();
1243         $this->assertCount(1, $contextids);
1244         $this->assertEquals($data->contexts->unprotected, $contextids);
1245     }
1247     /**
1248      * Test that export requests don't filter out protected purpose contexts.
1249      */
1250     public function test_add_request_contexts_with_status_export() {
1251         $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1252         $contextids = $data->list->get_contextids();
1254         $this->assertCount(2, $contextids);
1255         $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1256     }
1258     /**
1259      * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1260      *
1261      * @param       int $type The type of request to create
1262      * @return      \stdClass
1263      */
1264     protected function setup_test_add_request_contexts_with_status($type) {
1265         $this->setAdminUser();
1267         // User under test.
1268         $s1 = $this->getDataGenerator()->create_user();
1270         // Create three sample contexts.
1271         // 1 which should not be returned; and
1272         // 1 which will be returned and is not protected; and
1273         // 1 which will be returned and is protected.
1275         $c1 = $this->getDataGenerator()->create_course();
1276         $c2 = $this->getDataGenerator()->create_course();
1277         $c3 = $this->getDataGenerator()->create_course();
1279         $ctx1 = \context_course::instance($c1->id);
1280         $ctx2 = \context_course::instance($c2->id);
1281         $ctx3 = \context_course::instance($c3->id);
1283         $unprotected = api::create_purpose((object)[
1284             'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1285         $protected = api::create_purpose((object) [
1286             'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1288         $cat1 = api::create_category((object)['name' => 'a']);
1290         // Set the defaults.
1291         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1292             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1293         );
1294         set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1295         set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1297         $contextinstance1 = api::set_context_instance((object) [
1298                 'contextid' => $ctx1->id,
1299                 'purposeid' => $unprotected->get('id'),
1300                 'categoryid' => $cat1->get('id'),
1301             ]);
1303         $contextinstance2 = api::set_context_instance((object) [
1304                 'contextid' => $ctx2->id,
1305                 'purposeid' => $unprotected->get('id'),
1306                 'categoryid' => $cat1->get('id'),
1307             ]);
1309         $contextinstance3 = api::set_context_instance((object) [
1310                 'contextid' => $ctx3->id,
1311                 'purposeid' => $protected->get('id'),
1312                 'categoryid' => $cat1->get('id'),
1313             ]);
1315         $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1316         $contextlist = new \core_privacy\local\request\contextlist();
1317         $contextlist->set_component('tool_dataprivacy');
1318         $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1319                 'ctx2' => $ctx2->id,
1320                 'ctx3' => $ctx3->id,
1321             ]);
1323         $collection->add_contextlist($contextlist);
1325         // Create the sample data request.
1326         $datarequest = api::create_data_request($s1->id, $type);
1327         $requestid = $datarequest->get('id');
1329         // Add the full collection with contexts 2, and 3.
1330         api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1332         // Mark it as approved.
1333         api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1335         // Fetch the list.
1336         $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1338         return (object) [
1339             'contexts' => (object) [
1340                 'unused' => [
1341                     $ctx1->id,
1342                 ],
1343                 'used' => [
1344                     $ctx2->id,
1345                     $ctx3->id,
1346                 ],
1347                 'unprotected' => [
1348                     $ctx2->id,
1349                 ],
1350                 'protected' => [
1351                     $ctx3->id,
1352                 ],
1353             ],
1354             'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1355         ];
1356     }