2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Expired contexts tests.
20 * @package tool_dataprivacy
21 * @copyright 2018 David Monllao
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
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();
36 * Expired contexts tests.
38 * @package tool_dataprivacy
39 * @copyright 2018 David Monllao
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
45 * Setup the basics with the specified retention period.
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.
52 protected function setup_basics(string $system, string $user, string $course, string $activity = null) : array {
53 $this->resetAfterTest();
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);
67 * Create a retention period and set it for the specified context level.
69 * @param string $retention
70 * @param int $contextlevel
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',
81 $cat = new category(0, (object) ['name' => 'Test category']);
84 if ($contextlevel <= CONTEXT_USER) {
86 'purposeid' => $purpose->get('id'),
87 'categoryid' => $cat->get('id'),
88 'contextlevel' => $contextlevel,
90 api::set_contextlevel($record);
92 list($purposevar, ) = data_registry::var_names_from_context(
93 \context_helper::get_class_for_level(CONTEXT_COURSE)
95 set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
102 * Ensure that a user with no lastaccess is not flagged for deletion.
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);
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);
123 * Ensure that a user with no lastaccess is not flagged for deletion.
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);
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);
146 * Ensure that a user with a recent lastaccess is not flagged for deletion.
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);
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);
169 * Ensure that a user with a lastaccess in the past is flagged for deletion.
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);
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);
193 * Ensure that a user with a lastaccess in the past but active enrolments is not flagged for deletion.
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);
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);
221 * Ensure that a user with a lastaccess in the past and expired enrolments.
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);
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);
249 * Ensure that a user with a lastaccess in the past and expired enrolments.
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);
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);
277 * Ensure that a user with a lastaccess in the past and enrolments without a course end date are respected
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);
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);
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.
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);
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);
341 * Ensure that a user with a recent lastaccess is not flagged for deletion.
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,
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);
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'));
375 * Ensure that a user with a recent lastaccess is not flagged for deletion.
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);
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'));
415 * Ensure that a user with a historically expired expired block record child is cleaned up.
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);
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,
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);
452 * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
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);
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);
480 * Ensure that a course with no end date is not flagged.
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);
499 * Ensure that a course with an end date in the distant past, but a child which is unexpired is not flagged.
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,
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);
521 * Ensure that a course with an end date in the distant past is flagged.
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,
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);
543 * Ensure that a course with an end date in the distant past is flagged.
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,
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,
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);
571 * Ensure that a course with an end date in the future is not flagged.
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);
590 * Ensure that a course with an end date in the future is not flagged.
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);
608 * Ensure that a site not setup will not process anything.
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,
621 $expiredcontext->save();
623 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
625 'delete_data_for_user',
626 'delete_data_for_all_users_in_context',
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'])
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);
645 * Ensure that a user with no lastaccess is not flagged for deletion.
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,
660 $expiredcontext->save();
662 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
664 'delete_data_for_user',
665 'delete_data_for_all_users_in_context',
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'])
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);
684 * Ensure that a user with no lastaccess is not flagged for deletion.
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) [
694 'status' => expired_context::STATUS_APPROVED,
696 $expiredcontext->save();
698 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
700 'delete_data_for_user',
701 'delete_data_for_all_users_in_context',
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'])
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'));
723 * Ensure that a user context previously flagged as approved is removed.
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);
738 // Create an existing expired_context.
739 $expiredcontext = new expired_context(0, (object) [
740 'contextid' => $usercontext->id,
741 'status' => expired_context::STATUS_APPROVED,
743 $expiredcontext->save();
745 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
747 'delete_data_for_user',
748 'delete_data_for_all_users_in_context',
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')
759 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
760 ->setMethods(['get_privacy_manager'])
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'));
785 * Ensure that a course context previously flagged as approved is removed.
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,
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,
803 $expiredcontext->save();
805 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
807 'delete_data_for_user',
808 'delete_data_for_all_users_in_context',
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'])
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'));
830 * Ensure that a user context previously flagged as approved is not removed if the user then logs in.
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);
845 // Create an existing expired_context.
846 $expiredcontext = new expired_context(0, (object) [
847 'contextid' => $usercontext->id,
848 'status' => expired_context::STATUS_APPROVED,
850 $expiredcontext->save();
852 // Now bump the user's last login time.
853 $this->setUser($user);
854 user_accesstime_log();
857 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
859 'delete_data_for_user',
860 'delete_data_for_all_users_in_context',
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'])
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'));
882 * Ensure that a user context previously flagged as approved is not removed if the purpose has changed.
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);
897 // Create an existing expired_context.
898 $expiredcontext = new expired_context(0, (object) [
899 'contextid' => $usercontext->id,
900 'status' => expired_context::STATUS_APPROVED,
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)
911 'delete_data_for_user',
912 'delete_data_for_all_users_in_context',
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'])
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'));
934 * Ensure that a user with a historically expired expired block record child is cleaned up.
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);
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,
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,
961 $expiredblockcontext->save();
963 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
965 'delete_data_for_user',
966 'delete_data_for_all_users_in_context',
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')
977 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
978 ->setMethods(['get_privacy_manager'])
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'));
993 * Ensure that a user with a block which has a default retention period which has not expired, is still expired.
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);
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,
1014 $expiredusercontext->save();
1016 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1018 'delete_data_for_user',
1019 'delete_data_for_all_users_in_context',
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')
1030 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1031 ->setMethods(['get_privacy_manager'])
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'));
1046 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
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,
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,
1066 $expiredcontext->save();
1068 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1070 'delete_data_for_user',
1071 'delete_data_for_all_users_in_context',
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'])
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'));
1099 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
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,
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,
1119 $expiredcontext->save();
1121 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1123 'delete_data_for_user',
1124 'delete_data_for_all_users_in_context',
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'])
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'));
1148 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
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,
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,
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,
1177 $expiredforumcontext->save();
1179 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1181 'delete_data_for_user',
1182 'delete_data_for_all_users_in_context',
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'])
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'));
1206 * Ensure that a course context previously flagged as approved for deletion which now has an unflagged child, is
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,
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,
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,
1235 $expiredforumcontext->save();
1237 $mockprivacymanager = $this->getMockBuilder(\core_privacy\manager::class)
1239 'delete_data_for_user',
1240 'delete_data_for_all_users_in_context',
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')
1251 $manager = $this->getMockBuilder(\tool_dataprivacy\expired_contexts_manager::class)
1252 ->setMethods(['get_privacy_manager'])
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'));
1281 * Test that the can_process_deletion function returns expected results.
1283 * @dataProvider can_process_deletion_provider
1284 * @param int $status
1285 * @param bool $expected
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,
1294 $this->assertEquals($expected, $purpose->can_process_deletion());
1298 * Data provider for the can_process_deletion tests.
1302 public function can_process_deletion_provider() : array {
1305 expired_context::STATUS_EXPIRED,
1309 expired_context::STATUS_APPROVED,
1313 expired_context::STATUS_CLEANED,
1320 * Test that the is_complete function returns expected results.
1322 * @dataProvider is_complete_provider
1323 * @param int $status
1324 * @param bool $expected
1326 public function test_is_complete($status, $expected) {
1327 $purpose = new expired_context(0, (object) [
1328 'status' => $status,
1329 'contextid' => \context_system::instance()->id,
1332 $this->assertEquals($expected, $purpose->is_complete());
1336 * Data provider for the is_complete tests.
1340 public function is_complete_provider() : array {
1343 expired_context::STATUS_EXPIRED,
1347 expired_context::STATUS_APPROVED,
1351 expired_context::STATUS_CLEANED,
1358 * Ensure that any orphaned records are removed once the context has been removed.
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,
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);
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);
1409 * Ensure that the progres tracer works as expected out of the box.
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));
1422 * Ensure that the progres tracer works as expected when given a specific traer.
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));
1437 * Creates an HTML block on a user.
1439 * @param string $title
1440 * @param string $body
1441 * @param string $format
1442 * @return \block_instance
1444 protected function create_user_block($title, $body, $format) {
1447 $configdata = (object) [
1452 'format' => $format,
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);
1465 * Creates an HTML block on a page.
1467 * @param \page $page Page
1469 protected function create_block($page) {
1470 $page->blocks->add_block_at_end_of_default_region('html');
1474 * Constructs a Page object for the User Dashboard.
1476 * @param \stdClass $user User to create Dashboard for.
1477 * @return \moodle_page
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();
1489 * Get the last block on the page.
1491 * @param \page $page Page
1492 * @return \block_html Block instance object
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);