MDL-64063 core_calendar: Unit test for suspended users.
[moodle.git] / calendar / tests / event_vault_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains the class that handles testing of the calendar event vault.
19  *
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
23  */
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.
35  *
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
39  */
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.
45      */
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             ]);
64         }
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);
81     }
83     /**
84      * Test that get_action_events_by_timesort returns events before the
85      * provided timesort value.
86      */
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
104             ]);
105         }
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);
122     }
124     /**
125      * Test that get_action_events_by_timesort returns events between the
126      * provided timesort values.
127      */
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
145             ]);
146         }
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());
159     }
161     /**
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.
165      */
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
184             ]);
185         }
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);
197     }
199     /**
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.
203      */
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;
214         });
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
226             ]);
227         }
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());
247     }
249     /**
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.
254      */
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;
269             }
270         });
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
282             ]);
283         }
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());
293     }
295     /**
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.
300      */
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);
310         /*
311          * The events should be ordered by timesort as follows:
312          *
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
333          */
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
343             ]);
344         }
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
354             ]);
355         }
357         /*
358          * Expected result set:
359          *
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
369          */
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());
381         /*
382          * Expected result set:
383          *
384          * 2 event 4
385          * 1 event 5
386          */
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());
393         /*
394          * Expected result set:
395          *
396          * 2 event 8
397          */
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());
408         /*
409          * Expected empty result set.
410          */
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);
418     }
420     /**
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.
425      *
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
430      */
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()
447         ];
449         // Enrol users.
450         foreach ($users as $user) {
451             $this->getDataGenerator()->enrol_user($user->id, $course->id);
452         }
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 = [
469             [
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
484             ],
485             [
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
500             ],
501             [
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,
516             ]
517         ];
519         foreach ($events as $event) {
520             calendar_event::create($event, false);
521         }
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;
540             }
542             // Otherwise there should be only one assign event for each user.
543             $this->assertCount(1, $userevents);
544         }
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());
558     }
560     /**
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.
563      */
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))
575             ]
576         );
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());
589     }
591     /**
592      * Test that get_action_events_by_course returns events after the
593      * provided timesort value.
594      */
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,
616             ]);
617         }
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,
627             ]);
628         }
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);
644     }
646     /**
647      * Test that get_action_events_by_course returns events before the
648      * provided timesort value.
649      */
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,
671             ]);
672         }
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,
682             ]);
683         }
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);
700     }
702     /**
703      * Test that get_action_events_by_course returns events between the
704      * provided timesort values.
705      */
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,
727             ]);
728         }
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,
738             ]);
739         }
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());
752     }
754     /**
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.
758      */
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,
781             ]);
782         }
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,
792             ]);
793         }
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);
805     }
807     /**
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.
811      */
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;
821         });
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,
838             ]);
839         }
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,
849             ]);
850         }
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());
870     }
872     /**
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.
877      */
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;
891             }
892         });
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,
909             ]);
910         }
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,
920             ]);
921         }
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());
931     }
933     /**
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.
938      */
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);
954         /*
955          * The events should be ordered by timesort as follows:
956          *
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
977          */
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,
987             ]);
988         }
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,
998             ]);
999         }
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,
1010             ]);
1011         }
1013         /*
1014          * Expected result set:
1015          *
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
1025          */
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());
1037         /*
1038          * Expected result set:
1039          *
1040          * 2 event 4
1041          * 1 event 5
1042          */
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());
1049         /*
1050          * Expected result set:
1051          *
1052          * 2 event 8
1053          */
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());
1064         /*
1065          * Expected empty result set.
1066          */
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);
1075     }
1077     /**
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.
1082      *
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
1087      */
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()
1104         ];
1106         // Enrol users.
1107         foreach ($users as $user) {
1108             $this->getDataGenerator()->enrol_user($user->id, $course->id);
1109         }
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 = [
1126             [
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
1141             ],
1142             [
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
1157             ],
1158             [
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,
1173             ]
1174         ];
1176         foreach ($events as $event) {
1177             calendar_event::create($event, false);
1178         }
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)
1190             ];
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;
1199             }
1201             // Otherwise there should be only one assign event for each user.
1202             $this->assertCount(1, $userevents);
1203         }
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());
1217     }