Merge branch 'MDL-73711_310' of https://github.com/stronk7/moodle into MOODLE_310_STABLE
[moodle.git] / calendar / tests / event_vault_test.php
blob5d90f93b3d83e3f824c9761c778a79e6fdf7f463
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 /**
18 * This file contains the class that handles testing of the calendar event vault.
20 * @package core_calendar
21 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
27 global $CFG;
28 require_once($CFG->dirroot . '/calendar/tests/helpers.php');
30 use core_calendar\local\event\data_access\event_vault;
31 use core_calendar\local\event\strategies\raw_event_retrieval_strategy;
33 /**
34 * This file contains the class that handles testing of the calendar event vault.
36 * @package core_calendar
37 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class core_calendar_event_vault_testcase extends advanced_testcase {
42 /**
43 * Test that get_action_events_by_timesort returns events after the
44 * provided timesort value.
46 public function test_get_action_events_by_timesort_after_time() {
47 $this->resetAfterTest(true);
49 $user = $this->getDataGenerator()->create_user();
50 $factory = new action_event_test_factory();
51 $strategy = new raw_event_retrieval_strategy();
52 $vault = new event_vault($factory, $strategy);
54 $this->setUser($user);
56 for ($i = 1; $i < 6; $i++) {
57 create_event([
58 'name' => sprintf('Event %d', $i),
59 'eventtype' => 'user',
60 'userid' => $user->id,
61 'timesort' => $i,
62 'type' => CALENDAR_EVENT_TYPE_ACTION
63 ]);
66 $events = $vault->get_action_events_by_timesort($user, 3);
68 $this->assertCount(3, $events);
69 $this->assertEquals('Event 3', $events[0]->get_name());
70 $this->assertEquals('Event 4', $events[1]->get_name());
71 $this->assertEquals('Event 5', $events[2]->get_name());
73 $events = $vault->get_action_events_by_timesort($user, 3, null, null, 1);
75 $this->assertCount(1, $events);
76 $this->assertEquals('Event 3', $events[0]->get_name());
78 $events = $vault->get_action_events_by_timesort($user, 6);
80 $this->assertCount(0, $events);
83 /**
84 * Test that get_action_events_by_timesort returns events before the
85 * provided timesort value.
87 public function test_get_action_events_by_timesort_before_time() {
88 $this->resetAfterTest(true);
89 $this->setAdminuser();
91 $user = $this->getDataGenerator()->create_user();
92 $factory = new action_event_test_factory();
93 $strategy = new raw_event_retrieval_strategy();
94 $vault = new event_vault($factory, $strategy);
96 for ($i = 1; $i < 6; $i++) {
97 create_event([
98 'name' => sprintf('Event %d', $i),
99 'eventtype' => 'user',
100 'userid' => $user->id,
101 'timesort' => $i,
102 'type' => CALENDAR_EVENT_TYPE_ACTION,
103 'courseid' => 1
107 $events = $vault->get_action_events_by_timesort($user, null, 3);
109 $this->assertCount(3, $events);
110 $this->assertEquals('Event 1', $events[0]->get_name());
111 $this->assertEquals('Event 2', $events[1]->get_name());
112 $this->assertEquals('Event 3', $events[2]->get_name());
114 $events = $vault->get_action_events_by_timesort($user, null, 3, null, 1);
116 $this->assertCount(1, $events);
117 $this->assertEquals('Event 1', $events[0]->get_name());
119 $events = $vault->get_action_events_by_timesort($user, 6);
121 $this->assertCount(0, $events);
125 * Test that get_action_events_by_timesort returns events between the
126 * provided timesort values.
128 public function test_get_action_events_by_timesort_between_time() {
129 $this->resetAfterTest(true);
130 $this->setAdminuser();
132 $user = $this->getDataGenerator()->create_user();
133 $factory = new action_event_test_factory();
134 $strategy = new raw_event_retrieval_strategy();
135 $vault = new event_vault($factory, $strategy);
137 for ($i = 1; $i < 6; $i++) {
138 create_event([
139 'name' => sprintf('Event %d', $i),
140 'eventtype' => 'user',
141 'userid' => $user->id,
142 'timesort' => $i,
143 'type' => CALENDAR_EVENT_TYPE_ACTION,
144 'courseid' => 1
148 $events = $vault->get_action_events_by_timesort($user, 2, 4);
150 $this->assertCount(3, $events);
151 $this->assertEquals('Event 2', $events[0]->get_name());
152 $this->assertEquals('Event 3', $events[1]->get_name());
153 $this->assertEquals('Event 4', $events[2]->get_name());
155 $events = $vault->get_action_events_by_timesort($user, 2, 4, null, 1);
157 $this->assertCount(1, $events);
158 $this->assertEquals('Event 2', $events[0]->get_name());
162 * Test that get_action_events_by_timesort returns events between the
163 * provided timesort values and after the last seen event when one is
164 * provided.
166 public function test_get_action_events_by_timesort_between_time_after_event() {
167 $this->resetAfterTest(true);
168 $this->setAdminuser();
170 $user = $this->getDataGenerator()->create_user();
171 $factory = new action_event_test_factory();
172 $strategy = new raw_event_retrieval_strategy();
173 $vault = new event_vault($factory, $strategy);
175 $records = [];
176 for ($i = 1; $i < 21; $i++) {
177 $records[] = create_event([
178 'name' => sprintf('Event %d', $i),
179 'eventtype' => 'user',
180 'userid' => $user->id,
181 'timesort' => $i,
182 'type' => CALENDAR_EVENT_TYPE_ACTION,
183 'courseid' => 1
187 $aftereventid = $records[6]->id;
188 $afterevent = $vault->get_event_by_id($aftereventid);
189 $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent);
191 $this->assertCount(8, $events);
192 $this->assertEquals('Event 8', $events[0]->get_name());
194 $events = $vault->get_action_events_by_timesort($user, 3, 15, $afterevent, 3);
196 $this->assertCount(3, $events);
200 * Test that get_action_events_by_timesort returns events between the
201 * provided timesort values and the last seen event can be provided to
202 * get paginated results.
204 public function test_get_action_events_by_timesort_between_time_skip_even_records() {
205 $this->resetAfterTest(true);
206 $this->setAdminuser();
208 $user = $this->getDataGenerator()->create_user();
209 // The factory will return every event that is divisible by 2.
210 $factory = new action_event_test_factory(function($actionevent) {
211 static $count = 0;
212 $count++;
213 return ($count % 2) ? true : false;
215 $strategy = new raw_event_retrieval_strategy();
216 $vault = new event_vault($factory, $strategy);
218 for ($i = 1; $i < 41; $i++) {
219 create_event([
220 'name' => sprintf('Event %d', $i),
221 'eventtype' => 'user',
222 'userid' => $user->id,
223 'timesort' => $i,
224 'type' => CALENDAR_EVENT_TYPE_ACTION,
225 'courseid' => 1
229 $events = $vault->get_action_events_by_timesort($user, 3, 35, null, 5);
231 $this->assertCount(5, $events);
232 $this->assertEquals('Event 3', $events[0]->get_name());
233 $this->assertEquals('Event 5', $events[1]->get_name());
234 $this->assertEquals('Event 7', $events[2]->get_name());
235 $this->assertEquals('Event 9', $events[3]->get_name());
236 $this->assertEquals('Event 11', $events[4]->get_name());
238 $afterevent = $events[4];
239 $events = $vault->get_action_events_by_timesort($user, 3, 35, $afterevent, 5);
241 $this->assertCount(5, $events);
242 $this->assertEquals('Event 13', $events[0]->get_name());
243 $this->assertEquals('Event 15', $events[1]->get_name());
244 $this->assertEquals('Event 17', $events[2]->get_name());
245 $this->assertEquals('Event 19', $events[3]->get_name());
246 $this->assertEquals('Event 21', $events[4]->get_name());
250 * Test that get_action_events_by_timesort returns events between the
251 * provided timesort values. The database will continue to be read until the
252 * number of events requested has been satisfied. In this case the first
253 * five events are rejected so it should require two database requests.
255 public function test_get_action_events_by_timesort_between_time_skip_first_records() {
256 $this->resetAfterTest(true);
257 $this->setAdminuser();
259 $user = $this->getDataGenerator()->create_user();
260 $limit = 5;
261 $seen = 0;
262 // The factory will skip the first $limit events.
263 $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
264 if ($seen < $limit) {
265 $seen++;
266 return false;
267 } else {
268 return true;
271 $strategy = new raw_event_retrieval_strategy();
272 $vault = new event_vault($factory, $strategy);
274 for ($i = 1; $i < 21; $i++) {
275 create_event([
276 'name' => sprintf('Event %d', $i),
277 'eventtype' => 'user',
278 'userid' => $user->id,
279 'timesort' => $i,
280 'type' => CALENDAR_EVENT_TYPE_ACTION,
281 'courseid' => 1
285 $events = $vault->get_action_events_by_timesort($user, 1, 20, null, $limit);
287 $this->assertCount($limit, $events);
288 $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
289 $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
290 $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
291 $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
292 $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
296 * Test that get_action_events_by_timesort returns events between the
297 * provided timesort values and after the last seen event when one is
298 * provided. This should work even when the event ids aren't ordered the
299 * same as the timesort order.
301 public function test_get_action_events_by_timesort_non_consecutive_ids() {
302 $this->resetAfterTest(true);
303 $this->setAdminuser();
305 $user = $this->getDataGenerator()->create_user();
306 $factory = new action_event_test_factory();
307 $strategy = new raw_event_retrieval_strategy();
308 $vault = new event_vault($factory, $strategy);
311 * The events should be ordered by timesort as follows:
313 * 1 event 1
314 * 2 event 1
315 * 1 event 2
316 * 2 event 2
317 * 1 event 3
318 * 2 event 3
319 * 1 event 4
320 * 2 event 4
321 * 1 event 5
322 * 2 event 5
323 * 1 event 6
324 * 2 event 6
325 * 1 event 7
326 * 2 event 7
327 * 1 event 8
328 * 2 event 8
329 * 1 event 9
330 * 2 event 9
331 * 1 event 10
332 * 2 event 10
334 $records = [];
335 for ($i = 1; $i < 11; $i++) {
336 $records[] = create_event([
337 'name' => sprintf('1 event %d', $i),
338 'eventtype' => 'user',
339 'userid' => $user->id,
340 'timesort' => $i,
341 'type' => CALENDAR_EVENT_TYPE_ACTION,
342 'courseid' => 1
346 for ($i = 1; $i < 11; $i++) {
347 $records[] = create_event([
348 'name' => sprintf('2 event %d', $i),
349 'eventtype' => 'user',
350 'userid' => $user->id,
351 'timesort' => $i,
352 'type' => CALENDAR_EVENT_TYPE_ACTION,
353 'courseid' => 1
358 * Expected result set:
360 * 2 event 4
361 * 1 event 5
362 * 2 event 5
363 * 1 event 6
364 * 2 event 6
365 * 1 event 7
366 * 2 event 7
367 * 1 event 8
368 * 2 event 8
370 $aftereventid = $records[3]->id;
371 $afterevent = $vault->get_event_by_id($aftereventid);
372 // Offset results by event with name "1 event 4" which has the same timesort
373 // value as the lower boundary of this query (3). Confirm that the given
374 // $afterevent is used to ignore events with the same timesortfrom values.
375 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
377 $this->assertCount(9, $events);
378 $this->assertEquals('2 event 4', $events[0]->get_name());
379 $this->assertEquals('2 event 8', $events[8]->get_name());
382 * Expected result set:
384 * 2 event 4
385 * 1 event 5
387 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent, 2);
389 $this->assertCount(2, $events);
390 $this->assertEquals('2 event 4', $events[0]->get_name());
391 $this->assertEquals('1 event 5', $events[1]->get_name());
394 * Expected result set:
396 * 2 event 8
398 $aftereventid = $records[7]->id;
399 $afterevent = $vault->get_event_by_id($aftereventid);
400 // Offset results by event with name "1 event 8" which has the same timesort
401 // value as the upper boundary of this query (8). Confirm that the given
402 // $afterevent is used to ignore events with the same timesortto values.
403 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
405 $this->assertCount(1, $events);
406 $this->assertEquals('2 event 8', $events[0]->get_name());
409 * Expected empty result set.
411 $aftereventid = $records[18]->id;
412 $afterevent = $vault->get_event_by_id($aftereventid);
413 // Offset results by event with name "2 event 9" which has a timesort
414 // value larger than the upper boundary of this query (9 > 8). Confirm
415 // that the given $afterevent is used for filtering events.
416 $events = $vault->get_action_events_by_timesort($user, 3, 8, $afterevent);
417 $this->assertEmpty($events);
421 * There are subtle cases where the priority of an event override may be identical to another.
422 * For example, if you duplicate a group override, but make it apply to a different group. Now
423 * there are two overrides with exactly the same overridden dates. In this case the priority of
424 * both is 1.
426 * In this situation:
427 * - A user in group A should see only the A override
428 * - A user in group B should see only the B override
429 * - A user in both A and B should see both
431 public function test_get_action_events_by_timesort_with_identical_group_override_priorities() {
432 $this->resetAfterTest();
433 $this->setAdminuser();
435 $course = $this->getDataGenerator()->create_course();
437 // Create an assign instance.
438 $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
439 $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
441 // Create users.
442 $users = [
443 'Only in group A' => $this->getDataGenerator()->create_user(),
444 'Only in group B' => $this->getDataGenerator()->create_user(),
445 'In group A and B' => $this->getDataGenerator()->create_user(),
446 'In no groups' => $this->getDataGenerator()->create_user()
449 // Enrol users.
450 foreach ($users as $user) {
451 $this->getDataGenerator()->enrol_user($user->id, $course->id);
454 // Create groups.
455 $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
456 $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
458 // Add members to groups.
459 // Group A.
460 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
461 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
463 // Group B.
464 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
465 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
467 // Events with the same module name, instance and event type.
468 $events = [
470 'name' => 'Assignment 1 due date - Group A override',
471 'description' => '',
472 'format' => 1,
473 'courseid' => $course->id,
474 'groupid' => $groupa->id,
475 'userid' => 2,
476 'modulename' => 'assign',
477 'instance' => $assigninstance->id,
478 'eventtype' => 'due',
479 'type' => CALENDAR_EVENT_TYPE_ACTION,
480 'timestart' => 1,
481 'timeduration' => 0,
482 'visible' => 1,
483 'priority' => 1
486 'name' => 'Assignment 1 due date - Group B override',
487 'description' => '',
488 'format' => 1,
489 'courseid' => $course->id,
490 'groupid' => $groupb->id,
491 'userid' => 2,
492 'modulename' => 'assign',
493 'instance' => $assigninstance->id,
494 'eventtype' => 'due',
495 'type' => CALENDAR_EVENT_TYPE_ACTION,
496 'timestart' => 1,
497 'timeduration' => 0,
498 'visible' => 1,
499 'priority' => 1
502 'name' => 'Assignment 1 due date',
503 'description' => '',
504 'format' => 1,
505 'courseid' => $course->id,
506 'groupid' => 0,
507 'userid' => 2,
508 'modulename' => 'assign',
509 'instance' => $assigninstance->id,
510 'eventtype' => 'due',
511 'type' => CALENDAR_EVENT_TYPE_ACTION,
512 'timestart' => 1,
513 'timeduration' => 0,
514 'visible' => 1,
515 'priority' => null,
519 foreach ($events as $event) {
520 calendar_event::create($event, false);
523 $factory = new action_event_test_factory();
524 $strategy = new raw_event_retrieval_strategy();
525 $vault = new event_vault($factory, $strategy);
527 $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $vault) {
528 // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
529 // It needs to be fixed, see MDL-58736.
530 $this->setUser($users[$description]);
531 return $carry + ['For user ' . lcfirst($description) => $vault->get_action_events_by_timesort($users[$description])];
532 }, []);
534 foreach ($usersevents as $description => $userevents) {
535 if ($description == 'For user in group A and B') {
536 // User is in both A and B, so they should see the override for both
537 // given that the priority is the same.
538 $this->assertCount(2, $userevents);
539 continue;
542 // Otherwise there should be only one assign event for each user.
543 $this->assertCount(1, $userevents);
546 // User in only group A should see the group A override.
547 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
549 // User in only group B should see the group B override.
550 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
552 // User in group A and B should see see both overrides since the priorities are the same.
553 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
554 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
556 // User in no groups should see the plain assignment event.
557 $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());
561 * Test that if a user is suspended that events related to that course are not shown.
562 * User 1 is suspended. User 2 is active.
564 public function test_get_action_events_by_timesort_with_suspended_user() {
565 $this->resetAfterTest();
566 $user1 = $this->getDataGenerator()->create_user();
567 $user2 = $this->getDataGenerator()->create_user();
568 $course = $this->getDataGenerator()->create_course();
569 $this->setAdminuser();
570 $lesson = $this->getDataGenerator()->create_module('lesson', [
571 'name' => 'Lesson 1',
572 'course' => $course->id,
573 'available' => time(),
574 'deadline' => (time() + (60 * 60 * 24 * 5))
577 $this->getDataGenerator()->enrol_user($user1->id, $course->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
578 $this->getDataGenerator()->enrol_user($user2->id, $course->id);
580 $factory = new action_event_test_factory();
581 $strategy = new raw_event_retrieval_strategy();
582 $vault = new event_vault($factory, $strategy);
584 $user1events = $vault->get_action_events_by_timesort($user1, null, null, null, 20, true);
585 $this->assertEmpty($user1events);
586 $user2events = $vault->get_action_events_by_timesort($user2, null, null, null, 20, true);
587 $this->assertCount(1, $user2events);
588 $this->assertEquals('Lesson 1 closes', $user2events[0]->get_name());
592 * Test that get_action_events_by_course returns events after the
593 * provided timesort value.
595 public function test_get_action_events_by_course_after_time() {
596 $user = $this->getDataGenerator()->create_user();
597 $course1 = $this->getDataGenerator()->create_course();
598 $course2 = $this->getDataGenerator()->create_course();
599 $factory = new action_event_test_factory();
600 $strategy = new raw_event_retrieval_strategy();
601 $vault = new event_vault($factory, $strategy);
603 $this->resetAfterTest(true);
604 $this->setAdminuser();
605 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
606 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
608 for ($i = 1; $i < 6; $i++) {
609 create_event([
610 'name' => sprintf('Event %d', $i),
611 'eventtype' => 'user',
612 'userid' => $user->id,
613 'timesort' => $i,
614 'type' => CALENDAR_EVENT_TYPE_ACTION,
615 'courseid' => $course1->id,
619 for ($i = 6; $i < 12; $i++) {
620 create_event([
621 'name' => sprintf('Event %d', $i),
622 'eventtype' => 'user',
623 'userid' => $user->id,
624 'timesort' => $i,
625 'type' => CALENDAR_EVENT_TYPE_ACTION,
626 'courseid' => $course2->id,
630 $events = $vault->get_action_events_by_course($user, $course1, 3);
631 $this->assertCount(3, $events);
632 $this->assertEquals('Event 3', $events[0]->get_name());
633 $this->assertEquals('Event 4', $events[1]->get_name());
634 $this->assertEquals('Event 5', $events[2]->get_name());
636 $events = $vault->get_action_events_by_course($user, $course1, 3, null, null, 1);
638 $this->assertCount(1, $events);
639 $this->assertEquals('Event 3', $events[0]->get_name());
641 $events = $vault->get_action_events_by_course($user, $course1, 6);
643 $this->assertCount(0, $events);
647 * Test that get_action_events_by_course returns events before the
648 * provided timesort value.
650 public function test_get_action_events_by_course_before_time() {
651 $user = $this->getDataGenerator()->create_user();
652 $course1 = $this->getDataGenerator()->create_course();
653 $course2 = $this->getDataGenerator()->create_course();
654 $factory = new action_event_test_factory();
655 $strategy = new raw_event_retrieval_strategy();
656 $vault = new event_vault($factory, $strategy);
658 $this->resetAfterTest(true);
659 $this->setAdminuser();
660 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
661 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
663 for ($i = 1; $i < 6; $i++) {
664 create_event([
665 'name' => sprintf('Event %d', $i),
666 'eventtype' => 'user',
667 'userid' => $user->id,
668 'timesort' => $i,
669 'type' => CALENDAR_EVENT_TYPE_ACTION,
670 'courseid' => $course1->id,
674 for ($i = 6; $i < 12; $i++) {
675 create_event([
676 'name' => sprintf('Event %d', $i),
677 'eventtype' => 'user',
678 'userid' => $user->id,
679 'timesort' => $i,
680 'type' => CALENDAR_EVENT_TYPE_ACTION,
681 'courseid' => $course2->id,
685 $events = $vault->get_action_events_by_course($user, $course1, null, 3);
687 $this->assertCount(3, $events);
688 $this->assertEquals('Event 1', $events[0]->get_name());
689 $this->assertEquals('Event 2', $events[1]->get_name());
690 $this->assertEquals('Event 3', $events[2]->get_name());
692 $events = $vault->get_action_events_by_course($user, $course1, null, 3, null, 1);
694 $this->assertCount(1, $events);
695 $this->assertEquals('Event 1', $events[0]->get_name());
697 $events = $vault->get_action_events_by_course($user, $course1, 6);
699 $this->assertCount(0, $events);
703 * Test that get_action_events_by_course returns events between the
704 * provided timesort values.
706 public function test_get_action_events_by_course_between_time() {
707 $user = $this->getDataGenerator()->create_user();
708 $course1 = $this->getDataGenerator()->create_course();
709 $course2 = $this->getDataGenerator()->create_course();
710 $factory = new action_event_test_factory();
711 $strategy = new raw_event_retrieval_strategy();
712 $vault = new event_vault($factory, $strategy);
714 $this->resetAfterTest(true);
715 $this->setAdminuser();
716 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
717 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
719 for ($i = 1; $i < 6; $i++) {
720 create_event([
721 'name' => sprintf('Event %d', $i),
722 'eventtype' => 'user',
723 'userid' => $user->id,
724 'timesort' => $i,
725 'type' => CALENDAR_EVENT_TYPE_ACTION,
726 'courseid' => $course1->id,
730 for ($i = 6; $i < 12; $i++) {
731 create_event([
732 'name' => sprintf('Event %d', $i),
733 'eventtype' => 'user',
734 'userid' => $user->id,
735 'timesort' => $i,
736 'type' => CALENDAR_EVENT_TYPE_ACTION,
737 'courseid' => $course2->id,
741 $events = $vault->get_action_events_by_course($user, $course1, 2, 4);
743 $this->assertCount(3, $events);
744 $this->assertEquals('Event 2', $events[0]->get_name());
745 $this->assertEquals('Event 3', $events[1]->get_name());
746 $this->assertEquals('Event 4', $events[2]->get_name());
748 $events = $vault->get_action_events_by_course($user, $course1, 2, 4, null, 1);
750 $this->assertCount(1, $events);
751 $this->assertEquals('Event 2', $events[0]->get_name());
755 * Test that get_action_events_by_course returns events between the
756 * provided timesort values and after the last seen event when one is
757 * provided.
759 public function test_get_action_events_by_course_between_time_after_event() {
760 $user = $this->getDataGenerator()->create_user();
761 $course1 = $this->getDataGenerator()->create_course();
762 $course2 = $this->getDataGenerator()->create_course();
763 $factory = new action_event_test_factory();
764 $strategy = new raw_event_retrieval_strategy();
765 $vault = new event_vault($factory, $strategy);
766 $records = [];
768 $this->resetAfterTest(true);
769 $this->setAdminuser();
770 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
771 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
773 for ($i = 1; $i < 21; $i++) {
774 $records[] = create_event([
775 'name' => sprintf('Event %d', $i),
776 'eventtype' => 'user',
777 'userid' => $user->id,
778 'timesort' => $i,
779 'type' => CALENDAR_EVENT_TYPE_ACTION,
780 'courseid' => $course1->id,
784 for ($i = 21; $i < 41; $i++) {
785 $records[] = create_event([
786 'name' => sprintf('Event %d', $i),
787 'eventtype' => 'user',
788 'userid' => $user->id,
789 'timesort' => $i,
790 'type' => CALENDAR_EVENT_TYPE_ACTION,
791 'courseid' => $course2->id,
795 $aftereventid = $records[6]->id;
796 $afterevent = $vault->get_event_by_id($aftereventid);
797 $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent);
799 $this->assertCount(8, $events);
800 $this->assertEquals('Event 8', $events[0]->get_name());
802 $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent, 3);
804 $this->assertCount(3, $events);
808 * Test that get_action_events_by_course returns events between the
809 * provided timesort values and the last seen event can be provided to
810 * get paginated results.
812 public function test_get_action_events_by_course_between_time_skip_even_records() {
813 $user = $this->getDataGenerator()->create_user();
814 $course1 = $this->getDataGenerator()->create_course();
815 $course2 = $this->getDataGenerator()->create_course();
816 // The factory will return every event that is divisible by 2.
817 $factory = new action_event_test_factory(function($actionevent) {
818 static $count = 0;
819 $count++;
820 return ($count % 2) ? true : false;
822 $strategy = new raw_event_retrieval_strategy();
823 $vault = new event_vault($factory, $strategy);
825 $this->resetAfterTest(true);
826 $this->setAdminuser();
827 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
828 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
830 for ($i = 1; $i < 41; $i++) {
831 create_event([
832 'name' => sprintf('Event %d', $i),
833 'eventtype' => 'user',
834 'userid' => $user->id,
835 'timesort' => $i,
836 'type' => CALENDAR_EVENT_TYPE_ACTION,
837 'courseid' => $course1->id,
841 for ($i = 41; $i < 81; $i++) {
842 create_event([
843 'name' => sprintf('Event %d', $i),
844 'eventtype' => 'user',
845 'userid' => $user->id,
846 'timesort' => $i,
847 'type' => CALENDAR_EVENT_TYPE_ACTION,
848 'courseid' => $course2->id,
852 $events = $vault->get_action_events_by_course($user, $course1, 3, 35, null, 5);
854 $this->assertCount(5, $events);
855 $this->assertEquals('Event 3', $events[0]->get_name());
856 $this->assertEquals('Event 5', $events[1]->get_name());
857 $this->assertEquals('Event 7', $events[2]->get_name());
858 $this->assertEquals('Event 9', $events[3]->get_name());
859 $this->assertEquals('Event 11', $events[4]->get_name());
861 $afterevent = $events[4];
862 $events = $vault->get_action_events_by_course($user, $course1, 3, 35, $afterevent, 5);
864 $this->assertCount(5, $events);
865 $this->assertEquals('Event 13', $events[0]->get_name());
866 $this->assertEquals('Event 15', $events[1]->get_name());
867 $this->assertEquals('Event 17', $events[2]->get_name());
868 $this->assertEquals('Event 19', $events[3]->get_name());
869 $this->assertEquals('Event 21', $events[4]->get_name());
873 * Test that get_action_events_by_course returns events between the
874 * provided timesort values. The database will continue to be read until the
875 * number of events requested has been satisfied. In this case the first
876 * five events are rejected so it should require two database requests.
878 public function test_get_action_events_by_course_between_time_skip_first_records() {
879 $user = $this->getDataGenerator()->create_user();
880 $course1 = $this->getDataGenerator()->create_course();
881 $course2 = $this->getDataGenerator()->create_course();
882 $limit = 5;
883 $seen = 0;
884 // The factory will skip the first $limit events.
885 $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
886 if ($seen < $limit) {
887 $seen++;
888 return false;
889 } else {
890 return true;
893 $strategy = new raw_event_retrieval_strategy();
894 $vault = new event_vault($factory, $strategy);
896 $this->resetAfterTest(true);
897 $this->setAdminuser();
898 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
899 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
901 for ($i = 1; $i < 21; $i++) {
902 create_event([
903 'name' => sprintf('Event %d', $i),
904 'eventtype' => 'user',
905 'userid' => $user->id,
906 'timesort' => $i,
907 'type' => CALENDAR_EVENT_TYPE_ACTION,
908 'courseid' => $course1->id,
912 for ($i = 21; $i < 41; $i++) {
913 create_event([
914 'name' => sprintf('Event %d', $i),
915 'eventtype' => 'user',
916 'userid' => $user->id,
917 'timesort' => $i,
918 'type' => CALENDAR_EVENT_TYPE_ACTION,
919 'courseid' => $course2->id,
923 $events = $vault->get_action_events_by_course($user, $course1, 1, 20, null, $limit);
925 $this->assertCount($limit, $events);
926 $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
927 $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
928 $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
929 $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
930 $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
934 * Test that get_action_events_by_course returns events between the
935 * provided timesort values and after the last seen event when one is
936 * provided. This should work even when the event ids aren't ordered the
937 * same as the timesort order.
939 public function test_get_action_events_by_course_non_consecutive_ids() {
940 $this->resetAfterTest(true);
941 $this->setAdminuser();
943 $user = $this->getDataGenerator()->create_user();
944 $course1 = $this->getDataGenerator()->create_course();
945 $course2 = $this->getDataGenerator()->create_course();
946 $factory = new action_event_test_factory();
947 $strategy = new raw_event_retrieval_strategy();
948 $vault = new event_vault($factory, $strategy);
950 $this->setAdminuser();
951 $this->getDataGenerator()->enrol_user($user->id, $course1->id);
952 $this->getDataGenerator()->enrol_user($user->id, $course2->id);
955 * The events should be ordered by timesort as follows:
957 * 1 event 1
958 * 2 event 1
959 * 1 event 2
960 * 2 event 2
961 * 1 event 3
962 * 2 event 3
963 * 1 event 4
964 * 2 event 4
965 * 1 event 5
966 * 2 event 5
967 * 1 event 6
968 * 2 event 6
969 * 1 event 7
970 * 2 event 7
971 * 1 event 8
972 * 2 event 8
973 * 1 event 9
974 * 2 event 9
975 * 1 event 10
976 * 2 event 10
978 $records = [];
979 for ($i = 1; $i < 11; $i++) {
980 $records[] = create_event([
981 'name' => sprintf('1 event %d', $i),
982 'eventtype' => 'user',
983 'userid' => $user->id,
984 'timesort' => $i,
985 'type' => CALENDAR_EVENT_TYPE_ACTION,
986 'courseid' => $course1->id,
990 for ($i = 1; $i < 11; $i++) {
991 $records[] = create_event([
992 'name' => sprintf('2 event %d', $i),
993 'eventtype' => 'user',
994 'userid' => $user->id,
995 'timesort' => $i,
996 'type' => CALENDAR_EVENT_TYPE_ACTION,
997 'courseid' => $course1->id,
1001 // Create events for the other course.
1002 for ($i = 1; $i < 11; $i++) {
1003 $records[] = create_event([
1004 'name' => sprintf('3 event %d', $i),
1005 'eventtype' => 'user',
1006 'userid' => $user->id,
1007 'timesort' => $i,
1008 'type' => CALENDAR_EVENT_TYPE_ACTION,
1009 'courseid' => $course2->id,
1014 * Expected result set:
1016 * 2 event 4
1017 * 1 event 5
1018 * 2 event 5
1019 * 1 event 6
1020 * 2 event 6
1021 * 1 event 7
1022 * 2 event 7
1023 * 1 event 8
1024 * 2 event 8
1026 $aftereventid = $records[3]->id;
1027 $afterevent = $vault->get_event_by_id($aftereventid);
1028 // Offset results by event with name "1 event 4" which has the same timesort
1029 // value as the lower boundary of this query (3). Confirm that the given
1030 // $afterevent is used to ignore events with the same timesortfrom values.
1031 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1033 $this->assertCount(9, $events);
1034 $this->assertEquals('2 event 4', $events[0]->get_name());
1035 $this->assertEquals('2 event 8', $events[8]->get_name());
1038 * Expected result set:
1040 * 2 event 4
1041 * 1 event 5
1043 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent, 2);
1045 $this->assertCount(2, $events);
1046 $this->assertEquals('2 event 4', $events[0]->get_name());
1047 $this->assertEquals('1 event 5', $events[1]->get_name());
1050 * Expected result set:
1052 * 2 event 8
1054 $aftereventid = $records[7]->id;
1055 $afterevent = $vault->get_event_by_id($aftereventid);
1056 // Offset results by event with name "1 event 8" which has the same timesort
1057 // value as the upper boundary of this query (8). Confirm that the given
1058 // $afterevent is used to ignore events with the same timesortto values.
1059 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1061 $this->assertCount(1, $events);
1062 $this->assertEquals('2 event 8', $events[0]->get_name());
1065 * Expected empty result set.
1067 $aftereventid = $records[18]->id;
1068 $afterevent = $vault->get_event_by_id($aftereventid);
1069 // Offset results by event with name "2 event 9" which has a timesort
1070 // value larger than the upper boundary of this query (9 > 8). Confirm
1071 // that the given $afterevent is used for filtering events.
1072 $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1074 $this->assertEmpty($events);
1078 * There are subtle cases where the priority of an event override may be identical to another.
1079 * For example, if you duplicate a group override, but make it apply to a different group. Now
1080 * there are two overrides with exactly the same overridden dates. In this case the priority of
1081 * both is 1.
1083 * In this situation:
1084 * - A user in group A should see only the A override
1085 * - A user in group B should see only the B override
1086 * - A user in both A and B should see both
1088 public function test_get_action_events_by_course_with_identical_group_override_priorities() {
1089 $this->resetAfterTest();
1090 $this->setAdminuser();
1092 $course = $this->getDataGenerator()->create_course();
1094 // Create an assign instance.
1095 $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1096 $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
1098 // Create users.
1099 $users = [
1100 'Only in group A' => $this->getDataGenerator()->create_user(),
1101 'Only in group B' => $this->getDataGenerator()->create_user(),
1102 'In group A and B' => $this->getDataGenerator()->create_user(),
1103 'In no groups' => $this->getDataGenerator()->create_user()
1106 // Enrol users.
1107 foreach ($users as $user) {
1108 $this->getDataGenerator()->enrol_user($user->id, $course->id);
1111 // Create groups.
1112 $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1113 $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1115 // Add members to groups.
1116 // Group A.
1117 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
1118 $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
1120 // Group B.
1121 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
1122 $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
1124 // Events with the same module name, instance and event type.
1125 $events = [
1127 'name' => 'Assignment 1 due date - Group A override',
1128 'description' => '',
1129 'format' => 1,
1130 'courseid' => $course->id,
1131 'groupid' => $groupa->id,
1132 'userid' => 2,
1133 'modulename' => 'assign',
1134 'instance' => $assigninstance->id,
1135 'eventtype' => 'due',
1136 'type' => CALENDAR_EVENT_TYPE_ACTION,
1137 'timestart' => 1,
1138 'timeduration' => 0,
1139 'visible' => 1,
1140 'priority' => 1
1143 'name' => 'Assignment 1 due date - Group B override',
1144 'description' => '',
1145 'format' => 1,
1146 'courseid' => $course->id,
1147 'groupid' => $groupb->id,
1148 'userid' => 2,
1149 'modulename' => 'assign',
1150 'instance' => $assigninstance->id,
1151 'eventtype' => 'due',
1152 'type' => CALENDAR_EVENT_TYPE_ACTION,
1153 'timestart' => 1,
1154 'timeduration' => 0,
1155 'visible' => 1,
1156 'priority' => 1
1159 'name' => 'Assignment 1 due date',
1160 'description' => '',
1161 'format' => 1,
1162 'courseid' => $course->id,
1163 'groupid' => 0,
1164 'userid' => 2,
1165 'modulename' => 'assign',
1166 'instance' => $assigninstance->id,
1167 'eventtype' => 'due',
1168 'type' => CALENDAR_EVENT_TYPE_ACTION,
1169 'timestart' => 1,
1170 'timeduration' => 0,
1171 'visible' => 1,
1172 'priority' => null,
1176 foreach ($events as $event) {
1177 calendar_event::create($event, false);
1180 $factory = new action_event_test_factory();
1181 $strategy = new raw_event_retrieval_strategy();
1182 $vault = new event_vault($factory, $strategy);
1184 $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $course, $vault) {
1185 // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
1186 // It needs to be fixed, see MDL-58736.
1187 $this->setUser($users[$description]);
1188 return $carry + [
1189 'For user ' . lcfirst($description) => $vault->get_action_events_by_course($users[$description], $course)
1191 }, []);
1193 foreach ($usersevents as $description => $userevents) {
1194 if ($description == 'For user in group A and B') {
1195 // User is in both A and B, so they should see the override for both
1196 // given that the priority is the same.
1197 $this->assertCount(2, $userevents);
1198 continue;
1201 // Otherwise there should be only one assign event for each user.
1202 $this->assertCount(1, $userevents);
1205 // User in only group A should see the group A override.
1206 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
1208 // User in only group B should see the group B override.
1209 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
1211 // User in group A and B should see see both overrides since the priorities are the same.
1212 $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
1213 $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
1215 // User in no groups should see the plain assignment event.
1216 $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());