37cb86b97b046c41ea1febb1a7ceefe66e8a4c8c
[moodle.git] / admin / tool / dataprivacy / tests / expired_contexts_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  * Expired contexts tests.
19  *
20  * @package    tool_dataprivacy
21  * @copyright  2018 David Monllao
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 use tool_dataprivacy\api;
26 use tool_dataprivacy\data_registry;
27 use tool_dataprivacy\expired_context;
28 use tool_dataprivacy\purpose;
29 use tool_dataprivacy\category;
30 use tool_dataprivacy\contextlevel;
32 defined('MOODLE_INTERNAL') || die();
33 global $CFG;
35 /**
36  * Expired contexts tests.
37  *
38  * @package    tool_dataprivacy
39  * @copyright  2018 David Monllao
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
44     /**
45      * Setup the basics with the specified retention period.
46      *
47      * @param   string  $system Retention policy for the system.
48      * @param   string  $user Retention policy for users.
49      * @param   string  $course Retention policy for courses.
50      * @param   string  $activity Retention policy for activities.
51      */
52     protected function setup_basics(string $system, string $user, string $course, string $activity = null) : array {
53         $this->resetAfterTest();
55         $purposes = [];
56         $purposes[] = $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM);
57         $purposes[] = $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER);
58         $purposes[] = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
59         if (null !== $activity) {
60             $purposes[] = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
61         }
63         return $purposes;
64     }
66     /**
67      * Create a retention period and set it for the specified context level.
68      *
69      * @param   string  $retention
70      * @param   int     $contextlevel
71      * @return  purpose
72      */
73     protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) : purpose {
74         $purpose = new purpose(0, (object) [
75             'name' => 'Test purpose ' . rand(1, 1000),
76             'retentionperiod' => $retention,
77             'lawfulbases' => 'gdpr_art_6_1_a',
78         ]);
79         $purpose->create();
81         $cat = new category(0, (object) ['name' => 'Test category']);
82         $cat->create();
84         if ($contextlevel <= CONTEXT_USER) {
85             $record = (object) [
86                 'purposeid'     => $purpose->get('id'),
87                 'categoryid'    => $cat->get('id'),
88                 'contextlevel'  => $contextlevel,
89             ];
90             api::set_contextlevel($record);
91         } else {
92             list($purposevar, ) = data_registry::var_names_from_context(
93                     \context_helper::get_class_for_level(CONTEXT_COURSE)
94                 );
95             set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
96         }
98         return $purpose;
99     }
101     /**
102      * Ensure that a user with no lastaccess is not flagged for deletion.
103      */
104     public function test_flag_not_setup() {
105         $this->resetAfterTest();
107         $user = $this->getDataGenerator()->create_user();
109         $this->setUser($user);
110         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
111         $context = \context_block::instance($block->instance->id);
112         $this->setUser();
114         // Flag all expired contexts.
115         $manager = new \tool_dataprivacy\expired_contexts_manager();
116         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
118         $this->assertEquals(0, $flaggedcourses);
119         $this->assertEquals(0, $flaggedusers);
120     }
122     /**
123      * Ensure that a user with no lastaccess is not flagged for deletion.
124      */
125     public function test_flag_user_no_lastaccess() {
126         $this->resetAfterTest();
128         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
130         $user = $this->getDataGenerator()->create_user();
132         $this->setUser($user);
133         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
134         $context = \context_block::instance($block->instance->id);
135         $this->setUser();
137         // Flag all expired contexts.
138         $manager = new \tool_dataprivacy\expired_contexts_manager();
139         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
141         $this->assertEquals(0, $flaggedcourses);
142         $this->assertEquals(0, $flaggedusers);
143     }
145     /**
146      * Ensure that a user with a recent lastaccess is not flagged for deletion.
147      */
148     public function test_flag_user_recent_lastaccess() {
149         $this->resetAfterTest();
151         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
153         $user = $this->getDataGenerator()->create_user(['lastaccess' => time()]);
155         $this->setUser($user);
156         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
157         $context = \context_block::instance($block->instance->id);
158         $this->setUser();
160         // Flag all expired contexts.
161         $manager = new \tool_dataprivacy\expired_contexts_manager();
162         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
164         $this->assertEquals(0, $flaggedcourses);
165         $this->assertEquals(0, $flaggedusers);
166     }
168     /**
169      * Ensure that a user with a lastaccess in the past is flagged for deletion.
170      */
171     public function test_flag_user_past_lastaccess() {
172         $this->resetAfterTest();
174         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
176         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
178         $this->setUser($user);
179         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
180         $context = \context_block::instance($block->instance->id);
181         $this->setUser();
183         // Flag all expired contexts.
184         $manager = new \tool_dataprivacy\expired_contexts_manager();
185         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
187         // Although there is a block in the user context, everything in the user context is regarded as one.
188         $this->assertEquals(0, $flaggedcourses);
189         $this->assertEquals(1, $flaggedusers);
190     }
192     /**
193      * Ensure that a user with a lastaccess in the past but active enrolments is not flagged for deletion.
194      */
195     public function test_flag_user_past_lastaccess_still_enrolled() {
196         $this->resetAfterTest();
198         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
200         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
201         $course = $this->getDataGenerator()->create_course(['startdate' => time(), 'enddate' => time() + YEARSECS]);
202         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
204         $otheruser = $this->getDataGenerator()->create_user();
205         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
207         $this->setUser($user);
208         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
209         $context = \context_block::instance($block->instance->id);
210         $this->setUser();
212         // Flag all expired contexts.
213         $manager = new \tool_dataprivacy\expired_contexts_manager();
214         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
216         $this->assertEquals(0, $flaggedcourses);
217         $this->assertEquals(0, $flaggedusers);
218     }
220     /**
221      * Ensure that a user with a lastaccess in the past and expired enrolments.
222      */
223     public function test_flag_user_past_lastaccess_unexpired_past_enrolment() {
224         $this->resetAfterTest();
226         $this->setup_basics('PT1H', 'PT1H', 'P1Y');
228         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
229         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
230         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
232         $otheruser = $this->getDataGenerator()->create_user();
233         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
235         $this->setUser($user);
236         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
237         $context = \context_block::instance($block->instance->id);
238         $this->setUser();
240         // Flag all expired contexts.
241         $manager = new \tool_dataprivacy\expired_contexts_manager();
242         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
244         $this->assertEquals(0, $flaggedcourses);
245         $this->assertEquals(0, $flaggedusers);
246     }
248     /**
249      * Ensure that a user with a lastaccess in the past and expired enrolments.
250      */
251     public function test_flag_user_past_lastaccess_expired_enrolled() {
252         $this->resetAfterTest();
254         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
256         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
257         $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - WEEKSECS]);
258         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
260         $otheruser = $this->getDataGenerator()->create_user();
261         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
263         $this->setUser($user);
264         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
265         $context = \context_block::instance($block->instance->id);
266         $this->setUser();
268         // Flag all expired contexts.
269         $manager = new \tool_dataprivacy\expired_contexts_manager();
270         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
272         $this->assertEquals(1, $flaggedcourses);
273         $this->assertEquals(1, $flaggedusers);
274     }
276     /**
277      * Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected
278      * correctly.
279      */
280     public function test_flag_user_past_lastaccess_missing_enddate_required() {
281         $this->resetAfterTest();
283         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
285         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
286         $course = $this->getDataGenerator()->create_course();
287         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
289         $otheruser = $this->getDataGenerator()->create_user();
290         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
292         $this->setUser($user);
293         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
294         $context = \context_block::instance($block->instance->id);
295         $this->setUser();
297         // Ensure that course end dates are not required.
298         set_config('requireallenddatesforuserdeletion', 1, 'tool_dataprivacy');
300         // Flag all expired contexts.
301         $manager = new \tool_dataprivacy\expired_contexts_manager();
302         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
304         $this->assertEquals(0, $flaggedcourses);
305         $this->assertEquals(0, $flaggedusers);
306     }
308     /**
309      * Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected
310      * correctly when the end date is not required.
311      */
312     public function test_flag_user_past_lastaccess_missing_enddate_not_required() {
313         $this->resetAfterTest();
315         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
317         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
318         $course = $this->getDataGenerator()->create_course();
319         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
321         $otheruser = $this->getDataGenerator()->create_user();
322         $this->getDataGenerator()->enrol_user($otheruser->id, $course->id, 'student');
324         $this->setUser($user);
325         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
326         $context = \context_block::instance($block->instance->id);
327         $this->setUser();
329         // Ensure that course end dates are required.
330         set_config('requireallenddatesforuserdeletion', 0, 'tool_dataprivacy');
332         // Flag all expired contexts.
333         $manager = new \tool_dataprivacy\expired_contexts_manager();
334         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
336         $this->assertEquals(0, $flaggedcourses);
337         $this->assertEquals(1, $flaggedusers);
338     }
340     /**
341      * Ensure that a user with a recent lastaccess is not flagged for deletion.
342      */
343     public function test_flag_user_recent_lastaccess_existing_record() {
344         $this->resetAfterTest();
346         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
348         $user = $this->getDataGenerator()->create_user(['lastaccess' => time()]);
349         $usercontext = \context_user::instance($user->id);
351         // Create an existing expired_context.
352         $expiredcontext = new expired_context(0, (object) [
353                 'contextid' => $usercontext->id,
354                 'status' => expired_context::STATUS_EXPIRED,
355             ]);
356         $expiredcontext->save();
358         $this->setUser($user);
359         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
360         $context = \context_block::instance($block->instance->id);
361         $this->setUser();
363         // Flag all expired contexts.
364         $manager = new \tool_dataprivacy\expired_contexts_manager();
365         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
367         $this->assertEquals(0, $flaggedcourses);
368         $this->assertEquals(0, $flaggedusers);
370         $this->expectException('dml_missing_record_exception');
371         new expired_context($expiredcontext->get('id'));
372     }
374     /**
375      * Ensure that a user with a recent lastaccess is not flagged for deletion.
376      */
377     public function test_flag_user_retention_changed() {
378         $this->resetAfterTest();
380         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
382         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
383         $usercontext = \context_user::instance($user->id);
385         $this->setUser($user);
386         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
387         $context = \context_block::instance($block->instance->id);
388         $this->setUser();
390         // Flag all expired contexts.
391         $manager = new \tool_dataprivacy\expired_contexts_manager();
392         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
394         $this->assertEquals(0, $flaggedcourses);
395         $this->assertEquals(1, $flaggedusers);
397         $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
398         $this->assertNotFalse($expiredcontext);
400         // Increase the retention period to 5 years.
401         $userpurpose->set('retentionperiod', 'P5Y');
402         $userpurpose->save();
404         // Re-run the expiry job - the previously flagged user will be removed because the retention period has been increased.
405         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
406         $this->assertEquals(0, $flaggedcourses);
407         $this->assertEquals(0, $flaggedusers);
409         // The expiry record will now have been removed.
410         $this->expectException('dml_missing_record_exception');
411         new expired_context($expiredcontext->get('id'));
412     }
414     /**
415      * Ensure that a user with a historically expired expired block record child is cleaned up.
416      */
417     public function test_flag_user_historic_block_unapproved() {
418         $this->resetAfterTest();
420         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
422         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
423         $usercontext = \context_user::instance($user->id);
425         $this->setUser($user);
426         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
427         $blockcontext = \context_block::instance($block->instance->id);
428         $this->setUser();
430         // Create an existing expired_context which has not been approved for the block.
431         $expiredcontext = new expired_context(0, (object) [
432                 'contextid' => $blockcontext->id,
433                 'status' => expired_context::STATUS_EXPIRED,
434             ]);
435         $expiredcontext->save();
437         // Flag all expired contexts.
438         $manager = new \tool_dataprivacy\expired_contexts_manager();
439         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
441         $this->assertEquals(0, $flaggedcourses);
442         $this->assertEquals(1, $flaggedusers);
444         $expiredblockcontext = expired_context::get_record(['contextid' => $blockcontext->id]);
445         $this->assertFalse($expiredblockcontext);
447         $expiredusercontext = expired_context::get_record(['contextid' => $usercontext->id]);
448         $this->assertNotFalse($expiredusercontext);
449     }
451     /**
452      * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
453      */
454     public function test_flag_user_historic_unexpired_child() {
455         $this->resetAfterTest();
457         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
458         $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
460         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
461         $usercontext = \context_user::instance($user->id);
463         $this->setUser($user);
464         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
465         $blockcontext = \context_block::instance($block->instance->id);
466         $this->setUser();
468         // Flag all expired contexts.
469         $manager = new \tool_dataprivacy\expired_contexts_manager();
470         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
472         $this->assertEquals(0, $flaggedcourses);
473         $this->assertEquals(1, $flaggedusers);
475         $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
476         $this->assertNotFalse($expiredcontext);
477     }
479     /**
480      * Ensure that a course with no end date is not flagged.
481      */
482     public function test_flag_course_no_enddate() {
483         $this->resetAfterTest();
485         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
487         $course = $this->getDataGenerator()->create_course();
488         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
490         // Flag all expired contexts.
491         $manager = new \tool_dataprivacy\expired_contexts_manager();
492         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
494         $this->assertEquals(0, $flaggedcourses);
495         $this->assertEquals(0, $flaggedusers);
496     }
498     /**
499      * Ensure that a course with an end date in the distant past, but a child which is unexpired is not flagged.
500      */
501     public function test_flag_course_past_enddate_future_child() {
502         $this->resetAfterTest();
504         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'P5Y');
506         $course = $this->getDataGenerator()->create_course([
507                 'startdate' => time() - (2 * YEARSECS),
508                 'enddate' => time() - YEARSECS,
509             ]);
510         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
512         // Flag all expired contexts.
513         $manager = new \tool_dataprivacy\expired_contexts_manager();
514         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
516         $this->assertEquals(0, $flaggedcourses);
517         $this->assertEquals(0, $flaggedusers);
518     }
520     /**
521      * Ensure that a course with an end date in the distant past is flagged.
522      */
523     public function test_flag_course_past_enddate() {
524         $this->resetAfterTest();
526         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
528         $course = $this->getDataGenerator()->create_course([
529                 'startdate' => time() - (2 * YEARSECS),
530                 'enddate' => time() - YEARSECS,
531             ]);
532         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
534         // Flag all expired contexts.
535         $manager = new \tool_dataprivacy\expired_contexts_manager();
536         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
538         $this->assertEquals(2, $flaggedcourses);
539         $this->assertEquals(0, $flaggedusers);
540     }
542     /**
543      * Ensure that a course with an end date in the distant past is flagged.
544      */
545     public function test_flag_course_past_enddate_multiple() {
546         $this->resetAfterTest();
548         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
550         $course1 = $this->getDataGenerator()->create_course([
551                 'startdate' => time() - (2 * YEARSECS),
552                 'enddate' => time() - YEARSECS,
553             ]);
554         $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
556         $course2 = $this->getDataGenerator()->create_course([
557                 'startdate' => time() - (2 * YEARSECS),
558                 'enddate' => time() - YEARSECS,
559             ]);
560         $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
562         // Flag all expired contexts.
563         $manager = new \tool_dataprivacy\expired_contexts_manager();
564         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
566         $this->assertEquals(4, $flaggedcourses);
567         $this->assertEquals(0, $flaggedusers);
568     }
570     /**
571      * Ensure that a course with an end date in the future is not flagged.
572      */
573     public function test_flag_course_future_enddate() {
574         $this->resetAfterTest();
576         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
578         $course = $this->getDataGenerator()->create_course(['enddate' => time() + YEARSECS]);
579         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
581         // Flag all expired contexts.
582         $manager = new \tool_dataprivacy\expired_contexts_manager();
583         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
585         $this->assertEquals(0, $flaggedcourses);
586         $this->assertEquals(0, $flaggedusers);
587     }
589     /**
590      * Ensure that a course with an end date in the future is not flagged.
591      */
592     public function test_flag_course_recent_unexpired_enddate() {
593         $this->resetAfterTest();
595         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
597         $course = $this->getDataGenerator()->create_course(['enddate' => time() - 1]);
599         // Flag all expired contexts.
600         $manager = new \tool_dataprivacy\expired_contexts_manager();
601         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
603         $this->assertEquals(0, $flaggedcourses);
604         $this->assertEquals(0, $flaggedusers);
605     }
607     /**
608      * Ensure that a site not setup will not process anything.
609      */
610     public function test_process_not_setup() {
611         $this->resetAfterTest();
613         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
614         $usercontext = \context_user::instance($user->id);
616         // Create an existing expired_context.
617         $expiredcontext = new expired_context(0, (object) [
618                 'contextid' => $usercontext->id,
619                 'status' => expired_context::STATUS_EXPIRED,
620             ]);
621         $expiredcontext->save();
623         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
624             ->setMethods([
625                 'delete_data_for_user',
626                 'delete_data_for_all_users_in_context',
627             ])
628             ->getMock();
629         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
630         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
632         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
633             ->setMethods(['get_privacy_manager'])
634             ->getMock();
635         $manager->set_progress(new \null_progress_trace());
637         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
638         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
640         $this->assertEquals(0, $processedcourses);
641         $this->assertEquals(0, $processedusers);
642     }
644     /**
645      * Ensure that a user with no lastaccess is not flagged for deletion.
646      */
647     public function test_process_none_approved() {
648         $this->resetAfterTest();
650         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
652         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
653         $usercontext = \context_user::instance($user->id);
655         // Create an existing expired_context.
656         $expiredcontext = new expired_context(0, (object) [
657                 'contextid' => $usercontext->id,
658                 'status' => expired_context::STATUS_EXPIRED,
659             ]);
660         $expiredcontext->save();
662         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
663             ->setMethods([
664                 'delete_data_for_user',
665                 'delete_data_for_all_users_in_context',
666             ])
667             ->getMock();
668         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
669         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
671         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
672             ->setMethods(['get_privacy_manager'])
673             ->getMock();
674         $manager->set_progress(new \null_progress_trace());
676         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
677         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
679         $this->assertEquals(0, $processedcourses);
680         $this->assertEquals(0, $processedusers);
681     }
683     /**
684      * Ensure that a user with no lastaccess is not flagged for deletion.
685      */
686     public function test_process_no_context() {
687         $this->resetAfterTest();
689         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
691         // Create an existing expired_context.
692         $expiredcontext = new expired_context(0, (object) [
693                 'contextid' => -1,
694                 'status' => expired_context::STATUS_APPROVED,
695             ]);
696         $expiredcontext->save();
698         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
699             ->setMethods([
700                 'delete_data_for_user',
701                 'delete_data_for_all_users_in_context',
702             ])
703             ->getMock();
704         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
705         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
707         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
708             ->setMethods(['get_privacy_manager'])
709             ->getMock();
710         $manager->set_progress(new \null_progress_trace());
712         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
713         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
715         $this->assertEquals(0, $processedcourses);
716         $this->assertEquals(0, $processedusers);
718         $this->expectException('dml_missing_record_exception');
719         new expired_context($expiredcontext->get('id'));
720     }
722     /**
723      * Ensure that a user context previously flagged as approved is removed.
724      */
725     public function test_process_user_context() {
726         $this->resetAfterTest();
728         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
730         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
731         $usercontext = \context_user::instance($user->id);
733         $this->setUser($user);
734         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
735         $blockcontext = \context_block::instance($block->instance->id);
736         $this->setUser();
738         // Create an existing expired_context.
739         $expiredcontext = new expired_context(0, (object) [
740                 'contextid' => $usercontext->id,
741                 'status' => expired_context::STATUS_APPROVED,
742             ]);
743         $expiredcontext->save();
745         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
746             ->setMethods([
747                 'delete_data_for_user',
748                 'delete_data_for_all_users_in_context',
749             ])
750             ->getMock();
751         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
752         $mockprivacymanager->expects($this->exactly(2))
753             ->method('delete_data_for_all_users_in_context')
754             ->withConsecutive(
755                 [$blockcontext],
756                 [$usercontext]
757             );
759         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
760             ->setMethods(['get_privacy_manager'])
761             ->getMock();
762         $manager->set_progress(new \null_progress_trace());
764         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
765         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
767         $this->assertEquals(0, $processedcourses);
768         $this->assertEquals(1, $processedusers);
770         $updatedcontext = new expired_context($expiredcontext->get('id'));
771         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
773         // Flag all expired contexts again.
774         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
776         $this->assertEquals(0, $flaggedcourses);
777         $this->assertEquals(0, $flaggedusers);
779         // Ensure that the deleted context record is still present.
780         $updatedcontext = new expired_context($expiredcontext->get('id'));
781         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
782     }
784     /**
785      * Ensure that a course context previously flagged as approved is removed.
786      */
787     public function test_process_course_context() {
788         $this->resetAfterTest();
790         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
792         $course = $this->getDataGenerator()->create_course([
793                 'startdate' => time() - (2 * YEARSECS),
794                 'enddate' => time() - YEARSECS,
795             ]);
796         $coursecontext = \context_course::instance($course->id);
798         // Create an existing expired_context.
799         $expiredcontext = new expired_context(0, (object) [
800                 'contextid' => $coursecontext->id,
801                 'status' => expired_context::STATUS_APPROVED,
802             ]);
803         $expiredcontext->save();
805         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
806             ->setMethods([
807                 'delete_data_for_user',
808                 'delete_data_for_all_users_in_context',
809             ])
810             ->getMock();
811         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
812         $mockprivacymanager->expects($this->once())->method('delete_data_for_all_users_in_context');
814         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
815             ->setMethods(['get_privacy_manager'])
816             ->getMock();
817         $manager->set_progress(new \null_progress_trace());
819         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
820         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
822         $this->assertEquals(1, $processedcourses);
823         $this->assertEquals(0, $processedusers);
825         $updatedcontext = new expired_context($expiredcontext->get('id'));
826         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
827     }
829     /**
830      * Ensure that a user context previously flagged as approved is not removed if the user then logs in.
831      */
832     public function test_process_user_context_logged_in_after_approval() {
833         $this->resetAfterTest();
835         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
837         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
838         $usercontext = \context_user::instance($user->id);
840         $this->setUser($user);
841         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
842         $context = \context_block::instance($block->instance->id);
843         $this->setUser();
845         // Create an existing expired_context.
846         $expiredcontext = new expired_context(0, (object) [
847                 'contextid' => $usercontext->id,
848                 'status' => expired_context::STATUS_APPROVED,
849             ]);
850         $expiredcontext->save();
852         // Now bump the user's last login time.
853         $this->setUser($user);
854         user_accesstime_log();
855         $this->setUser();
857         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
858             ->setMethods([
859                 'delete_data_for_user',
860                 'delete_data_for_all_users_in_context',
861             ])
862             ->getMock();
863         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
864         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
866         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
867             ->setMethods(['get_privacy_manager'])
868             ->getMock();
869         $manager->set_progress(new \null_progress_trace());
871         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
872         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
874         $this->assertEquals(0, $processedcourses);
875         $this->assertEquals(0, $processedusers);
877         $this->expectException('dml_missing_record_exception');
878         new expired_context($expiredcontext->get('id'));
879     }
881     /**
882      * Ensure that a user context previously flagged as approved is not removed if the purpose has changed.
883      */
884     public function test_process_user_context_changed_after_approved() {
885         $this->resetAfterTest();
887         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
889         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
890         $usercontext = \context_user::instance($user->id);
892         $this->setUser($user);
893         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
894         $context = \context_block::instance($block->instance->id);
895         $this->setUser();
897         // Create an existing expired_context.
898         $expiredcontext = new expired_context(0, (object) [
899                 'contextid' => $usercontext->id,
900                 'status' => expired_context::STATUS_APPROVED,
901             ]);
902         $expiredcontext->save();
904         // Now make the user a site admin.
905         $admins = explode(',', get_config('moodle', 'siteadmins'));
906         $admins[] = $user->id;
907         set_config('siteadmins', implode(',', $admins));
909         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
910             ->setMethods([
911                 'delete_data_for_user',
912                 'delete_data_for_all_users_in_context',
913             ])
914             ->getMock();
915         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
916         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
918         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
919             ->setMethods(['get_privacy_manager'])
920             ->getMock();
921         $manager->set_progress(new \null_progress_trace());
923         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
924         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
926         $this->assertEquals(0, $processedcourses);
927         $this->assertEquals(0, $processedusers);
929         $this->expectException('dml_missing_record_exception');
930         new expired_context($expiredcontext->get('id'));
931     }
933     /**
934      * Ensure that a user with a historically expired expired block record child is cleaned up.
935      */
936     public function test_process_user_historic_block_unapproved() {
937         $this->resetAfterTest();
939         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
941         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
942         $usercontext = \context_user::instance($user->id);
944         $this->setUser($user);
945         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
946         $blockcontext = \context_block::instance($block->instance->id);
947         $this->setUser();
949         // Create an expired_context for the user.
950         $expiredusercontext = new expired_context(0, (object) [
951                 'contextid' => $usercontext->id,
952                 'status' => expired_context::STATUS_APPROVED,
953             ]);
954         $expiredusercontext->save();
956         // Create an existing expired_context which has not been approved for the block.
957         $expiredblockcontext = new expired_context(0, (object) [
958                 'contextid' => $blockcontext->id,
959                 'status' => expired_context::STATUS_EXPIRED,
960             ]);
961         $expiredblockcontext->save();
963         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
964             ->setMethods([
965                 'delete_data_for_user',
966                 'delete_data_for_all_users_in_context',
967             ])
968             ->getMock();
969         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
970         $mockprivacymanager->expects($this->exactly(2))
971             ->method('delete_data_for_all_users_in_context')
972             ->withConsecutive(
973                 [$blockcontext],
974                 [$usercontext]
975             );
977         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
978             ->setMethods(['get_privacy_manager'])
979             ->getMock();
980         $manager->set_progress(new \null_progress_trace());
982         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
983         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
985         $this->assertEquals(0, $processedcourses);
986         $this->assertEquals(1, $processedusers);
988         $updatedcontext = new expired_context($expiredusercontext->get('id'));
989         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
990     }
992     /**
993      * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
994      */
995     public function test_process_user_historic_unexpired_child() {
996         $this->resetAfterTest();
998         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
999         $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
1001         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
1002         $usercontext = \context_user::instance($user->id);
1004         $this->setUser($user);
1005         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
1006         $blockcontext = \context_block::instance($block->instance->id);
1007         $this->setUser();
1009         // Create an expired_context for the user.
1010         $expiredusercontext = new expired_context(0, (object) [
1011                 'contextid' => $usercontext->id,
1012                 'status' => expired_context::STATUS_APPROVED,
1013             ]);
1014         $expiredusercontext->save();
1016         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1017             ->setMethods([
1018                 'delete_data_for_user',
1019                 'delete_data_for_all_users_in_context',
1020             ])
1021             ->getMock();
1022         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
1023         $mockprivacymanager->expects($this->exactly(2))
1024             ->method('delete_data_for_all_users_in_context')
1025             ->withConsecutive(
1026                 [$blockcontext],
1027                 [$usercontext]
1028             );
1030         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1031             ->setMethods(['get_privacy_manager'])
1032             ->getMock();
1033         $manager->set_progress(new \null_progress_trace());
1035         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1036         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1038         $this->assertEquals(0, $processedcourses);
1039         $this->assertEquals(1, $processedusers);
1041         $updatedcontext = new expired_context($expiredusercontext->get('id'));
1042         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1043     }
1045     /**
1046      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1047      * updated.
1048      */
1049     public function test_process_course_context_updated() {
1050         $this->resetAfterTest();
1052         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1054         $course = $this->getDataGenerator()->create_course([
1055                 'startdate' => time() - (2 * YEARSECS),
1056                 'enddate' => time() - YEARSECS,
1057             ]);
1058         $coursecontext = \context_course::instance($course->id);
1059         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1061         // Create an existing expired_context.
1062         $expiredcontext = new expired_context(0, (object) [
1063                 'contextid' => $coursecontext->id,
1064                 'status' => expired_context::STATUS_APPROVED,
1065             ]);
1066         $expiredcontext->save();
1068         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1069             ->setMethods([
1070                 'delete_data_for_user',
1071                 'delete_data_for_all_users_in_context',
1072             ])
1073             ->getMock();
1074         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1075         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1077         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1078             ->setMethods(['get_privacy_manager'])
1079             ->getMock();
1080         $manager->set_progress(new \null_progress_trace());
1081         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1083         $coursepurpose = $purposes[2];
1084         $coursepurpose->set('retentionperiod', 'P5Y');
1085         $coursepurpose->save();
1087         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1089         $this->assertEquals(0, $processedcourses);
1090         $this->assertEquals(0, $processedusers);
1092         $updatedcontext = new expired_context($expiredcontext->get('id'));
1094         // No change - we just can't process it until the children have finished.
1095         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1096     }
1098     /**
1099      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1100      * updated.
1101      */
1102     public function test_process_course_context_outstanding_children() {
1103         $this->resetAfterTest();
1105         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1107         $course = $this->getDataGenerator()->create_course([
1108                 'startdate' => time() - (2 * YEARSECS),
1109                 'enddate' => time() - YEARSECS,
1110             ]);
1111         $coursecontext = \context_course::instance($course->id);
1112         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1114         // Create an existing expired_context.
1115         $expiredcontext = new expired_context(0, (object) [
1116                 'contextid' => $coursecontext->id,
1117                 'status' => expired_context::STATUS_APPROVED,
1118             ]);
1119         $expiredcontext->save();
1121         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1122             ->setMethods([
1123                 'delete_data_for_user',
1124                 'delete_data_for_all_users_in_context',
1125             ])
1126             ->getMock();
1127         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1128         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1130         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1131             ->setMethods(['get_privacy_manager'])
1132             ->getMock();
1133         $manager->set_progress(new \null_progress_trace());
1135         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1136         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1138         $this->assertEquals(0, $processedcourses);
1139         $this->assertEquals(0, $processedusers);
1141         $updatedcontext = new expired_context($expiredcontext->get('id'));
1143         // No change - we just can't process it until the children have finished.
1144         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1145     }
1147     /**
1148      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1149      * updated.
1150      */
1151     public function test_process_course_context_pending_children() {
1152         $this->resetAfterTest();
1154         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1156         $course = $this->getDataGenerator()->create_course([
1157                 'startdate' => time() - (2 * YEARSECS),
1158                 'enddate' => time() - YEARSECS,
1159             ]);
1160         $coursecontext = \context_course::instance($course->id);
1161         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1162         $cm = get_coursemodule_from_instance('forum', $forum->id);
1163         $forumcontext = \context_module::instance($cm->id);
1165         // Create an existing expired_context for the course.
1166         $expiredcoursecontext = new expired_context(0, (object) [
1167                 'contextid' => $coursecontext->id,
1168                 'status' => expired_context::STATUS_APPROVED,
1169             ]);
1170         $expiredcoursecontext->save();
1172         // And for the forum.
1173         $expiredforumcontext = new expired_context(0, (object) [
1174                 'contextid' => $forumcontext->id,
1175                 'status' => expired_context::STATUS_EXPIRED,
1176             ]);
1177         $expiredforumcontext->save();
1179         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1180             ->setMethods([
1181                 'delete_data_for_user',
1182                 'delete_data_for_all_users_in_context',
1183             ])
1184             ->getMock();
1185         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1186         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1188         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1189             ->setMethods(['get_privacy_manager'])
1190             ->getMock();
1191         $manager->set_progress(new \null_progress_trace());
1193         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1194         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1196         $this->assertEquals(0, $processedcourses);
1197         $this->assertEquals(0, $processedusers);
1199         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1201         // No change - we just can't process it until the children have finished.
1202         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1203     }
1205     /**
1206      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1207      * updated.
1208      */
1209     public function test_process_course_context_approved_children() {
1210         $this->resetAfterTest();
1212         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1214         $course = $this->getDataGenerator()->create_course([
1215                 'startdate' => time() - (2 * YEARSECS),
1216                 'enddate' => time() - YEARSECS,
1217             ]);
1218         $coursecontext = \context_course::instance($course->id);
1219         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1220         $cm = get_coursemodule_from_instance('forum', $forum->id);
1221         $forumcontext = \context_module::instance($cm->id);
1223         // Create an existing expired_context for the course.
1224         $expiredcoursecontext = new expired_context(0, (object) [
1225                 'contextid' => $coursecontext->id,
1226                 'status' => expired_context::STATUS_APPROVED,
1227             ]);
1228         $expiredcoursecontext->save();
1230         // And for the forum.
1231         $expiredforumcontext = new expired_context(0, (object) [
1232                 'contextid' => $forumcontext->id,
1233                 'status' => expired_context::STATUS_APPROVED,
1234             ]);
1235         $expiredforumcontext->save();
1237         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1238             ->setMethods([
1239                 'delete_data_for_user',
1240                 'delete_data_for_all_users_in_context',
1241             ])
1242             ->getMock();
1243         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1244         $mockprivacymanager->expects($this->exactly(2))
1245             ->method('delete_data_for_all_users_in_context')
1246             ->withConsecutive(
1247                 [$forumcontext],
1248                 [$coursecontext]
1249             );
1251         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1252             ->setMethods(['get_privacy_manager'])
1253             ->getMock();
1254         $manager->set_progress(new \null_progress_trace());
1256         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1258         // Initially only the forum will be processed.
1259         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1261         $this->assertEquals(1, $processedcourses);
1262         $this->assertEquals(0, $processedusers);
1264         $updatedcontext = new expired_context($expiredforumcontext->get('id'));
1265         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1267         // The course won't have been processed yet.
1268         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1269         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1271         // A subsequent run will cause the course to processed as it is no longer dependent upon the child contexts.
1272         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1274         $this->assertEquals(1, $processedcourses);
1275         $this->assertEquals(0, $processedusers);
1276         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1277         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1278     }
1280     /**
1281      * Test that the can_process_deletion function returns expected results.
1282      *
1283      * @dataProvider    can_process_deletion_provider
1284      * @param       int     $status
1285      * @param       bool    $expected
1286      */
1287     public function test_can_process_deletion($status, $expected) {
1288         $purpose = new expired_context(0, (object) [
1289             'status' => $status,
1291             'contextid' => \context_system::instance()->id,
1292         ]);
1294         $this->assertEquals($expected, $purpose->can_process_deletion());
1295     }
1297     /**
1298      * Data provider for the can_process_deletion tests.
1299      *
1300      * @return  array
1301      */
1302     public function can_process_deletion_provider() : array {
1303         return [
1304             'Pending' => [
1305                 expired_context::STATUS_EXPIRED,
1306                 false,
1307             ],
1308             'Approved' => [
1309                 expired_context::STATUS_APPROVED,
1310                 true,
1311             ],
1312             'Complete' => [
1313                 expired_context::STATUS_CLEANED,
1314                 false,
1315             ],
1316         ];
1317     }
1319     /**
1320      * Test that the is_complete function returns expected results.
1321      *
1322      * @dataProvider        is_complete_provider
1323      * @param       int     $status
1324      * @param       bool    $expected
1325      */
1326     public function test_is_complete($status, $expected) {
1327         $purpose = new expired_context(0, (object) [
1328             'status' => $status,
1329             'contextid' => \context_system::instance()->id,
1330         ]);
1332         $this->assertEquals($expected, $purpose->is_complete());
1333     }
1335     /**
1336      * Data provider for the is_complete tests.
1337      *
1338      * @return  array
1339      */
1340     public function is_complete_provider() : array {
1341         return [
1342             'Pending' => [
1343                 expired_context::STATUS_EXPIRED,
1344                 false,
1345             ],
1346             'Approved' => [
1347                 expired_context::STATUS_APPROVED,
1348                 false,
1349             ],
1350             'Complete' => [
1351                 expired_context::STATUS_CLEANED,
1352                 true,
1353             ],
1354         ];
1355     }
1357     /**
1358      * Ensure that any orphaned records are removed once the context has been removed.
1359      */
1360     public function test_orphaned_records_are_cleared() {
1361         $this->resetAfterTest();
1363         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1365         $course = $this->getDataGenerator()->create_course([
1366                 'startdate' => time() - (2 * YEARSECS),
1367                 'enddate' => time() - YEARSECS,
1368             ]);
1369         $context = \context_course::instance($course->id);
1371         // Flag all expired contexts.
1372         $manager = new \tool_dataprivacy\expired_contexts_manager();
1373         $manager->set_progress(new \null_progress_trace());
1374         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
1376         $this->assertEquals(1, $flaggedcourses);
1377         $this->assertEquals(0, $flaggedusers);
1379         // Ensure that the record currently exists.
1380         $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
1381         $this->assertNotFalse($expiredcontext);
1383         // Approve it.
1384         $expiredcontext->set('status', expired_context::STATUS_APPROVED)->save();
1386         // Process deletions.
1387         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1389         $this->assertEquals(1, $processedcourses);
1390         $this->assertEquals(0, $processedusers);
1392         // Ensure that the record still exists.
1393         $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
1394         $this->assertNotFalse($expiredcontext);
1396         // Remove the actual course.
1397         delete_course($course->id, false);
1399         // The record will still exist until we flag it again.
1400         $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
1401         $this->assertNotFalse($expiredcontext);
1403         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
1404         $expiredcontext = expired_context::get_record(['contextid' => $context->id]);
1405         $this->assertFalse($expiredcontext);
1406     }
1408     /**
1409      * Ensure that the progres tracer works as expected out of the box.
1410      */
1411     public function test_progress_tracer_default() {
1412         $manager = new \tool_dataprivacy\expired_contexts_manager();
1414         $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
1415         $rcm = $rc->getMethod('get_progress');
1417         $rcm->setAccessible(true);
1418         $this->assertInstanceOf(\text_progress_trace::class, $rcm->invoke($manager));
1419     }
1421     /**
1422      * Ensure that the progres tracer works as expected when given a specific traer.
1423      */
1424     public function test_progress_tracer_set() {
1425         $manager = new \tool_dataprivacy\expired_contexts_manager();
1426         $mytrace = new \null_progress_trace();
1427         $manager->set_progress($mytrace);
1429         $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
1430         $rcm = $rc->getMethod('get_progress');
1432         $rcm->setAccessible(true);
1433         $this->assertSame($mytrace, $rcm->invoke($manager));
1434     }
1436     /**
1437      * Creates an HTML block on a user.
1438      *
1439      * @param   string  $title
1440      * @param   string  $body
1441      * @param   string  $format
1442      * @return  \block_instance
1443      */
1444     protected function create_user_block($title, $body, $format) {
1445         global $USER;
1447         $configdata = (object) [
1448             'title' => $title,
1449             'text' => [
1450                 'itemid' => 19,
1451                 'text' => $body,
1452                 'format' => $format,
1453             ],
1454         ];
1456         $this->create_block($this->construct_user_page($USER));
1457         $block = $this->get_last_block_on_page($this->construct_user_page($USER));
1458         $block = block_instance('html', $block->instance);
1459         $block->instance_config_save((object) $configdata);
1461         return $block;
1462     }
1464     /**
1465      * Creates an HTML block on a page.
1466      *
1467      * @param \page $page Page
1468      */
1469     protected function create_block($page) {
1470         $page->blocks->add_block_at_end_of_default_region('html');
1471     }
1473     /**
1474      * Constructs a Page object for the User Dashboard.
1475      *
1476      * @param   \stdClass       $user User to create Dashboard for.
1477      * @return  \moodle_page
1478      */
1479     protected function construct_user_page(\stdClass $user) {
1480         $page = new \moodle_page();
1481         $page->set_context(\context_user::instance($user->id));
1482         $page->set_pagelayout('mydashboard');
1483         $page->set_pagetype('my-index');
1484         $page->blocks->load_blocks();
1485         return $page;
1486     }
1488     /**
1489      * Get the last block on the page.
1490      *
1491      * @param \page $page Page
1492      * @return \block_html Block instance object
1493      */
1494     protected function get_last_block_on_page($page) {
1495         $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
1496         $block = end($blocks);
1498         return $block;
1499     }