MDL-62277 Theme boost: add badge criteria layout
[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\task\initiate_data_request_task;
33 use tool_dataprivacy\task\process_data_request_task;
35 defined('MOODLE_INTERNAL') || die();
36 global $CFG;
38 /**
39  * API tests.
40  *
41  * @package    tool_dataprivacy
42  * @copyright  2018 Jun Pataleta
43  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44  */
45 class tool_dataprivacy_api_testcase extends advanced_testcase {
47     /**
48      * setUp.
49      */
50     public function setUp() {
51         $this->resetAfterTest();
52     }
54     /**
55      * Test for api::update_request_status().
56      */
57     public function test_update_request_status() {
58         $generator = new testing_data_generator();
59         $s1 = $generator->create_user();
61         // Create the sample data request.
62         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
64         $requestid = $datarequest->get('id');
66         // Update with a valid status.
67         $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
68         $this->assertTrue($result);
70         // Fetch the request record again.
71         $datarequest = new data_request($requestid);
72         $this->assertEquals(api::DATAREQUEST_STATUS_COMPLETE, $datarequest->get('status'));
74         // Update with an invalid status.
75         $this->expectException(invalid_persistent_exception::class);
76         api::update_request_status($requestid, -1);
77     }
79     /**
80      * Test for api::get_site_dpos() when there are no users with the DPO role.
81      */
82     public function test_get_site_dpos_no_dpos() {
83         $admin = get_admin();
85         $dpos = api::get_site_dpos();
86         $this->assertCount(1, $dpos);
87         $dpo = reset($dpos);
88         $this->assertEquals($admin->id, $dpo->id);
89     }
91     /**
92      * Test for api::get_site_dpos() when there are no users with the DPO role.
93      */
94     public function test_get_site_dpos() {
95         global $DB;
96         $generator = new testing_data_generator();
97         $u1 = $generator->create_user();
98         $u2 = $generator->create_user();
100         $context = context_system::instance();
102         // Give the manager role with the capability to manage data requests.
103         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
104         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
105         // Assign u1 as a manager.
106         role_assign($managerroleid, $u1->id, $context->id);
108         // Give the editing teacher role with the capability to manage data requests.
109         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
110         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
111         // Assign u1 as an editing teacher as well.
112         role_assign($editingteacherroleid, $u1->id, $context->id);
113         // Assign u2 as an editing teacher.
114         role_assign($editingteacherroleid, $u2->id, $context->id);
116         // Only map the manager role to the DPO role.
117         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
119         $dpos = api::get_site_dpos();
120         $this->assertCount(1, $dpos);
121         $dpo = reset($dpos);
122         $this->assertEquals($u1->id, $dpo->id);
123     }
125     /**
126      * Test for api::approve_data_request().
127      */
128     public function test_approve_data_request() {
129         global $DB;
131         $generator = new testing_data_generator();
132         $s1 = $generator->create_user();
133         $u1 = $generator->create_user();
135         $context = context_system::instance();
137         // Manager role.
138         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
139         // Give the manager role with the capability to manage data requests.
140         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
141         // Assign u1 as a manager.
142         role_assign($managerroleid, $u1->id, $context->id);
144         // Map the manager role to the DPO role.
145         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
147         // Create the sample data request.
148         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
149         $requestid = $datarequest->get('id');
151         // Make this ready for approval.
152         api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
154         $this->setUser($u1);
155         $result = api::approve_data_request($requestid);
156         $this->assertTrue($result);
157         $datarequest = new data_request($requestid);
158         $this->assertEquals($u1->id, $datarequest->get('dpo'));
159         $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
161         // Test adhoc task creation.
162         $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
163         $this->assertCount(1, $adhoctasks);
164     }
166     /**
167      * Test for api::approve_data_request() with the request not yet waiting for approval.
168      */
169     public function test_approve_data_request_not_yet_ready() {
170         global $DB;
172         $generator = new testing_data_generator();
173         $s1 = $generator->create_user();
174         $u1 = $generator->create_user();
176         $context = context_system::instance();
178         // Manager role.
179         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
180         // Give the manager role with the capability to manage data requests.
181         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
182         // Assign u1 as a manager.
183         role_assign($managerroleid, $u1->id, $context->id);
185         // Map the manager role to the DPO role.
186         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
188         // Create the sample data request.
189         $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
190         $requestid = $datarequest->get('id');
192         $this->setUser($u1);
193         $this->expectException(moodle_exception::class);
194         api::approve_data_request($requestid);
195     }
197     /**
198      * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
199      */
200     public function test_approve_data_request_non_dpo_user() {
201         $generator = new testing_data_generator();
202         $student = $generator->create_user();
203         $teacher = $generator->create_user();
205         // Create the sample data request.
206         $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
208         $requestid = $datarequest->get('id');
210         // Login as a user without DPO role.
211         $this->setUser($teacher);
212         $this->expectException(required_capability_exception::class);
213         api::approve_data_request($requestid);
214     }
216     /**
217      * Test for api::can_contact_dpo()
218      */
219     public function test_can_contact_dpo() {
220         // Default ('contactdataprotectionofficer' is disabled by default).
221         $this->assertFalse(api::can_contact_dpo());
223         // Enable.
224         set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
225         $this->assertTrue(api::can_contact_dpo());
227         // Disable again.
228         set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
229         $this->assertFalse(api::can_contact_dpo());
230     }
232     /**
233      * Test for api::can_manage_data_requests()
234      */
235     public function test_can_manage_data_requests() {
236         global $DB;
238         // No configured site DPOs yet.
239         $admin = get_admin();
240         $this->assertTrue(api::can_manage_data_requests($admin->id));
242         $generator = new testing_data_generator();
243         $dpo = $generator->create_user();
244         $nondpocapable = $generator->create_user();
245         $nondpoincapable = $generator->create_user();
247         $context = context_system::instance();
249         // Manager role.
250         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
251         // Give the manager role with the capability to manage data requests.
252         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
253         // Assign u1 as a manager.
254         role_assign($managerroleid, $dpo->id, $context->id);
256         // Editing teacher role.
257         $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
258         // Give the editing teacher role with the capability to manage data requests.
259         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
260         // Assign u2 as an editing teacher.
261         role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
263         // Map only the manager role to the DPO role.
264         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
266         // User with capability and has DPO role.
267         $this->assertTrue(api::can_manage_data_requests($dpo->id));
268         // User with capability but has no DPO role.
269         $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
270         // User without the capability and has no DPO role.
271         $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
272     }
274     /**
275      * Test for api::create_data_request()
276      */
277     public function test_create_data_request() {
278         $generator = new testing_data_generator();
279         $user = $generator->create_user();
280         $comment = 'sample comment';
282         // Login as user.
283         $this->setUser($user->id);
285         // Test data request creation.
286         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
287         $this->assertEquals($user->id, $datarequest->get('userid'));
288         $this->assertEquals($user->id, $datarequest->get('requestedby'));
289         $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
290         $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
291         $this->assertEquals($comment, $datarequest->get('comments'));
293         // Test adhoc task creation.
294         $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
295         $this->assertCount(1, $adhoctasks);
296     }
298     /**
299      * Test for api::deny_data_request()
300      */
301     public function test_deny_data_request() {
302         $generator = new testing_data_generator();
303         $user = $generator->create_user();
304         $comment = 'sample comment';
306         // Login as user.
307         $this->setUser($user->id);
309         // Test data request creation.
310         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
312         // Login as the admin (default DPO when no one is set).
313         $this->setAdminUser();
315         // Make this ready for approval.
316         api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
318         // Deny the data request.
319         $result = api::deny_data_request($datarequest->get('id'));
320         $this->assertTrue($result);
321     }
323     /**
324      * Test for api::deny_data_request()
325      */
326     public function test_deny_data_request_without_permissions() {
327         $generator = new testing_data_generator();
328         $user = $generator->create_user();
329         $comment = 'sample comment';
331         // Login as user.
332         $this->setUser($user->id);
334         // Test data request creation.
335         $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
337         // Login as a non-DPO user and try to call deny_data_request.
338         $user2 = $generator->create_user();
339         $this->setUser($user2);
340         $this->expectException(required_capability_exception::class);
341         api::deny_data_request($datarequest->get('id'));
342     }
344     /**
345      * Test for api::get_data_requests()
346      */
347     public function test_get_data_requests() {
348         $generator = new testing_data_generator();
349         $user1 = $generator->create_user();
350         $user2 = $generator->create_user();
351         $comment = 'sample comment';
353         // Make a data request as user 1.
354         $d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
355         // Make a data request as user 2.
356         $d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
358         // Fetching data requests of specific users.
359         $requests = api::get_data_requests($user1->id);
360         $this->assertCount(1, $requests);
361         $datarequest = reset($requests);
362         $this->assertEquals($d1->to_record(), $datarequest->to_record());
364         $requests = api::get_data_requests($user2->id);
365         $this->assertCount(1, $requests);
366         $datarequest = reset($requests);
367         $this->assertEquals($d2->to_record(), $datarequest->to_record());
369         // Fetching data requests of all users.
370         // As guest.
371         $this->setGuestUser();
372         $requests = api::get_data_requests();
373         $this->assertEmpty($requests);
375         // As DPO (admin in this case, which is default if no site DPOs are set).
376         $this->setAdminUser();
377         $requests = api::get_data_requests();
378         $this->assertCount(2, $requests);
379     }
381     /**
382      * Data provider for test_has_ongoing_request.
383      */
384     public function status_provider() {
385         return [
386             [api::DATAREQUEST_STATUS_PENDING, true],
387             [api::DATAREQUEST_STATUS_PREPROCESSING, true],
388             [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
389             [api::DATAREQUEST_STATUS_APPROVED, true],
390             [api::DATAREQUEST_STATUS_PROCESSING, true],
391             [api::DATAREQUEST_STATUS_COMPLETE, false],
392             [api::DATAREQUEST_STATUS_CANCELLED, false],
393             [api::DATAREQUEST_STATUS_REJECTED, false],
394         ];
395     }
397     /**
398      * Test for api::has_ongoing_request()
399      *
400      * @dataProvider status_provider
401      * @param int $status The request status.
402      * @param bool $expected The expected result.
403      */
404     public function test_has_ongoing_request($status, $expected) {
405         $generator = new testing_data_generator();
406         $user1 = $generator->create_user();
408         // Make a data request as user 1.
409         $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
410         // Set the status.
411         api::update_request_status($request->get('id'), $status);
413         // Check if this request is ongoing.
414         $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
415         $this->assertEquals($expected, $result);
416     }
418     /**
419      * Test for api::is_active()
420      *
421      * @dataProvider status_provider
422      * @param int $status The request status
423      * @param bool $expected The expected result
424      */
425     public function test_is_active($status, $expected) {
426         // Check if this request is ongoing.
427         $result = api::is_active($status);
428         $this->assertEquals($expected, $result);
429     }
431     /**
432      * Test for api::is_site_dpo()
433      */
434     public function test_is_site_dpo() {
435         global $DB;
437         // No configured site DPOs yet.
438         $admin = get_admin();
439         $this->assertTrue(api::is_site_dpo($admin->id));
441         $generator = new testing_data_generator();
442         $dpo = $generator->create_user();
443         $nondpo = $generator->create_user();
445         $context = context_system::instance();
447         // Manager role.
448         $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
449         // Give the manager role with the capability to manage data requests.
450         assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
451         // Assign u1 as a manager.
452         role_assign($managerroleid, $dpo->id, $context->id);
454         // Map only the manager role to the DPO role.
455         set_config('dporoles', $managerroleid, 'tool_dataprivacy');
457         // User is a DPO.
458         $this->assertTrue(api::is_site_dpo($dpo->id));
459         // User is not a DPO.
460         $this->assertFalse(api::is_site_dpo($nondpo->id));
461     }
463     /**
464      * Data provider function for test_notify_dpo
465      *
466      * @return array
467      */
468     public function notify_dpo_provider() {
469         return [
470             [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
471             [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
472             [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
473             [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
474         ];
475     }
477     /**
478      * Test for api::notify_dpo()
479      *
480      * @dataProvider notify_dpo_provider
481      * @param bool $byadmin Whether the admin requests data on behalf of the user
482      * @param int $type The request type
483      * @param string $typestringid The request lang string identifier
484      * @param string $comments The requestor's message to the DPO.
485      */
486     public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
487         $generator = new testing_data_generator();
488         $user1 = $generator->create_user();
489         // Let's just use admin as DPO (It's the default if not set).
490         $dpo = get_admin();
491         if ($byadmin) {
492             $this->setAdminUser();
493             $requestedby = $dpo;
494         } else {
495             $this->setUser($user1);
496             $requestedby = $user1;
497         }
499         // Make a data request for user 1.
500         $request = api::create_data_request($user1->id, $type, $comments);
502         $sink = $this->redirectMessages();
503         $messageid = api::notify_dpo($dpo, $request);
504         $this->assertNotFalse($messageid);
505         $messages = $sink->get_messages();
506         $this->assertCount(1, $messages);
507         $message = reset($messages);
509         // Check some of the message properties.
510         $this->assertEquals($requestedby->id, $message->useridfrom);
511         $this->assertEquals($dpo->id, $message->useridto);
512         $typestring = get_string($typestringid, 'tool_dataprivacy');
513         $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
514         $this->assertEquals($subject, $message->subject);
515         $this->assertEquals('tool_dataprivacy', $message->component);
516         $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
517         $this->assertContains(fullname($dpo), $message->fullmessage);
518         $this->assertContains(fullname($user1), $message->fullmessage);
519     }
521     /**
522      * Test of creating purpose as a user without privileges.
523      */
524     public function test_create_purpose_non_dpo_user() {
525         $pleb = $this->getDataGenerator()->create_user();
527         $this->setUser($pleb);
528         $this->expectException(required_capability_exception::class);
529         api::create_purpose((object)[
530             'name' => 'aaa',
531             'description' => '<b>yeah</b>',
532             'descriptionformat' => 1,
533             'retentionperiod' => 'PT1M'
534         ]);
535     }
537     /**
538      * Test fetching of purposes as a user without privileges.
539      */
540     public function test_get_purposes_non_dpo_user() {
541         $pleb = $this->getDataGenerator()->create_user();
542         $this->setAdminUser();
543         api::create_purpose((object)[
544             'name' => 'bbb',
545             'description' => '<b>yeah</b>',
546             'descriptionformat' => 1,
547             'retentionperiod' => 'PT1M',
548             'lawfulbases' => 'gdpr_art_6_1_a'
549         ]);
551         $this->setUser($pleb);
552         $this->expectException(required_capability_exception::class);
553         api::get_purposes();
554     }
556     /**
557      * Test updating of purpose as a user without privileges.
558      */
559     public function test_update_purposes_non_dpo_user() {
560         $pleb = $this->getDataGenerator()->create_user();
561         $this->setAdminUser();
562         $purpose = api::create_purpose((object)[
563             'name' => 'bbb',
564             'description' => '<b>yeah</b>',
565             'descriptionformat' => 1,
566             'retentionperiod' => 'PT1M',
567             'lawfulbases' => 'gdpr_art_6_1_a'
568         ]);
570         $this->setUser($pleb);
571         $this->expectException(required_capability_exception::class);
572         $purpose->set('retentionperiod', 'PT2M');
573         api::update_purpose($purpose->to_record());
574     }
576     /**
577      * Test purpose deletion as a user without privileges.
578      */
579     public function test_delete_purpose_non_dpo_user() {
580         $pleb = $this->getDataGenerator()->create_user();
581         $this->setAdminUser();
582         $purpose = api::create_purpose((object)[
583             'name' => 'bbb',
584             'description' => '<b>yeah</b>',
585             'descriptionformat' => 1,
586             'retentionperiod' => 'PT1M',
587             'lawfulbases' => 'gdpr_art_6_1_a'
588         ]);
590         $this->setUser($pleb);
591         $this->expectException(required_capability_exception::class);
592         api::delete_purpose($purpose->get('id'));
593     }
595     /**
596      * Test data purposes CRUD actions.
597      *
598      * @return null
599      */
600     public function test_purpose_crud() {
602         $this->setAdminUser();
604         // Add.
605         $purpose = api::create_purpose((object)[
606             'name' => 'bbb',
607             'description' => '<b>yeah</b>',
608             'descriptionformat' => 1,
609             'retentionperiod' => 'PT1M',
610             'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
611         ]);
612         $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
613         $this->assertEquals('bbb', $purpose->get('name'));
614         $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
615         $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
617         // Update.
618         $purpose->set('retentionperiod', 'PT2M');
619         $purpose = api::update_purpose($purpose->to_record());
620         $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
622         // Retrieve.
623         $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
624         $purposes = api::get_purposes();
625         $this->assertCount(2, $purposes);
626         $this->assertEquals('aaa', $purposes[0]->get('name'));
627         $this->assertEquals('bbb', $purposes[1]->get('name'));
629         // Delete.
630         api::delete_purpose($purposes[0]->get('id'));
631         $this->assertCount(1, api::get_purposes());
632         api::delete_purpose($purposes[1]->get('id'));
633         $this->assertCount(0, api::get_purposes());
634     }
636     /**
637      * Test creation of data categories as a user without privileges.
638      */
639     public function test_create_category_non_dpo_user() {
640         $pleb = $this->getDataGenerator()->create_user();
642         $this->setUser($pleb);
643         $this->expectException(required_capability_exception::class);
644         api::create_category((object)[
645             'name' => 'bbb',
646             'description' => '<b>yeah</b>',
647             'descriptionformat' => 1
648         ]);
649     }
651     /**
652      * Test fetching of data categories as a user without privileges.
653      */
654     public function test_get_categories_non_dpo_user() {
655         $pleb = $this->getDataGenerator()->create_user();
657         $this->setAdminUser();
658         api::create_category((object)[
659             'name' => 'bbb',
660             'description' => '<b>yeah</b>',
661             'descriptionformat' => 1
662         ]);
664         // Back to a regular user.
665         $this->setUser($pleb);
666         $this->expectException(required_capability_exception::class);
667         api::get_categories();
668     }
670     /**
671      * Test updating of data category as a user without privileges.
672      */
673     public function test_update_category_non_dpo_user() {
674         $pleb = $this->getDataGenerator()->create_user();
676         $this->setAdminUser();
677         $category = api::create_category((object)[
678             'name' => 'bbb',
679             'description' => '<b>yeah</b>',
680             'descriptionformat' => 1
681         ]);
683         // Back to a regular user.
684         $this->setUser($pleb);
685         $this->expectException(required_capability_exception::class);
686         $category->set('name', 'yeah');
687         api::update_category($category->to_record());
688     }
690     /**
691      * Test deletion of data category as a user without privileges.
692      */
693     public function test_delete_category_non_dpo_user() {
694         $pleb = $this->getDataGenerator()->create_user();
696         $this->setAdminUser();
697         $category = api::create_category((object)[
698             'name' => 'bbb',
699             'description' => '<b>yeah</b>',
700             'descriptionformat' => 1
701         ]);
703         // Back to a regular user.
704         $this->setUser($pleb);
705         $this->expectException(required_capability_exception::class);
706         api::delete_category($category->get('id'));
707         $this->fail('Users shouldn\'t be allowed to manage categories by default');
708     }
710     /**
711      * Test data categories CRUD actions.
712      *
713      * @return null
714      */
715     public function test_category_crud() {
717         $this->setAdminUser();
719         // Add.
720         $category = api::create_category((object)[
721             'name' => 'bbb',
722             'description' => '<b>yeah</b>',
723             'descriptionformat' => 1
724         ]);
725         $this->assertInstanceOf('\tool_dataprivacy\category', $category);
726         $this->assertEquals('bbb', $category->get('name'));
728         // Update.
729         $category->set('name', 'bcd');
730         $category = api::update_category($category->to_record());
731         $this->assertEquals('bcd', $category->get('name'));
733         // Retrieve.
734         $category = api::create_category((object)['name' => 'aaa']);
735         $categories = api::get_categories();
736         $this->assertCount(2, $categories);
737         $this->assertEquals('aaa', $categories[0]->get('name'));
738         $this->assertEquals('bcd', $categories[1]->get('name'));
740         // Delete.
741         api::delete_category($categories[0]->get('id'));
742         $this->assertCount(1, api::get_categories());
743         api::delete_category($categories[1]->get('id'));
744         $this->assertCount(0, api::get_categories());
745     }
747     /**
748      * Test context instances.
749      *
750      * @return null
751      */
752     public function test_context_instances() {
753         global $DB;
755         $this->setAdminUser();
757         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
759         $coursecontext1 = \context_course::instance($courses[0]->id);
760         $coursecontext2 = \context_course::instance($courses[1]->id);
762         $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
763             'categoryid' => $categories[0]->get('id')];
764         $contextinstance1 = api::set_context_instance($record1);
766         $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
767             'categoryid' => $categories[1]->get('id')];
768         $contextinstance2 = api::set_context_instance($record2);
770         $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
772         api::unset_context_instance($contextinstance1);
773         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
775         $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
776             'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
777         $contextinstance2 = api::set_context_instance($update);
778         $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
779     }
781     /**
782      * Test contextlevel.
783      *
784      * @return null
785      */
786     public function test_contextlevel() {
787         global $DB;
789         $this->setAdminUser();
790         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
792         $record = (object)[
793             'purposeid' => $purposes[0]->get('id'),
794             'categoryid' => $categories[0]->get('id'),
795             'contextlevel' => CONTEXT_SYSTEM,
796         ];
797         $contextlevel = api::set_contextlevel($record);
798         $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
799         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
800         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
801         $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
803         // Now update it.
804         $record->purposeid = $purposes[1]->get('id');
805         $contextlevel = api::set_contextlevel($record);
806         $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
807         $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
808         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
810         $record->contextlevel = CONTEXT_USER;
811         $contextlevel = api::set_contextlevel($record);
812         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
813     }
815     /**
816      * Test effective context levels purpose and category defaults.
817      *
818      * @return null
819      */
820     public function test_effective_contextlevel_defaults() {
821         $this->setAdminUser();
823         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
825         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
826         $this->assertEquals(false, $purposeid);
827         $this->assertEquals(false, $categoryid);
829         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
830             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
831         );
832         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
834         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
835         $this->assertEquals($purposes[0]->get('id'), $purposeid);
836         $this->assertEquals(false, $categoryid);
838         // Course inherits from system if not defined.
839         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
840         $this->assertEquals($purposes[0]->get('id'), $purposeid);
841         $this->assertEquals(false, $categoryid);
843         // Course defined values should have preference.
844         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
845             \context_helper::get_class_for_level(CONTEXT_COURSE)
846         );
847         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
848         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
850         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
851         $this->assertEquals($purposes[1]->get('id'), $purposeid);
852         $this->assertEquals($categories[0]->get('id'), $categoryid);
854         // Context level defaults are also allowed to be set to 'inherit'.
855         set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
857         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
858         $this->assertEquals($purposes[0]->get('id'), $purposeid);
859         $this->assertEquals($categories[0]->get('id'), $categoryid);
861         list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
862         $this->assertEquals($purposes[0]->get('id'), $purposeid);
863         $this->assertEquals($categories[0]->get('id'), $categoryid);
864     }
866     /**
867      * Test effective contextlevel return.
868      *
869      * @return null
870      */
871     public function test_effective_contextlevel() {
872         $this->setAdminUser();
874         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
876         // Set the system context level to purpose 1.
877         $record = (object)[
878             'contextlevel' => CONTEXT_SYSTEM,
879             'purposeid' => $purposes[1]->get('id'),
880             'categoryid' => $categories[1]->get('id'),
881         ];
882         api::set_contextlevel($record);
884         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
885         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
887         // Value 'not set' will get the default value for the context level. For context level defaults
888         // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
889         // will be retrieved.
890         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
891         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
893         // The behaviour forcing an inherit from context system should result in the same effective
894         // purpose.
895         $record->purposeid = context_instance::INHERIT;
896         $record->contextlevel = CONTEXT_USER;
897         api::set_contextlevel($record);
898         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
899         $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
901         $record->purposeid = $purposes[2]->get('id');
902         $record->contextlevel = CONTEXT_USER;
903         api::set_contextlevel($record);
905         $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
906         $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
908         // Only system and user allowed.
909         $this->expectException(coding_exception::class);
910         $record->contextlevel = CONTEXT_COURSE;
911         $record->purposeid = $purposes[1]->get('id');
912         api::set_contextlevel($record);
913     }
915     /**
916      * Test effective context purposes and categories.
917      *
918      * @return null
919      */
920     public function test_effective_context() {
921         $this->setAdminUser();
923         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
925         // Define system defaults (all context levels below will inherit).
926         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
927             \context_helper::get_class_for_level(CONTEXT_SYSTEM)
928         );
929         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
930         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
932         // Define course defaults.
933         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
934             \context_helper::get_class_for_level(CONTEXT_COURSE)
935         );
936         set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
937         set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
939         $course0context = \context_course::instance($courses[0]->id);
940         $course1context = \context_course::instance($courses[1]->id);
941         $mod0context = \context_module::instance($modules[0]->cmid);
942         $mod1context = \context_module::instance($modules[1]->cmid);
944         // Set course instance values.
945         $record = (object)[
946             'contextid' => $course0context->id,
947             'purposeid' => $purposes[1]->get('id'),
948             'categoryid' => $categories[2]->get('id'),
949         ];
950         api::set_context_instance($record);
951         $category = api::get_effective_context_category($course0context);
952         $this->assertEquals($record->categoryid, $category->get('id'));
954         // Module instances get the context level default if nothing specified.
955         $category = api::get_effective_context_category($mod0context);
956         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
958         // Module instances get the parent context category if they inherit.
959         $record->contextid = $mod0context->id;
960         $record->categoryid = context_instance::INHERIT;
961         api::set_context_instance($record);
962         $category = api::get_effective_context_category($mod0context);
963         $this->assertEquals($categories[2]->get('id'), $category->get('id'));
965         // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
966         $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
967         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
968         $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
969         $this->assertEquals($categories[0]->get('id'), $category->get('id'));
971         // Module instances get the parent context category if they inherit; in
972         // this case the parent context category is not set so it should use the
973         // context level default (see 'Define course defaults' above).
974         $record->contextid = $mod1context->id;
975         $record->categoryid = context_instance::INHERIT;
976         api::set_context_instance($record);
977         $category = api::get_effective_context_category($mod1context);
978         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
980         // User instances use the value set at user context level instead of the user default.
982         // User defaults to cat 0 and user context level to 1.
983         list($purposevar, $categoryvar) = data_registry::var_names_from_context(
984             \context_helper::get_class_for_level(CONTEXT_USER)
985         );
986         set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
987         set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
988         $usercontextlevel = (object)[
989             'contextlevel' => CONTEXT_USER,
990             'purposeid' => $purposes[1]->get('id'),
991             'categoryid' => $categories[1]->get('id'),
992         ];
993         api::set_contextlevel($usercontextlevel);
995         $newuser = $this->getDataGenerator()->create_user();
996         $usercontext = \context_user::instance($newuser->id);
997         $category = api::get_effective_context_category($usercontext);
998         $this->assertEquals($categories[1]->get('id'), $category->get('id'));
999     }
1001     /**
1002      * Tests the deletion of expired contexts.
1003      *
1004      * @return null
1005      */
1006     public function test_expired_context_deletion() {
1007         global $DB;
1009         $this->setAdminUser();
1011         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1013         $course0context = \context_course::instance($courses[0]->id);
1014         $course1context = \context_course::instance($courses[1]->id);
1016         $expiredcontext0 = api::create_expired_context($course0context->id);
1017         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1018         $expiredcontext1 = api::create_expired_context($course1context->id);
1019         $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
1021         api::delete_expired_context($expiredcontext0->get('id'));
1022         $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1023     }
1025     /**
1026      * Tests the status of expired contexts.
1027      *
1028      * @return null
1029      */
1030     public function test_expired_context_status() {
1031         global $DB;
1033         $this->setAdminUser();
1035         list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1037         $course0context = \context_course::instance($courses[0]->id);
1039         $expiredcontext = api::create_expired_context($course0context->id);
1041         // Default status.
1042         $this->assertEquals(expired_context::STATUS_EXPIRED, $expiredcontext->get('status'));
1044         api::set_expired_context_status($expiredcontext, expired_context::STATUS_APPROVED);
1045         $this->assertEquals(expired_context::STATUS_APPROVED, $expiredcontext->get('status'));
1046     }
1048     /**
1049      * Creates test purposes and categories.
1050      *
1051      * @return null
1052      */
1053     protected function add_purposes_and_categories() {
1055         $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1056         $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1057         $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
1059         $cat1 = api::create_category((object)['name' => 'a']);
1060         $cat2 = api::create_category((object)['name' => 'b']);
1061         $cat3 = api::create_category((object)['name' => 'c']);
1063         $course1 = $this->getDataGenerator()->create_course();
1064         $course2 = $this->getDataGenerator()->create_course();
1066         $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1067         $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1069         return [
1070             [$purpose1, $purpose2, $purpose3],
1071             [$cat1, $cat2, $cat3],
1072             [$course1, $course2],
1073             [$module1, $module2]
1074         ];
1075     }