fffe47509e18feaef437db6c41a102662f17be03
[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 no active enrolments is flagged for deletion.
222      */
223     public function test_flag_user_past_lastaccess_enrol_expired() {
224         $this->resetAfterTest();
226         $this->setup_basics('PT1H', 'PT1H', 'P5Y');
228         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
229         $course = $this->getDataGenerator()->create_course(['startdate' => time() - (YEARSECS * 2), 'enddate' => time() - DAYSECS]);
230         $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
232         $this->setUser($user);
233         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
234         $context = \context_block::instance($block->instance->id);
235         $this->setUser();
237         // Flag all expired contexts.
238         $manager = new \tool_dataprivacy\expired_contexts_manager();
239         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
241         // Although there is a block in the user context, everything in the user context is regarded as one.
242         $this->assertEquals(0, $flaggedcourses);
243         $this->assertEquals(1, $flaggedusers);
244     }
246     /**
247      * Ensure that a user with a recent lastaccess is not flagged for deletion.
248      */
249     public function test_flag_user_recent_lastaccess_existing_record() {
250         $this->resetAfterTest();
252         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
254         $user = $this->getDataGenerator()->create_user(['lastaccess' => time()]);
255         $usercontext = \context_user::instance($user->id);
257         // Create an existing expired_context.
258         $expiredcontext = new expired_context(0, (object) [
259                 'contextid' => $usercontext->id,
260                 'status' => expired_context::STATUS_EXPIRED,
261             ]);
262         $expiredcontext->save();
264         $this->setUser($user);
265         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
266         $context = \context_block::instance($block->instance->id);
267         $this->setUser();
269         // Flag all expired contexts.
270         $manager = new \tool_dataprivacy\expired_contexts_manager();
271         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
273         $this->assertEquals(0, $flaggedcourses);
274         $this->assertEquals(0, $flaggedusers);
276         $this->expectException('dml_missing_record_exception');
277         new expired_context($expiredcontext->get('id'));
278     }
280     /**
281      * Ensure that a user with a recent lastaccess is not flagged for deletion.
282      */
283     public function test_flag_user_retention_changed() {
284         $this->resetAfterTest();
286         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
288         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
289         $usercontext = \context_user::instance($user->id);
291         $this->setUser($user);
292         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
293         $context = \context_block::instance($block->instance->id);
294         $this->setUser();
296         // Flag all expired contexts.
297         $manager = new \tool_dataprivacy\expired_contexts_manager();
298         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
300         $this->assertEquals(0, $flaggedcourses);
301         $this->assertEquals(1, $flaggedusers);
303         $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
304         $this->assertNotFalse($expiredcontext);
306         // Increase the retention period to 5 years.
307         $userpurpose->set('retentionperiod', 'P5Y');
308         $userpurpose->save();
310         // Re-run the expiry job - the previously flagged user will be removed because the retention period has been increased.
311         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
312         $this->assertEquals(0, $flaggedcourses);
313         $this->assertEquals(0, $flaggedusers);
315         // The expiry record will now have been removed.
316         $this->expectException('dml_missing_record_exception');
317         new expired_context($expiredcontext->get('id'));
318     }
320     /**
321      * Ensure that a user with a historically expired expired block record child is cleaned up.
322      */
323     public function test_flag_user_historic_block_unapproved() {
324         $this->resetAfterTest();
326         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
328         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
329         $usercontext = \context_user::instance($user->id);
331         $this->setUser($user);
332         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
333         $blockcontext = \context_block::instance($block->instance->id);
334         $this->setUser();
336         // Create an existing expired_context which has not been approved for the block.
337         $expiredcontext = new expired_context(0, (object) [
338                 'contextid' => $blockcontext->id,
339                 'status' => expired_context::STATUS_EXPIRED,
340             ]);
341         $expiredcontext->save();
343         // Flag all expired contexts.
344         $manager = new \tool_dataprivacy\expired_contexts_manager();
345         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
347         $this->assertEquals(0, $flaggedcourses);
348         $this->assertEquals(1, $flaggedusers);
350         $expiredblockcontext = expired_context::get_record(['contextid' => $blockcontext->id]);
351         $this->assertFalse($expiredblockcontext);
353         $expiredusercontext = expired_context::get_record(['contextid' => $usercontext->id]);
354         $this->assertNotFalse($expiredusercontext);
355     }
357     /**
358      * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
359      */
360     public function test_flag_user_historic_unexpired_child() {
361         $this->resetAfterTest();
363         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
364         $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
366         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
367         $usercontext = \context_user::instance($user->id);
369         $this->setUser($user);
370         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
371         $blockcontext = \context_block::instance($block->instance->id);
372         $this->setUser();
374         // Flag all expired contexts.
375         $manager = new \tool_dataprivacy\expired_contexts_manager();
376         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
378         $this->assertEquals(0, $flaggedcourses);
379         $this->assertEquals(1, $flaggedusers);
381         $expiredcontext = expired_context::get_record(['contextid' => $usercontext->id]);
382         $this->assertNotFalse($expiredcontext);
383     }
385     /**
386      * Ensure that a course with no end date is not flagged.
387      */
388     public function test_flag_course_no_enddate() {
389         $this->resetAfterTest();
391         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
393         $course = $this->getDataGenerator()->create_course();
394         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
396         // Flag all expired contexts.
397         $manager = new \tool_dataprivacy\expired_contexts_manager();
398         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
400         $this->assertEquals(0, $flaggedcourses);
401         $this->assertEquals(0, $flaggedusers);
402     }
404     /**
405      * Ensure that a course with an end date in the distant past, but a child which is unexpired is not flagged.
406      */
407     public function test_flag_course_past_enddate_future_child() {
408         $this->resetAfterTest();
410         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'P5Y');
412         $course = $this->getDataGenerator()->create_course([
413                 'startdate' => time() - (2 * YEARSECS),
414                 'enddate' => time() - YEARSECS,
415             ]);
416         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
418         // Flag all expired contexts.
419         $manager = new \tool_dataprivacy\expired_contexts_manager();
420         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
422         $this->assertEquals(0, $flaggedcourses);
423         $this->assertEquals(0, $flaggedusers);
424     }
426     /**
427      * Ensure that a course with an end date in the distant past is flagged.
428      */
429     public function test_flag_course_past_enddate() {
430         $this->resetAfterTest();
432         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
434         $course = $this->getDataGenerator()->create_course([
435                 'startdate' => time() - (2 * YEARSECS),
436                 'enddate' => time() - YEARSECS,
437             ]);
438         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
440         // Flag all expired contexts.
441         $manager = new \tool_dataprivacy\expired_contexts_manager();
442         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
444         $this->assertEquals(2, $flaggedcourses);
445         $this->assertEquals(0, $flaggedusers);
446     }
448     /**
449      * Ensure that a course with an end date in the distant past is flagged.
450      */
451     public function test_flag_course_past_enddate_multiple() {
452         $this->resetAfterTest();
454         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
456         $course1 = $this->getDataGenerator()->create_course([
457                 'startdate' => time() - (2 * YEARSECS),
458                 'enddate' => time() - YEARSECS,
459             ]);
460         $forum1 = $this->getDataGenerator()->create_module('forum', ['course' => $course1->id]);
462         $course2 = $this->getDataGenerator()->create_course([
463                 'startdate' => time() - (2 * YEARSECS),
464                 'enddate' => time() - YEARSECS,
465             ]);
466         $forum2 = $this->getDataGenerator()->create_module('forum', ['course' => $course2->id]);
468         // Flag all expired contexts.
469         $manager = new \tool_dataprivacy\expired_contexts_manager();
470         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
472         $this->assertEquals(4, $flaggedcourses);
473         $this->assertEquals(0, $flaggedusers);
474     }
476     /**
477      * Ensure that a course with an end date in the future is not flagged.
478      */
479     public function test_flag_course_future_enddate() {
480         $this->resetAfterTest();
482         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
484         $course = $this->getDataGenerator()->create_course(['enddate' => time() + YEARSECS]);
485         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
487         // Flag all expired contexts.
488         $manager = new \tool_dataprivacy\expired_contexts_manager();
489         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
491         $this->assertEquals(0, $flaggedcourses);
492         $this->assertEquals(0, $flaggedusers);
493     }
495     /**
496      * Ensure that a course with an end date in the future is not flagged.
497      */
498     public function test_flag_course_recent_unexpired_enddate() {
499         $this->resetAfterTest();
501         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
503         $course = $this->getDataGenerator()->create_course(['enddate' => time() - 1]);
505         // Flag all expired contexts.
506         $manager = new \tool_dataprivacy\expired_contexts_manager();
507         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
509         $this->assertEquals(0, $flaggedcourses);
510         $this->assertEquals(0, $flaggedusers);
511     }
513     /**
514      * Ensure that a site not setup will not process anything.
515      */
516     public function test_process_not_setup() {
517         $this->resetAfterTest();
519         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
520         $usercontext = \context_user::instance($user->id);
522         // Create an existing expired_context.
523         $expiredcontext = new expired_context(0, (object) [
524                 'contextid' => $usercontext->id,
525                 'status' => expired_context::STATUS_EXPIRED,
526             ]);
527         $expiredcontext->save();
529         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
530             ->setMethods([
531                 'delete_data_for_user',
532                 'delete_data_for_all_users_in_context',
533             ])
534             ->getMock();
535         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
536         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
538         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
539             ->setMethods(['get_privacy_manager'])
540             ->getMock();
541         $manager->set_progress(new \null_progress_trace());
543         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
544         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
546         $this->assertEquals(0, $processedcourses);
547         $this->assertEquals(0, $processedusers);
548     }
550     /**
551      * Ensure that a user with no lastaccess is not flagged for deletion.
552      */
553     public function test_process_none_approved() {
554         $this->resetAfterTest();
556         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
558         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
559         $usercontext = \context_user::instance($user->id);
561         // Create an existing expired_context.
562         $expiredcontext = new expired_context(0, (object) [
563                 'contextid' => $usercontext->id,
564                 'status' => expired_context::STATUS_EXPIRED,
565             ]);
566         $expiredcontext->save();
568         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
569             ->setMethods([
570                 'delete_data_for_user',
571                 'delete_data_for_all_users_in_context',
572             ])
573             ->getMock();
574         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
575         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
577         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
578             ->setMethods(['get_privacy_manager'])
579             ->getMock();
580         $manager->set_progress(new \null_progress_trace());
582         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
583         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
585         $this->assertEquals(0, $processedcourses);
586         $this->assertEquals(0, $processedusers);
587     }
589     /**
590      * Ensure that a user with no lastaccess is not flagged for deletion.
591      */
592     public function test_process_no_context() {
593         $this->resetAfterTest();
595         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
597         // Create an existing expired_context.
598         $expiredcontext = new expired_context(0, (object) [
599                 'contextid' => -1,
600                 'status' => expired_context::STATUS_APPROVED,
601             ]);
602         $expiredcontext->save();
604         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
605             ->setMethods([
606                 'delete_data_for_user',
607                 'delete_data_for_all_users_in_context',
608             ])
609             ->getMock();
610         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
611         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
613         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
614             ->setMethods(['get_privacy_manager'])
615             ->getMock();
616         $manager->set_progress(new \null_progress_trace());
618         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
619         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
621         $this->assertEquals(0, $processedcourses);
622         $this->assertEquals(0, $processedusers);
624         $this->expectException('dml_missing_record_exception');
625         new expired_context($expiredcontext->get('id'));
626     }
628     /**
629      * Ensure that a user context previously flagged as approved is removed.
630      */
631     public function test_process_user_context() {
632         $this->resetAfterTest();
634         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
636         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
637         $usercontext = \context_user::instance($user->id);
639         $this->setUser($user);
640         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
641         $blockcontext = \context_block::instance($block->instance->id);
642         $this->setUser();
644         // Create an existing expired_context.
645         $expiredcontext = new expired_context(0, (object) [
646                 'contextid' => $usercontext->id,
647                 'status' => expired_context::STATUS_APPROVED,
648             ]);
649         $expiredcontext->save();
651         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
652             ->setMethods([
653                 'delete_data_for_user',
654                 'delete_data_for_all_users_in_context',
655             ])
656             ->getMock();
657         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
658         $mockprivacymanager->expects($this->exactly(2))
659             ->method('delete_data_for_all_users_in_context')
660             ->withConsecutive(
661                 [$blockcontext],
662                 [$usercontext]
663             );
665         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
666             ->setMethods(['get_privacy_manager'])
667             ->getMock();
668         $manager->set_progress(new \null_progress_trace());
670         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
671         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
673         $this->assertEquals(0, $processedcourses);
674         $this->assertEquals(1, $processedusers);
676         $updatedcontext = new expired_context($expiredcontext->get('id'));
677         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
679         // Flag all expired contexts again.
680         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
682         $this->assertEquals(0, $flaggedcourses);
683         $this->assertEquals(0, $flaggedusers);
685         // Ensure that the deleted context record is still present.
686         $updatedcontext = new expired_context($expiredcontext->get('id'));
687         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
688     }
690     /**
691      * Ensure that a course context previously flagged as approved is removed.
692      */
693     public function test_process_course_context() {
694         $this->resetAfterTest();
696         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
698         $course = $this->getDataGenerator()->create_course([
699                 'startdate' => time() - (2 * YEARSECS),
700                 'enddate' => time() - YEARSECS,
701             ]);
702         $coursecontext = \context_course::instance($course->id);
704         // Create an existing expired_context.
705         $expiredcontext = new expired_context(0, (object) [
706                 'contextid' => $coursecontext->id,
707                 'status' => expired_context::STATUS_APPROVED,
708             ]);
709         $expiredcontext->save();
711         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
712             ->setMethods([
713                 'delete_data_for_user',
714                 'delete_data_for_all_users_in_context',
715             ])
716             ->getMock();
717         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
718         $mockprivacymanager->expects($this->once())->method('delete_data_for_all_users_in_context');
720         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
721             ->setMethods(['get_privacy_manager'])
722             ->getMock();
723         $manager->set_progress(new \null_progress_trace());
725         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
726         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
728         $this->assertEquals(1, $processedcourses);
729         $this->assertEquals(0, $processedusers);
731         $updatedcontext = new expired_context($expiredcontext->get('id'));
732         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
733     }
735     /**
736      * Ensure that a user context previously flagged as approved is not removed if the user then logs in.
737      */
738     public function test_process_user_context_logged_in_after_approval() {
739         $this->resetAfterTest();
741         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
743         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
744         $usercontext = \context_user::instance($user->id);
746         $this->setUser($user);
747         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
748         $context = \context_block::instance($block->instance->id);
749         $this->setUser();
751         // Create an existing expired_context.
752         $expiredcontext = new expired_context(0, (object) [
753                 'contextid' => $usercontext->id,
754                 'status' => expired_context::STATUS_APPROVED,
755             ]);
756         $expiredcontext->save();
758         // Now bump the user's last login time.
759         $this->setUser($user);
760         user_accesstime_log();
761         $this->setUser();
763         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
764             ->setMethods([
765                 'delete_data_for_user',
766                 'delete_data_for_all_users_in_context',
767             ])
768             ->getMock();
769         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
770         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
772         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
773             ->setMethods(['get_privacy_manager'])
774             ->getMock();
775         $manager->set_progress(new \null_progress_trace());
777         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
778         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
780         $this->assertEquals(0, $processedcourses);
781         $this->assertEquals(0, $processedusers);
783         $this->expectException('dml_missing_record_exception');
784         new expired_context($expiredcontext->get('id'));
785     }
787     /**
788      * Ensure that a user context previously flagged as approved is not removed if the purpose has changed.
789      */
790     public function test_process_user_context_changed_after_approved() {
791         $this->resetAfterTest();
793         $this->setup_basics('PT1H', 'PT1H', 'PT1H');
795         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
796         $usercontext = \context_user::instance($user->id);
798         $this->setUser($user);
799         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
800         $context = \context_block::instance($block->instance->id);
801         $this->setUser();
803         // Create an existing expired_context.
804         $expiredcontext = new expired_context(0, (object) [
805                 'contextid' => $usercontext->id,
806                 'status' => expired_context::STATUS_APPROVED,
807             ]);
808         $expiredcontext->save();
810         // Now make the user a site admin.
811         $admins = explode(',', get_config('moodle', 'siteadmins'));
812         $admins[] = $user->id;
813         set_config('siteadmins', implode(',', $admins));
815         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
816             ->setMethods([
817                 'delete_data_for_user',
818                 'delete_data_for_all_users_in_context',
819             ])
820             ->getMock();
821         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
822         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
824         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
825             ->setMethods(['get_privacy_manager'])
826             ->getMock();
827         $manager->set_progress(new \null_progress_trace());
829         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
830         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
832         $this->assertEquals(0, $processedcourses);
833         $this->assertEquals(0, $processedusers);
835         $this->expectException('dml_missing_record_exception');
836         new expired_context($expiredcontext->get('id'));
837     }
839     /**
840      * Ensure that a user with a historically expired expired block record child is cleaned up.
841      */
842     public function test_process_user_historic_block_unapproved() {
843         $this->resetAfterTest();
845         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
847         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
848         $usercontext = \context_user::instance($user->id);
850         $this->setUser($user);
851         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
852         $blockcontext = \context_block::instance($block->instance->id);
853         $this->setUser();
855         // Create an expired_context for the user.
856         $expiredusercontext = new expired_context(0, (object) [
857                 'contextid' => $usercontext->id,
858                 'status' => expired_context::STATUS_APPROVED,
859             ]);
860         $expiredusercontext->save();
862         // Create an existing expired_context which has not been approved for the block.
863         $expiredblockcontext = new expired_context(0, (object) [
864                 'contextid' => $blockcontext->id,
865                 'status' => expired_context::STATUS_EXPIRED,
866             ]);
867         $expiredblockcontext->save();
869         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
870             ->setMethods([
871                 'delete_data_for_user',
872                 'delete_data_for_all_users_in_context',
873             ])
874             ->getMock();
875         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
876         $mockprivacymanager->expects($this->exactly(2))
877             ->method('delete_data_for_all_users_in_context')
878             ->withConsecutive(
879                 [$blockcontext],
880                 [$usercontext]
881             );
883         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
884             ->setMethods(['get_privacy_manager'])
885             ->getMock();
886         $manager->set_progress(new \null_progress_trace());
888         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
889         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
891         $this->assertEquals(0, $processedcourses);
892         $this->assertEquals(1, $processedusers);
894         $updatedcontext = new expired_context($expiredusercontext->get('id'));
895         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
896     }
898     /**
899      * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
900      */
901     public function test_process_user_historic_unexpired_child() {
902         $this->resetAfterTest();
904         list($systempurpose, $userpurpose, $coursepurpose) = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
905         $blockpurpose = $this->create_and_set_purpose_for_contextlevel('P5Y', CONTEXT_BLOCK);
907         $user = $this->getDataGenerator()->create_user(['lastaccess' => time() - DAYSECS]);
908         $usercontext = \context_user::instance($user->id);
910         $this->setUser($user);
911         $block = $this->create_user_block('Title', 'Content', FORMAT_PLAIN);
912         $blockcontext = \context_block::instance($block->instance->id);
913         $this->setUser();
915         // Create an expired_context for the user.
916         $expiredusercontext = new expired_context(0, (object) [
917                 'contextid' => $usercontext->id,
918                 'status' => expired_context::STATUS_APPROVED,
919             ]);
920         $expiredusercontext->save();
922         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
923             ->setMethods([
924                 'delete_data_for_user',
925                 'delete_data_for_all_users_in_context',
926             ])
927             ->getMock();
928         $mockprivacymanager->expects($this->atLeastOnce())->method('delete_data_for_user');
929         $mockprivacymanager->expects($this->exactly(2))
930             ->method('delete_data_for_all_users_in_context')
931             ->withConsecutive(
932                 [$blockcontext],
933                 [$usercontext]
934             );
936         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
937             ->setMethods(['get_privacy_manager'])
938             ->getMock();
939         $manager->set_progress(new \null_progress_trace());
941         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
942         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
944         $this->assertEquals(0, $processedcourses);
945         $this->assertEquals(1, $processedusers);
947         $updatedcontext = new expired_context($expiredusercontext->get('id'));
948         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
949     }
951     /**
952      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
953      * updated.
954      */
955     public function test_process_course_context_updated() {
956         $this->resetAfterTest();
958         $purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
960         $course = $this->getDataGenerator()->create_course([
961                 'startdate' => time() - (2 * YEARSECS),
962                 'enddate' => time() - YEARSECS,
963             ]);
964         $coursecontext = \context_course::instance($course->id);
965         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
967         // Create an existing expired_context.
968         $expiredcontext = new expired_context(0, (object) [
969                 'contextid' => $coursecontext->id,
970                 'status' => expired_context::STATUS_APPROVED,
971             ]);
972         $expiredcontext->save();
974         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
975             ->setMethods([
976                 'delete_data_for_user',
977                 'delete_data_for_all_users_in_context',
978             ])
979             ->getMock();
980         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
981         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
983         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
984             ->setMethods(['get_privacy_manager'])
985             ->getMock();
986         $manager->set_progress(new \null_progress_trace());
987         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
989         $coursepurpose = $purposes[2];
990         $coursepurpose->set('retentionperiod', 'P5Y');
991         $coursepurpose->save();
993         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
995         $this->assertEquals(0, $processedcourses);
996         $this->assertEquals(0, $processedusers);
998         $updatedcontext = new expired_context($expiredcontext->get('id'));
1000         // No change - we just can't process it until the children have finished.
1001         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1002     }
1004     /**
1005      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1006      * updated.
1007      */
1008     public function test_process_course_context_outstanding_children() {
1009         $this->resetAfterTest();
1011         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1013         $course = $this->getDataGenerator()->create_course([
1014                 'startdate' => time() - (2 * YEARSECS),
1015                 'enddate' => time() - YEARSECS,
1016             ]);
1017         $coursecontext = \context_course::instance($course->id);
1018         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1020         // Create an existing expired_context.
1021         $expiredcontext = new expired_context(0, (object) [
1022                 'contextid' => $coursecontext->id,
1023                 'status' => expired_context::STATUS_APPROVED,
1024             ]);
1025         $expiredcontext->save();
1027         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1028             ->setMethods([
1029                 'delete_data_for_user',
1030                 'delete_data_for_all_users_in_context',
1031             ])
1032             ->getMock();
1033         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1034         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1036         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1037             ->setMethods(['get_privacy_manager'])
1038             ->getMock();
1039         $manager->set_progress(new \null_progress_trace());
1041         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1042         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1044         $this->assertEquals(0, $processedcourses);
1045         $this->assertEquals(0, $processedusers);
1047         $updatedcontext = new expired_context($expiredcontext->get('id'));
1049         // No change - we just can't process it until the children have finished.
1050         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1051     }
1053     /**
1054      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1055      * updated.
1056      */
1057     public function test_process_course_context_pending_children() {
1058         $this->resetAfterTest();
1060         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1062         $course = $this->getDataGenerator()->create_course([
1063                 'startdate' => time() - (2 * YEARSECS),
1064                 'enddate' => time() - YEARSECS,
1065             ]);
1066         $coursecontext = \context_course::instance($course->id);
1067         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1068         $cm = get_coursemodule_from_instance('forum', $forum->id);
1069         $forumcontext = \context_module::instance($cm->id);
1071         // Create an existing expired_context for the course.
1072         $expiredcoursecontext = new expired_context(0, (object) [
1073                 'contextid' => $coursecontext->id,
1074                 'status' => expired_context::STATUS_APPROVED,
1075             ]);
1076         $expiredcoursecontext->save();
1078         // And for the forum.
1079         $expiredforumcontext = new expired_context(0, (object) [
1080                 'contextid' => $forumcontext->id,
1081                 'status' => expired_context::STATUS_EXPIRED,
1082             ]);
1083         $expiredforumcontext->save();
1085         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1086             ->setMethods([
1087                 'delete_data_for_user',
1088                 'delete_data_for_all_users_in_context',
1089             ])
1090             ->getMock();
1091         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1092         $mockprivacymanager->expects($this->never())->method('delete_data_for_all_users_in_context');
1094         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1095             ->setMethods(['get_privacy_manager'])
1096             ->getMock();
1097         $manager->set_progress(new \null_progress_trace());
1099         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1100         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1102         $this->assertEquals(0, $processedcourses);
1103         $this->assertEquals(0, $processedusers);
1105         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1107         // No change - we just can't process it until the children have finished.
1108         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1109     }
1111     /**
1112      * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
1113      * updated.
1114      */
1115     public function test_process_course_context_approved_children() {
1116         $this->resetAfterTest();
1118         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1120         $course = $this->getDataGenerator()->create_course([
1121                 'startdate' => time() - (2 * YEARSECS),
1122                 'enddate' => time() - YEARSECS,
1123             ]);
1124         $coursecontext = \context_course::instance($course->id);
1125         $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1126         $cm = get_coursemodule_from_instance('forum', $forum->id);
1127         $forumcontext = \context_module::instance($cm->id);
1129         // Create an existing expired_context for the course.
1130         $expiredcoursecontext = new expired_context(0, (object) [
1131                 'contextid' => $coursecontext->id,
1132                 'status' => expired_context::STATUS_APPROVED,
1133             ]);
1134         $expiredcoursecontext->save();
1136         // And for the forum.
1137         $expiredforumcontext = new expired_context(0, (object) [
1138                 'contextid' => $forumcontext->id,
1139                 'status' => expired_context::STATUS_APPROVED,
1140             ]);
1141         $expiredforumcontext->save();
1143         $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1144             ->setMethods([
1145                 'delete_data_for_user',
1146                 'delete_data_for_all_users_in_context',
1147             ])
1148             ->getMock();
1149         $mockprivacymanager->expects($this->never())->method('delete_data_for_user');
1150         $mockprivacymanager->expects($this->exactly(2))
1151             ->method('delete_data_for_all_users_in_context')
1152             ->withConsecutive(
1153                 [$forumcontext],
1154                 [$coursecontext]
1155             );
1157         $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1158             ->setMethods(['get_privacy_manager'])
1159             ->getMock();
1160         $manager->set_progress(new \null_progress_trace());
1162         $manager->method('get_privacy_manager')->willReturn($mockprivacymanager);
1164         // Initially only the forum will be processed.
1165         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1167         $this->assertEquals(1, $processedcourses);
1168         $this->assertEquals(0, $processedusers);
1170         $updatedcontext = new expired_context($expiredforumcontext->get('id'));
1171         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1173         // The course won't have been processed yet.
1174         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1175         $this->assertEquals(expired_context::STATUS_APPROVED, $updatedcontext->get('status'));
1177         // A subsequent run will cause the course to processed as it is no longer dependent upon the child contexts.
1178         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1180         $this->assertEquals(1, $processedcourses);
1181         $this->assertEquals(0, $processedusers);
1182         $updatedcontext = new expired_context($expiredcoursecontext->get('id'));
1183         $this->assertEquals(expired_context::STATUS_CLEANED, $updatedcontext->get('status'));
1184     }
1186     /**
1187      * Test that the can_process_deletion function returns expected results.
1188      *
1189      * @dataProvider    can_process_deletion_provider
1190      * @param       int     $status
1191      * @param       bool    $expected
1192      */
1193     public function test_can_process_deletion($status, $expected) {
1194         $purpose = new expired_context(0, (object) [
1195             'status' => $status,
1197             'contextid' => \context_system::instance()->id,
1198         ]);
1200         $this->assertEquals($expected, $purpose->can_process_deletion());
1201     }
1203     /**
1204      * Data provider for the can_process_deletion tests.
1205      *
1206      * @return  array
1207      */
1208     public function can_process_deletion_provider() : array {
1209         return [
1210             'Pending' => [
1211                 expired_context::STATUS_EXPIRED,
1212                 false,
1213             ],
1214             'Approved' => [
1215                 expired_context::STATUS_APPROVED,
1216                 true,
1217             ],
1218             'Complete' => [
1219                 expired_context::STATUS_CLEANED,
1220                 false,
1221             ],
1222         ];
1223     }
1225     /**
1226      * Test that the is_complete function returns expected results.
1227      *
1228      * @dataProvider        is_complete_provider
1229      * @param       int     $status
1230      * @param       bool    $expected
1231      */
1232     public function test_is_complete($status, $expected) {
1233         $purpose = new expired_context(0, (object) [
1234             'status' => $status,
1235             'contextid' => \context_system::instance()->id,
1236         ]);
1238         $this->assertEquals($expected, $purpose->is_complete());
1239     }
1241     /**
1242      * Data provider for the is_complete tests.
1243      *
1244      * @return  array
1245      */
1246     public function is_complete_provider() : array {
1247         return [
1248             'Pending' => [
1249                 expired_context::STATUS_EXPIRED,
1250                 false,
1251             ],
1252             'Approved' => [
1253                 expired_context::STATUS_APPROVED,
1254                 false,
1255             ],
1256             'Complete' => [
1257                 expired_context::STATUS_CLEANED,
1258                 true,
1259             ],
1260         ];
1261     }
1263     /**
1264      * Ensure that any orphaned records are removed once the context has been removed.
1265      */
1266     public function test_orphaned_records_are_cleared() {
1267         $this->resetAfterTest();
1269         $this->setup_basics('PT1H', 'PT1H', 'PT1H', 'PT1H');
1271         $course = $this->getDataGenerator()->create_course([
1272                 'startdate' => time() - (2 * YEARSECS),
1273                 'enddate' => time() - YEARSECS,
1274             ]);
1275         $context = \context_course::instance($course->id);
1277         // Flag all expired contexts.
1278         $manager = new \tool_dataprivacy\expired_contexts_manager();
1279         $manager->set_progress(new \null_progress_trace());
1280         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
1282         $this->assertEquals(1, $flaggedcourses);
1283         $this->assertEquals(0, $flaggedusers);
1285         // Ensure that the record currently exists.
1286         $expiredcontext =  expired_context::get_record(['contextid' => $context->id]);
1287         $this->assertNotFalse($expiredcontext);
1289         // Approve it.
1290         $expiredcontext->set('status', expired_context::STATUS_APPROVED)->save();
1292         // Process deletions
1293         list($processedcourses, $processedusers) = $manager->process_approved_deletions();
1295         $this->assertEquals(1, $processedcourses);
1296         $this->assertEquals(0, $processedusers);
1298         // Ensure that the record still exists.
1299         $expiredcontext =  expired_context::get_record(['contextid' => $context->id]);
1300         $this->assertNotFalse($expiredcontext);
1302         // Remove the actual course.
1303         delete_course($course->id, false);
1305         // The record will still exist until we flag it again.
1306         $expiredcontext =  expired_context::get_record(['contextid' => $context->id]);
1307         $this->assertNotFalse($expiredcontext);
1309         list($flaggedcourses, $flaggedusers) = $manager->flag_expired_contexts();
1310         $expiredcontext =  expired_context::get_record(['contextid' => $context->id]);
1311         $this->assertFalse($expiredcontext);
1312     }
1314     /**
1315      * Ensure that the progres tracer works as expected out of the box.
1316      */
1317     public function test_progress_tracer_default() {
1318         $manager = new \tool_dataprivacy\expired_contexts_manager();
1320         $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
1321         $rcm = $rc->getMethod('get_progress');
1323         $rcm->setAccessible(true);
1324         $this->assertInstanceOf(\text_progress_trace::class, $rcm->invoke($manager));
1325     }
1327     /**
1328      * Ensure that the progres tracer works as expected when given a specific traer.
1329      */
1330     public function test_progress_tracer_set() {
1331         $manager = new \tool_dataprivacy\expired_contexts_manager();
1332         $mytrace = new \null_progress_trace();
1333         $manager->set_progress($mytrace);
1335         $rc = new \ReflectionClass(\tool_dataprivacy\expired_contexts_manager::class);
1336         $rcm = $rc->getMethod('get_progress');
1338         $rcm->setAccessible(true);
1339         $this->assertSame($mytrace, $rcm->invoke($manager));
1340     }
1342     /**
1343      * Creates an HTML block on a user.
1344      *
1345      * @param   string  $title
1346      * @param   string  $body
1347      * @param   string  $format
1348      * @return  \block_instance
1349      */
1350     protected function create_user_block($title, $body, $format) {
1351         global $USER;
1353         $configdata = (object) [
1354             'title' => $title,
1355             'text' => [
1356                 'itemid' => 19,
1357                 'text' => $body,
1358                 'format' => $format,
1359             ],
1360         ];
1362         $this->create_block($this->construct_user_page($USER));
1363         $block = $this->get_last_block_on_page($this->construct_user_page($USER));
1364         $block = block_instance('html', $block->instance);
1365         $block->instance_config_save((object) $configdata);
1367         return $block;
1368     }
1370     /**
1371      * Creates an HTML block on a page.
1372      *
1373      * @param \page $page Page
1374      */
1375     protected function create_block($page) {
1376         $page->blocks->add_block_at_end_of_default_region('html');
1377     }
1379     /**
1380      * Constructs a Page object for the User Dashboard.
1381      *
1382      * @param   \stdClass       $user User to create Dashboard for.
1383      * @return  \moodle_page
1384      */
1385     protected function construct_user_page(\stdClass $user) {
1386         $page = new \moodle_page();
1387         $page->set_context(\context_user::instance($user->id));
1388         $page->set_pagelayout('mydashboard');
1389         $page->set_pagetype('my-index');
1390         $page->blocks->load_blocks();
1391         return $page;
1392     }
1394     /**
1395      * Get the last block on the page.
1396      *
1397      * @param \page $page Page
1398      * @return \block_html Block instance object
1399      */
1400     protected function get_last_block_on_page($page) {
1401         $blocks = $page->blocks->get_blocks_for_region($page->blocks->get_default_region());
1402         $block = end($blocks);
1404         return $block;
1405     }