weekly back-to-dev release 5.0dev
[moodle.git] / calendar / tests / event_vault_test.php
blobc8c29368325b5f980d8af8f21ee0da0bc244081a
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.
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 namespace core_calendar;
19 use action_event_test_factory;
20 use core_calendar\local\event\data_access\event_vault;
21 use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
23 defined('MOODLE_INTERNAL') || die();
25 global $CFG;
26 require_once($CFG->dirroot . '/calendar/tests/helpers.php');
28 /**
29 * This file contains the class that handles testing of the calendar event vault.
31 * @package core_calendar
32 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 class event_vault_test extends \advanced_testcase {
37 /**
38 * Test that get_action_events_by_timesort returns events after the
39 * provided timesort value.
41 public function test_get_action_events_by_timesort_after_time(): void {
42 $this->resetAfterTest(true);
44 $user = $this->getDataGenerator()->create_user();
45 $factory = new action_event_test_factory();
46 $strategy = new raw_event_retrieval_strategy();
47 $vault = new event_vault($factory, $strategy);
49 $this->setUser($user);
51 for ($i = 1; $i < 6; $i++) {
52 create_event([
53 'name' => sprintf('Event %d', $i),
54 'eventtype' => 'user',
55 'userid' => $user->id,
56 'timesort' => $i,
57 'type' => CALENDAR_EVENT_TYPE_ACTION
58 ]);
61 $events = $vault->get_action_events_by_timesort($user, 3);
63 $this->assertCount(3, $events);
64 $this->assertEquals('Event 3', $events[0]->get_name());
65 $this->assertEquals('Event 4', $events[1]->get_name());
66 $this->assertEquals('Event 5', $events[2]->get_name());
68 $events = $vault->get_action_events_by_timesort($user, 3, null, null, 1);
70 $this->assertCount(1, $events);
71 $this->assertEquals('Event 3', $events[0]->get_name());
73 $events = $vault->get_action_events_by_timesort($user, 6);
75 $this->assertCount(0, $events);
78 /**
79 * Test that get_action_events_by_timesort returns events before the
80 * provided timesort value.
82 public function test_get_action_events_by_timesort_before_time(): void {
83 $this->resetAfterTest(true);
84 $this->setAdminuser();
86 $user = $this->getDataGenerator()->create_user();
87 $factory = new action_event_test_factory();
88 $strategy = new raw_event_retrieval_strategy();
89 $vault = new event_vault($factory, $strategy);
91 for ($i = 1; $i < 6; $i++) {
92 create_event([
93 'name' => sprintf('Event %d', $i),
94 'eventtype' => 'user',
95 'userid' => $user->id,
96 'timesort' => $i,
97 'type' => CALENDAR_EVENT_TYPE_ACTION,
98 'courseid' => 1
99 ]);
102 $events = $vault->get_action_events_by_timesort($user, null, 3);
104 $this->assertCount(3, $events);
105 $this->assertEquals('Event 1', $events[0]->get_name());
106 $this->assertEquals('Event 2', $events[1]->get_name());
107 $this->assertEquals('Event 3', $events[2]->get_name());
109 $events = $vault->get_action_events_by_timesort($user, null, 3, null, 1);
111 $this->assertCount(1, $events);
112 $this->assertEquals('Event 1', $events[0]->get_name());
114 $events = $vault->get_action_events_by_timesort($user, 6);
116 $this->assertCount(0, $events);
120 * Test that get_action_events_by_timesort returns events between the
121 * provided timesort values.
123 public function test_get_action_events_by_timesort_between_time(): void {
124 $this->resetAfterTest(true);
125 $this->setAdminuser();
127 $user = $this->getDataGenerator()->create_user();
128 $factory = new action_event_test_factory();
129 $strategy = new raw_event_retrieval_strategy();
130 $vault = new event_vault($factory, $strategy);
132 for ($i = 1; $i < 6; $i++) {
133 create_event([
134 'name' => sprintf('Event %d', $i),
135 'eventtype' => 'user',
136 'userid' => $user->id,
137 'timesort' => $i,
138 'type' => CALENDAR_EVENT_TYPE_ACTION,
139 'courseid' => 1
143 $events = $vault->get_action_events_by_timesort($user, 2, 4);
145 $this->assertCount(3, $events);
146 $this->assertEquals('Event 2', $events[0]->get_name());
147 $this->assertEquals('Event 3', $events[1]->get_name());
148 $this->assertEquals('Event 4', $events[2]->get_name());
150 $events = $vault->get_action_events_by_timesort($user, 2, 4, null, 1);
152 $this->assertCount(1, $events);
153 $this->assertEquals('Event 2', $events[0]->get_name());
157 * Test that get_action_events_by_timesort returns events between the
158 * provided timesort values and after the last seen event when one is
159 * provided.
161 public function test_get_action_events_by_timesort_between_time_after_event(): void {
162 $this->resetAfterTest(true);
163 $this->setAdminuser();
165 $user = $this->getDataGenerator()->create_user();
166 $factory = new action_event_test_factory();
167 $strategy = new raw_event_retrieval_strategy();
168 $vault = new event_vault($factory, $strategy);
170 $records = [];
171 for ($i = 1; $i < 21; $i++) {
172 $records[] = create_event([
173 'name' => sprintf('Event %d', $i),
174 'eventtype' => 'user',
175 'userid' => $user->id,
176 'timesort' => $i,
177 'type' => CALENDAR_EVENT_TYPE_ACTION,
178 'courseid' => 1
182 $aftereventid = $records[6]->id;
183 $afterevent = $vault->get_event_by_id($aftereventid);
184 $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent);
186 $this->assertCount(8, $events);
187 $this->assertEquals('Event 8', $events[0]->get_name());
189 $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent, 3);
191 $this->assertCount(3, $events);
195 * Test that get_action_events_by_timesort returns events between the
196 * provided timesort values and the last seen event can be provided to
197 * get paginated results.
199 public function test_get_action_events_by_timesort_between_time_skip_even_records(): void {
200 $this->resetAfterTest(true);
201 $this->setAdminuser();
203 $user = $this->getDataGenerator()->create_user();
204 // The factory will return every event that is divisible by 2.
205 $factory = new action_event_test_factory(function($actionevent) {
206 static $count = 0;
207 $count++;
208 return ($count % 2) ? true : false;
210 $strategy = new raw_event_retrieval_strategy();
211 $vault = new event_vault($factory, $strategy);
213 for ($i = 1; $i < 41; $i++) {
214 create_event([
215 'name' => sprintf('Event %d', $i),
216 'eventtype' => 'user',
217 'userid' => $user->id,
218 'timesort' => $i,
219 'type' => CALENDAR_EVENT_TYPE_ACTION,
220 'courseid' => 1
224 $events = $vault->get_action_events_by_timesort($user, 3, 35, null, 5);
226 $this->assertCount(5, $events);
227 $this->assertEquals('Event 3', $events[0]->get_name());
228 $this->assertEquals('Event 5', $events[1]->get_name());
229 $this->assertEquals('Event 7', $events[2]->get_name());
230 $this->assertEquals('Event 9', $events[3]->get_name());
231 $this->assertEquals('Event 11', $events[4]->get_name());
233 $afterevent = $events[4];
234 $events = $vault->get_action_events_by_timesort($user, 3, 35, $afterevent, 5);
236 $this->assertCount(5, $events);
237 $this->assertEquals('Event 13', $events[0]->get_name());
238 $this->assertEquals('Event 15', $events[1]->get_name());
239 $this->assertEquals('Event 17', $events[2]->get_name());
240 $this->assertEquals('Event 19', $events[3]->get_name());
241 $this->assertEquals('Event 21', $events[4]->get_name());
245 * Test that get_action_events_by_timesort returns events between the
246 * provided timesort values. The database will continue to be read until the
247 * number of events requested has been satisfied. In this case the first
248 * five events are rejected so it should require two database requests.
250 public function test_get_action_events_by_timesort_between_time_skip_first_records(): void {
251 $this->resetAfterTest(true);
252 $this->setAdminuser();
254 $user = $this->getDataGenerator()->create_user();
255 $limit = 5;
256 $seen = 0;
257 // The factory will skip the first $limit events.
258 $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
259 if ($seen < $limit) {
260 $seen++;
261 return false;
262 } else {
263 return true;
266 $strategy = new raw_event_retrieval_strategy();
267 $vault = new event_vault($factory, $strategy);
269 for ($i = 1; $i < 21; $i++) {
270 create_event([
271 'name' => sprintf('Event %d', $i),
272 'eventtype' => 'user',
273 'userid' => $user->id,
274 'timesort' => $i,
275 'type' => CALENDAR_EVENT_TYPE_ACTION,
276 'courseid' => 1
280 $events = $vault->get_action_events_by_timesort($user, 1, 20, null, $limit);
282 $this->assertCount($limit, $events);
283 $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
284 $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
285 $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
286 $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
287 $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
291 * Test that get_action_events_by_timesort returns events between the
292 * provided timesort values and after the last seen event when one is
293 * provided. This should work even when the event ids aren't ordered the
294 * same as the timesort order.
296 public function test_get_action_events_by_timesort_non_consecutive_ids(): void {
297 $this->resetAfterTest(true);
298 $this->setAdminuser();
300 $user = $this->getDataGenerator()->create_user();
301 $factory = new action_event_test_factory();
302 $strategy = new raw_event_retrieval_strategy();
303 $vault = new event_vault($factory, $strategy);
306 * The events should be ordered by timesort as follows:
308 * 1 event 1
309 * 2 event 1
310 * 1 event 2
311 * 2 event 2
312 * 1 event 3
313 * 2 event 3
314 * 1 event 4
315 * 2 event 4
316 * 1 event 5
317 * 2 event 5
318 * 1 event 6
319 * 2 event 6
320 * 1 event 7
321 * 2 event 7
322 * 1 event 8
323 * 2 event 8
324 * 1 event 9
325 * 2 event 9
326 * 1 event 10
327 * 2 event 10
329 $records = [];
330 for ($i = 1; $i < 11; $i++) {
331 $records[] = create_event([
332 'name' => sprintf('1 event %d', $i),
333 'eventtype' => 'user',
334 'userid' => $user->id,
335 'timesort' => $i,
336 'type' => CALENDAR_EVENT_TYPE_ACTION,
337 'courseid' => 1
341 for ($i = 1; $i < 11; $i++) {
342 $records[] = create_event([
343 'name' => sprintf('2 event %d', $i),
344 'eventtype' => 'user',
345 'userid' => $user->id,
346 'timesort' => $i,
347 'type' => CALENDAR_EVENT_TYPE_ACTION,
348 'courseid' => 1
353 * Expected result set:
355 * 2 event 4
356 * 1 event 5
357 * 2 event 5
358 * 1 event 6
359 * 2 event 6
360 * 1 event 7
361 * 2 event 7
362 * 1 event 8
363 * 2 event 8
365 $aftereventid = $records[3]->id;
366 $afterevent = $vault->get_event_by_id($aftereventid);
367 // Offset results by event with name "1 event 4" which has the same timesort
368 // value as the lower boundary of this query (3). Confirm that the given
369 // $afterevent is used to ignore events with the same timesortfrom values.
370 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
372 $this->assertCount(9, $events);
373 $this->assertEquals('2 event 4', $events[0]->get_name());
374 $this->assertEquals('2 event 8', $events[8]->get_name());
377 * Expected result set:
379 * 2 event 4
380 * 1 event 5
382 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent, 2);
384 $this->assertCount(2, $events);
385 $this->assertEquals('2 event 4', $events[0]->get_name());
386 $this->assertEquals('1 event 5', $events[1]->get_name());
389 * Expected result set:
391 * 2 event 8
393 $aftereventid = $records[7]->id;
394 $afterevent = $vault->get_event_by_id($aftereventid);
395 // Offset results by event with name "1 event 8" which has the same timesort
396 // value as the upper boundary of this query (8). Confirm that the given
397 // $afterevent is used to ignore events with the same timesortto values.
398 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
400 $this->assertCount(1, $events);
401 $this->assertEquals('2 event 8', $events[0]->get_name());
404 * Expected empty result set.
406 $aftereventid = $records[18]->id;
407 $afterevent = $vault->get_event_by_id($aftereventid);
408 // Offset results by event with name "2 event 9" which has a timesort
409 // value larger than the upper boundary of this query (9 > 8). Confirm
410 // that the given $afterevent is used for filtering events.
411 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
412 $this->assertEmpty($events);
416 * There are subtle cases where the priority of an event override may be identical to another.
417 * For example, if you duplicate a group override, but make it apply to a different group. Now
418 * there are two overrides with exactly the same overridden dates. In this case the priority of
419 * both is 1.
421 * In this situation:
422 * - A user in group A should see only the A override
423 * - A user in group B should see only the B override
424 * - A user in both A and B should see both
426 public function test_get_action_events_by_timesort_with_identical_group_override_priorities(): void {
427 $this->resetAfterTest();
428 $this->setAdminuser();
430 $course = $this->getDataGenerator()->create_course();
432 // Create an assign instance.
433 $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
434 $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
436 // Create users.
437 $users = [
438 'Only in group A' => $this->getDataGenerator()->create_user(),
439 'Only in group B' => $this->getDataGenerator()->create_user(),
440 'In group A and B' => $this->getDataGenerator()->create_user(),
441 'In no groups' => $this->getDataGenerator()->create_user()
444 // Enrol users.
445 foreach ($users as $user) {
446 $this->getDataGenerator()->enrol_user($user->id, $course->id);
449 // Create groups.
450 $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
451 $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
453 // Add members to groups.
454 // Group A.
455 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
456 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
458 // Group B.
459 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
460 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
462 // Events with the same module name, instance and event type.
463 $events = [
465 'name' => 'Assignment 1 due date - Group A override',
466 'description' => '',
467 'format' => 1,
468 'courseid' => $course->id,
469 'groupid' => $groupa->id,
470 'userid' => 2,
471 'modulename' => 'assign',
472 'instance' => $assigninstance->id,
473 'eventtype' => 'due',
474 'type' => CALENDAR_EVENT_TYPE_ACTION,
475 'timestart' => 1,
476 'timeduration' => 0,
477 'visible' => 1,
478 'priority' => 1
481 'name' => 'Assignment 1 due date - Group B override',
482 'description' => '',
483 'format' => 1,
484 'courseid' => $course->id,
485 'groupid' => $groupb->id,
486 'userid' => 2,
487 'modulename' => 'assign',
488 'instance' => $assigninstance->id,
489 'eventtype' => 'due',
490 'type' => CALENDAR_EVENT_TYPE_ACTION,
491 'timestart' => 1,
492 'timeduration' => 0,
493 'visible' => 1,
494 'priority' => 1
497 'name' => 'Assignment 1 due date',
498 'description' => '',
499 'format' => 1,
500 'courseid' => $course->id,
501 'groupid' => 0,
502 'userid' => 2,
503 'modulename' => 'assign',
504 'instance' => $assigninstance->id,
505 'eventtype' => 'due',
506 'type' => CALENDAR_EVENT_TYPE_ACTION,
507 'timestart' => 1,
508 'timeduration' => 0,
509 'visible' => 1,
510 'priority' => null,
514 foreach ($events as $event) {
515 \calendar_event::create($event, false);
518 $factory = new action_event_test_factory();
519 $strategy = new raw_event_retrieval_strategy();
520 $vault = new event_vault($factory, $strategy);
522 $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $vault) {
523 // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
524 // It needs to be fixed, see MDL-58736.
525 $this->setUser($users[$description]);
526 return $carry + ['For user ' . lcfirst($description) => $vault->get_action_events_by_timesort($users[$description])];
527 }, []);
529 foreach ($usersevents as $description => $userevents) {
530 if ($description == 'For user in group A and B') {
531 // User is in both A and B, so they should see the override for both
532 // given that the priority is the same.
533 $this->assertCount(2, $userevents);
534 continue;
537 // Otherwise there should be only one assign event for each user.
538 $this->assertCount(1, $userevents);
541 // User in only group A should see the group A override.
542 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
544 // User in only group B should see the group B override.
545 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
547 // User in group A and B should see see both overrides since the priorities are the same.
548 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
549 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
551 // User in no groups should see the plain assignment event.
552 $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());
556 * Test that if a user is suspended that events related to that course are not shown.
557 * User 1 is suspended. User 2 is active.
559 public function test_get_action_events_by_timesort_with_suspended_user(): void {
560 $this->resetAfterTest();
561 $user1 = $this->getDataGenerator()->create_user();
562 $user2 = $this->getDataGenerator()->create_user();
563 $course = $this->getDataGenerator()->create_course();
564 $this->setAdminuser();
565 $lesson = $this->getDataGenerator()->create_module('lesson', [
566 'name' => 'Lesson 1',
567 'course' => $course->id,
568 'available' => time(),
569 'deadline' => (time() + (60 * 60 * 24 * 5))
572 $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
573 $this->getDataGenerator()->enrol_user($user2->id, $course->id);
575 $factory = new action_event_test_factory();
576 $strategy = new raw_event_retrieval_strategy();
577 $vault = new event_vault($factory, $strategy);
579 $user1events = $vault->get_action_events_by_timesort($user1, null, null, null, 20, true);
580 $this->assertEmpty($user1events);
581 $user2events = $vault->get_action_events_by_timesort($user2, null, null, null, 20, true);
582 $this->assertCount(1, $user2events);
583 $this->assertEquals('Lesson 1 closes', $user2events[0]->get_name());
587 * Test that get_action_events_by_course returns events after the
588 * provided timesort value.
590 public function test_get_action_events_by_course_after_time(): void {
591 $user = $this->getDataGenerator()->create_user();
592 $course1 = $this->getDataGenerator()->create_course();
593 $course2 = $this->getDataGenerator()->create_course();
594 $factory = new action_event_test_factory();
595 $strategy = new raw_event_retrieval_strategy();
596 $vault = new event_vault($factory, $strategy);
598 $this->resetAfterTest(true);
599 $this->setAdminuser();
600 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
601 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
603 for ($i = 1; $i < 6; $i++) {
604 create_event([
605 'name' => sprintf('Event %d', $i),
606 'eventtype' => 'user',
607 'userid' => $user->id,
608 'timesort' => $i,
609 'type' => CALENDAR_EVENT_TYPE_ACTION,
610 'courseid' => $course1->id,
614 for ($i = 6; $i < 12; $i++) {
615 create_event([
616 'name' => sprintf('Event %d', $i),
617 'eventtype' => 'user',
618 'userid' => $user->id,
619 'timesort' => $i,
620 'type' => CALENDAR_EVENT_TYPE_ACTION,
621 'courseid' => $course2->id,
625 $events = $vault->get_action_events_by_course($user, $course1, 3);
626 $this->assertCount(3, $events);
627 $this->assertEquals('Event 3', $events[0]->get_name());
628 $this->assertEquals('Event 4', $events[1]->get_name());
629 $this->assertEquals('Event 5', $events[2]->get_name());
631 $events = $vault->get_action_events_by_course($user, $course1, 3, null, null, 1);
633 $this->assertCount(1, $events);
634 $this->assertEquals('Event 3', $events[0]->get_name());
636 $events = $vault->get_action_events_by_course($user, $course1, 6);
638 $this->assertCount(0, $events);
642 * Test that get_action_events_by_course returns events before the
643 * provided timesort value.
645 public function test_get_action_events_by_course_before_time(): void {
646 $user = $this->getDataGenerator()->create_user();
647 $course1 = $this->getDataGenerator()->create_course();
648 $course2 = $this->getDataGenerator()->create_course();
649 $factory = new action_event_test_factory();
650 $strategy = new raw_event_retrieval_strategy();
651 $vault = new event_vault($factory, $strategy);
653 $this->resetAfterTest(true);
654 $this->setAdminuser();
655 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
656 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
658 for ($i = 1; $i < 6; $i++) {
659 create_event([
660 'name' => sprintf('Event %d', $i),
661 'eventtype' => 'user',
662 'userid' => $user->id,
663 'timesort' => $i,
664 'type' => CALENDAR_EVENT_TYPE_ACTION,
665 'courseid' => $course1->id,
669 for ($i = 6; $i < 12; $i++) {
670 create_event([
671 'name' => sprintf('Event %d', $i),
672 'eventtype' => 'user',
673 'userid' => $user->id,
674 'timesort' => $i,
675 'type' => CALENDAR_EVENT_TYPE_ACTION,
676 'courseid' => $course2->id,
680 $events = $vault->get_action_events_by_course($user, $course1, null, 3);
682 $this->assertCount(3, $events);
683 $this->assertEquals('Event 1', $events[0]->get_name());
684 $this->assertEquals('Event 2', $events[1]->get_name());
685 $this->assertEquals('Event 3', $events[2]->get_name());
687 $events = $vault->get_action_events_by_course($user, $course1, null, 3, null, 1);
689 $this->assertCount(1, $events);
690 $this->assertEquals('Event 1', $events[0]->get_name());
692 $events = $vault->get_action_events_by_course($user, $course1, 6);
694 $this->assertCount(0, $events);
698 * Test that get_action_events_by_course returns events between the
699 * provided timesort values.
701 public function test_get_action_events_by_course_between_time(): void {
702 $user = $this->getDataGenerator()->create_user();
703 $course1 = $this->getDataGenerator()->create_course();
704 $course2 = $this->getDataGenerator()->create_course();
705 $factory = new action_event_test_factory();
706 $strategy = new raw_event_retrieval_strategy();
707 $vault = new event_vault($factory, $strategy);
709 $this->resetAfterTest(true);
710 $this->setAdminuser();
711 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
712 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
714 for ($i = 1; $i < 6; $i++) {
715 create_event([
716 'name' => sprintf('Event %d', $i),
717 'eventtype' => 'user',
718 'userid' => $user->id,
719 'timesort' => $i,
720 'type' => CALENDAR_EVENT_TYPE_ACTION,
721 'courseid' => $course1->id,
725 for ($i = 6; $i < 12; $i++) {
726 create_event([
727 'name' => sprintf('Event %d', $i),
728 'eventtype' => 'user',
729 'userid' => $user->id,
730 'timesort' => $i,
731 'type' => CALENDAR_EVENT_TYPE_ACTION,
732 'courseid' => $course2->id,
736 $events = $vault->get_action_events_by_course($user, $course1, 2, 4);
738 $this->assertCount(3, $events);
739 $this->assertEquals('Event 2', $events[0]->get_name());
740 $this->assertEquals('Event 3', $events[1]->get_name());
741 $this->assertEquals('Event 4', $events[2]->get_name());
743 $events = $vault->get_action_events_by_course($user, $course1, 2, 4, null, 1);
745 $this->assertCount(1, $events);
746 $this->assertEquals('Event 2', $events[0]->get_name());
750 * Test that get_action_events_by_course returns events between the
751 * provided timesort values and after the last seen event when one is
752 * provided.
754 public function test_get_action_events_by_course_between_time_after_event(): void {
755 $user = $this->getDataGenerator()->create_user();
756 $course1 = $this->getDataGenerator()->create_course();
757 $course2 = $this->getDataGenerator()->create_course();
758 $factory = new action_event_test_factory();
759 $strategy = new raw_event_retrieval_strategy();
760 $vault = new event_vault($factory, $strategy);
761 $records = [];
763 $this->resetAfterTest(true);
764 $this->setAdminuser();
765 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
766 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
768 for ($i = 1; $i < 21; $i++) {
769 $records[] = create_event([
770 'name' => sprintf('Event %d', $i),
771 'eventtype' => 'user',
772 'userid' => $user->id,
773 'timesort' => $i,
774 'type' => CALENDAR_EVENT_TYPE_ACTION,
775 'courseid' => $course1->id,
779 for ($i = 21; $i < 41; $i++) {
780 $records[] = create_event([
781 'name' => sprintf('Event %d', $i),
782 'eventtype' => 'user',
783 'userid' => $user->id,
784 'timesort' => $i,
785 'type' => CALENDAR_EVENT_TYPE_ACTION,
786 'courseid' => $course2->id,
790 $aftereventid = $records[6]->id;
791 $afterevent = $vault->get_event_by_id($aftereventid);
792 $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent);
794 $this->assertCount(8, $events);
795 $this->assertEquals('Event 8', $events[0]->get_name());
797 $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent, 3);
799 $this->assertCount(3, $events);
803 * Test that get_action_events_by_course returns events between the
804 * provided timesort values and the last seen event can be provided to
805 * get paginated results.
807 public function test_get_action_events_by_course_between_time_skip_even_records(): void {
808 $user = $this->getDataGenerator()->create_user();
809 $course1 = $this->getDataGenerator()->create_course();
810 $course2 = $this->getDataGenerator()->create_course();
811 // The factory will return every event that is divisible by 2.
812 $factory = new action_event_test_factory(function($actionevent) {
813 static $count = 0;
814 $count++;
815 return ($count % 2) ? true : false;
817 $strategy = new raw_event_retrieval_strategy();
818 $vault = new event_vault($factory, $strategy);
820 $this->resetAfterTest(true);
821 $this->setAdminuser();
822 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
823 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
825 for ($i = 1; $i < 41; $i++) {
826 create_event([
827 'name' => sprintf('Event %d', $i),
828 'eventtype' => 'user',
829 'userid' => $user->id,
830 'timesort' => $i,
831 'type' => CALENDAR_EVENT_TYPE_ACTION,
832 'courseid' => $course1->id,
836 for ($i = 41; $i < 81; $i++) {
837 create_event([
838 'name' => sprintf('Event %d', $i),
839 'eventtype' => 'user',
840 'userid' => $user->id,
841 'timesort' => $i,
842 'type' => CALENDAR_EVENT_TYPE_ACTION,
843 'courseid' => $course2->id,
847 $events = $vault->get_action_events_by_course($user, $course1, 3, 35, null, 5);
849 $this->assertCount(5, $events);
850 $this->assertEquals('Event 3', $events[0]->get_name());
851 $this->assertEquals('Event 5', $events[1]->get_name());
852 $this->assertEquals('Event 7', $events[2]->get_name());
853 $this->assertEquals('Event 9', $events[3]->get_name());
854 $this->assertEquals('Event 11', $events[4]->get_name());
856 $afterevent = $events[4];
857 $events = $vault->get_action_events_by_course($user, $course1, 3, 35, $afterevent, 5);
859 $this->assertCount(5, $events);
860 $this->assertEquals('Event 13', $events[0]->get_name());
861 $this->assertEquals('Event 15', $events[1]->get_name());
862 $this->assertEquals('Event 17', $events[2]->get_name());
863 $this->assertEquals('Event 19', $events[3]->get_name());
864 $this->assertEquals('Event 21', $events[4]->get_name());
868 * Test that get_action_events_by_course returns events between the
869 * provided timesort values. The database will continue to be read until the
870 * number of events requested has been satisfied. In this case the first
871 * five events are rejected so it should require two database requests.
873 public function test_get_action_events_by_course_between_time_skip_first_records(): void {
874 $user = $this->getDataGenerator()->create_user();
875 $course1 = $this->getDataGenerator()->create_course();
876 $course2 = $this->getDataGenerator()->create_course();
877 $limit = 5;
878 $seen = 0;
879 // The factory will skip the first $limit events.
880 $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
881 if ($seen < $limit) {
882 $seen++;
883 return false;
884 } else {
885 return true;
888 $strategy = new raw_event_retrieval_strategy();
889 $vault = new event_vault($factory, $strategy);
891 $this->resetAfterTest(true);
892 $this->setAdminuser();
893 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
894 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
896 for ($i = 1; $i < 21; $i++) {
897 create_event([
898 'name' => sprintf('Event %d', $i),
899 'eventtype' => 'user',
900 'userid' => $user->id,
901 'timesort' => $i,
902 'type' => CALENDAR_EVENT_TYPE_ACTION,
903 'courseid' => $course1->id,
907 for ($i = 21; $i < 41; $i++) {
908 create_event([
909 'name' => sprintf('Event %d', $i),
910 'eventtype' => 'user',
911 'userid' => $user->id,
912 'timesort' => $i,
913 'type' => CALENDAR_EVENT_TYPE_ACTION,
914 'courseid' => $course2->id,
918 $events = $vault->get_action_events_by_course($user, $course1, 1, 20, null, $limit);
920 $this->assertCount($limit, $events);
921 $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
922 $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
923 $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
924 $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
925 $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
929 * Test that get_action_events_by_course returns events between the
930 * provided timesort values and after the last seen event when one is
931 * provided. This should work even when the event ids aren't ordered the
932 * same as the timesort order.
934 public function test_get_action_events_by_course_non_consecutive_ids(): void {
935 $this->resetAfterTest(true);
936 $this->setAdminuser();
938 $user = $this->getDataGenerator()->create_user();
939 $course1 = $this->getDataGenerator()->create_course();
940 $course2 = $this->getDataGenerator()->create_course();
941 $factory = new action_event_test_factory();
942 $strategy = new raw_event_retrieval_strategy();
943 $vault = new event_vault($factory, $strategy);
945 $this->setAdminuser();
946 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
947 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
950 * The events should be ordered by timesort as follows:
952 * 1 event 1
953 * 2 event 1
954 * 1 event 2
955 * 2 event 2
956 * 1 event 3
957 * 2 event 3
958 * 1 event 4
959 * 2 event 4
960 * 1 event 5
961 * 2 event 5
962 * 1 event 6
963 * 2 event 6
964 * 1 event 7
965 * 2 event 7
966 * 1 event 8
967 * 2 event 8
968 * 1 event 9
969 * 2 event 9
970 * 1 event 10
971 * 2 event 10
973 $records = [];
974 for ($i = 1; $i < 11; $i++) {
975 $records[] = create_event([
976 'name' => sprintf('1 event %d', $i),
977 'eventtype' => 'user',
978 'userid' => $user->id,
979 'timesort' => $i,
980 'type' => CALENDAR_EVENT_TYPE_ACTION,
981 'courseid' => $course1->id,
985 for ($i = 1; $i < 11; $i++) {
986 $records[] = create_event([
987 'name' => sprintf('2 event %d', $i),
988 'eventtype' => 'user',
989 'userid' => $user->id,
990 'timesort' => $i,
991 'type' => CALENDAR_EVENT_TYPE_ACTION,
992 'courseid' => $course1->id,
996 // Create events for the other course.
997 for ($i = 1; $i < 11; $i++) {
998 $records[] = create_event([
999 'name' => sprintf('3 event %d', $i),
1000 'eventtype' => 'user',
1001 'userid' => $user->id,
1002 'timesort' => $i,
1003 'type' => CALENDAR_EVENT_TYPE_ACTION,
1004 'courseid' => $course2->id,
1009 * Expected result set:
1011 * 2 event 4
1012 * 1 event 5
1013 * 2 event 5
1014 * 1 event 6
1015 * 2 event 6
1016 * 1 event 7
1017 * 2 event 7
1018 * 1 event 8
1019 * 2 event 8
1021 $aftereventid = $records[3]->id;
1022 $afterevent = $vault->get_event_by_id($aftereventid);
1023 // Offset results by event with name "1 event 4" which has the same timesort
1024 // value as the lower boundary of this query (3). Confirm that the given
1025 // $afterevent is used to ignore events with the same timesortfrom values.
1026 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1028 $this->assertCount(9, $events);
1029 $this->assertEquals('2 event 4', $events[0]->get_name());
1030 $this->assertEquals('2 event 8', $events[8]->get_name());
1033 * Expected result set:
1035 * 2 event 4
1036 * 1 event 5
1038 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent, 2);
1040 $this->assertCount(2, $events);
1041 $this->assertEquals('2 event 4', $events[0]->get_name());
1042 $this->assertEquals('1 event 5', $events[1]->get_name());
1045 * Expected result set:
1047 * 2 event 8
1049 $aftereventid = $records[7]->id;
1050 $afterevent = $vault->get_event_by_id($aftereventid);
1051 // Offset results by event with name "1 event 8" which has the same timesort
1052 // value as the upper boundary of this query (8). Confirm that the given
1053 // $afterevent is used to ignore events with the same timesortto values.
1054 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1056 $this->assertCount(1, $events);
1057 $this->assertEquals('2 event 8', $events[0]->get_name());
1060 * Expected empty result set.
1062 $aftereventid = $records[18]->id;
1063 $afterevent = $vault->get_event_by_id($aftereventid);
1064 // Offset results by event with name "2 event 9" which has a timesort
1065 // value larger than the upper boundary of this query (9 > 8). Confirm
1066 // that the given $afterevent is used for filtering events.
1067 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1069 $this->assertEmpty($events);
1073 * There are subtle cases where the priority of an event override may be identical to another.
1074 * For example, if you duplicate a group override, but make it apply to a different group. Now
1075 * there are two overrides with exactly the same overridden dates. In this case the priority of
1076 * both is 1.
1078 * In this situation:
1079 * - A user in group A should see only the A override
1080 * - A user in group B should see only the B override
1081 * - A user in both A and B should see both
1083 public function test_get_action_events_by_course_with_identical_group_override_priorities(): void {
1084 $this->resetAfterTest();
1085 $this->setAdminuser();
1087 $course = $this->getDataGenerator()->create_course();
1089 // Create an assign instance.
1090 $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1091 $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
1093 // Create users.
1094 $users = [
1095 'Only in group A' => $this->getDataGenerator()->create_user(),
1096 'Only in group B' => $this->getDataGenerator()->create_user(),
1097 'In group A and B' => $this->getDataGenerator()->create_user(),
1098 'In no groups' => $this->getDataGenerator()->create_user()
1101 // Enrol users.
1102 foreach ($users as $user) {
1103 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1106 // Create groups.
1107 $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1108 $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1110 // Add members to groups.
1111 // Group A.
1112 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
1113 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
1115 // Group B.
1116 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
1117 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
1119 // Events with the same module name, instance and event type.
1120 $events = [
1122 'name' => 'Assignment 1 due date - Group A override',
1123 'description' => '',
1124 'format' => 1,
1125 'courseid' => $course->id,
1126 'groupid' => $groupa->id,
1127 'userid' => 2,
1128 'modulename' => 'assign',
1129 'instance' => $assigninstance->id,
1130 'eventtype' => 'due',
1131 'type' => CALENDAR_EVENT_TYPE_ACTION,
1132 'timestart' => 1,
1133 'timeduration' => 0,
1134 'visible' => 1,
1135 'priority' => 1
1138 'name' => 'Assignment 1 due date - Group B override',
1139 'description' => '',
1140 'format' => 1,
1141 'courseid' => $course->id,
1142 'groupid' => $groupb->id,
1143 'userid' => 2,
1144 'modulename' => 'assign',
1145 'instance' => $assigninstance->id,
1146 'eventtype' => 'due',
1147 'type' => CALENDAR_EVENT_TYPE_ACTION,
1148 'timestart' => 1,
1149 'timeduration' => 0,
1150 'visible' => 1,
1151 'priority' => 1
1154 'name' => 'Assignment 1 due date',
1155 'description' => '',
1156 'format' => 1,
1157 'courseid' => $course->id,
1158 'groupid' => 0,
1159 'userid' => 2,
1160 'modulename' => 'assign',
1161 'instance' => $assigninstance->id,
1162 'eventtype' => 'due',
1163 'type' => CALENDAR_EVENT_TYPE_ACTION,
1164 'timestart' => 1,
1165 'timeduration' => 0,
1166 'visible' => 1,
1167 'priority' => null,
1171 foreach ($events as $event) {
1172 \calendar_event::create($event, false);
1175 $factory = new action_event_test_factory();
1176 $strategy = new raw_event_retrieval_strategy();
1177 $vault = new event_vault($factory, $strategy);
1179 $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $course, $vault) {
1180 // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
1181 // It needs to be fixed, see MDL-58736.
1182 $this->setUser($users[$description]);
1183 return $carry + [
1184 'For user ' . lcfirst($description) => $vault->get_action_events_by_course($users[$description], $course)
1186 }, []);
1188 foreach ($usersevents as $description => $userevents) {
1189 if ($description == 'For user in group A and B') {
1190 // User is in both A and B, so they should see the override for both
1191 // given that the priority is the same.
1192 $this->assertCount(2, $userevents);
1193 continue;
1196 // Otherwise there should be only one assign event for each user.
1197 $this->assertCount(1, $userevents);
1200 // User in only group A should see the group A override.
1201 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
1203 // User in only group B should see the group B override.
1204 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
1206 // User in group A and B should see see both overrides since the priorities are the same.
1207 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
1208 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
1210 // User in no groups should see the plain assignment event.
1211 $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());