e4a1b21236e7b55ae3b086cba4d69ae0d8377ca6
[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 get_action_events_by_course returns events after the
562      * provided timesort value.
563      */
564     public function test_get_action_events_by_course_after_time() {
565         $user = $this->getDataGenerator()->create_user();
566         $course1 = $this->getDataGenerator()->create_course();
567         $course2 = $this->getDataGenerator()->create_course();
568         $factory = new action_event_test_factory();
569         $strategy = new raw_event_retrieval_strategy();
570         $vault = new event_vault($factory, $strategy);
572         $this->resetAfterTest(true);
573         $this->setAdminuser();
574         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
575         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
577         for ($i = 1; $i < 6; $i++) {
578             create_event([
579                 'name' => sprintf('Event %d', $i),
580                 'eventtype' => 'user',
581                 'userid' => $user->id,
582                 'timesort' => $i,
583                 'type' => CALENDAR_EVENT_TYPE_ACTION,
584                 'courseid' => $course1->id,
585             ]);
586         }
588         for ($i = 6; $i < 12; $i++) {
589             create_event([
590                 'name' => sprintf('Event %d', $i),
591                 'eventtype' => 'user',
592                 'userid' => $user->id,
593                 'timesort' => $i,
594                 'type' => CALENDAR_EVENT_TYPE_ACTION,
595                 'courseid' => $course2->id,
596             ]);
597         }
599         $events = $vault->get_action_events_by_course($user, $course1, 3);
600         $this->assertCount(3, $events);
601         $this->assertEquals('Event 3', $events[0]->get_name());
602         $this->assertEquals('Event 4', $events[1]->get_name());
603         $this->assertEquals('Event 5', $events[2]->get_name());
605         $events = $vault->get_action_events_by_course($user, $course1, 3, null, null, 1);
607         $this->assertCount(1, $events);
608         $this->assertEquals('Event 3', $events[0]->get_name());
610         $events = $vault->get_action_events_by_course($user, $course1, 6);
612         $this->assertCount(0, $events);
613     }
615     /**
616      * Test that get_action_events_by_course returns events before the
617      * provided timesort value.
618      */
619     public function test_get_action_events_by_course_before_time() {
620         $user = $this->getDataGenerator()->create_user();
621         $course1 = $this->getDataGenerator()->create_course();
622         $course2 = $this->getDataGenerator()->create_course();
623         $factory = new action_event_test_factory();
624         $strategy = new raw_event_retrieval_strategy();
625         $vault = new event_vault($factory, $strategy);
627         $this->resetAfterTest(true);
628         $this->setAdminuser();
629         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
630         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
632         for ($i = 1; $i < 6; $i++) {
633             create_event([
634                 'name' => sprintf('Event %d', $i),
635                 'eventtype' => 'user',
636                 'userid' => $user->id,
637                 'timesort' => $i,
638                 'type' => CALENDAR_EVENT_TYPE_ACTION,
639                 'courseid' => $course1->id,
640             ]);
641         }
643         for ($i = 6; $i < 12; $i++) {
644             create_event([
645                 'name' => sprintf('Event %d', $i),
646                 'eventtype' => 'user',
647                 'userid' => $user->id,
648                 'timesort' => $i,
649                 'type' => CALENDAR_EVENT_TYPE_ACTION,
650                 'courseid' => $course2->id,
651             ]);
652         }
654         $events = $vault->get_action_events_by_course($user, $course1, null, 3);
656         $this->assertCount(3, $events);
657         $this->assertEquals('Event 1', $events[0]->get_name());
658         $this->assertEquals('Event 2', $events[1]->get_name());
659         $this->assertEquals('Event 3', $events[2]->get_name());
661         $events = $vault->get_action_events_by_course($user, $course1, null, 3, null, 1);
663         $this->assertCount(1, $events);
664         $this->assertEquals('Event 1', $events[0]->get_name());
666         $events = $vault->get_action_events_by_course($user, $course1, 6);
668         $this->assertCount(0, $events);
669     }
671     /**
672      * Test that get_action_events_by_course returns events between the
673      * provided timesort values.
674      */
675     public function test_get_action_events_by_course_between_time() {
676         $user = $this->getDataGenerator()->create_user();
677         $course1 = $this->getDataGenerator()->create_course();
678         $course2 = $this->getDataGenerator()->create_course();
679         $factory = new action_event_test_factory();
680         $strategy = new raw_event_retrieval_strategy();
681         $vault = new event_vault($factory, $strategy);
683         $this->resetAfterTest(true);
684         $this->setAdminuser();
685         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
686         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
688         for ($i = 1; $i < 6; $i++) {
689             create_event([
690                 'name' => sprintf('Event %d', $i),
691                 'eventtype' => 'user',
692                 'userid' => $user->id,
693                 'timesort' => $i,
694                 'type' => CALENDAR_EVENT_TYPE_ACTION,
695                 'courseid' => $course1->id,
696             ]);
697         }
699         for ($i = 6; $i < 12; $i++) {
700             create_event([
701                 'name' => sprintf('Event %d', $i),
702                 'eventtype' => 'user',
703                 'userid' => $user->id,
704                 'timesort' => $i,
705                 'type' => CALENDAR_EVENT_TYPE_ACTION,
706                 'courseid' => $course2->id,
707             ]);
708         }
710         $events = $vault->get_action_events_by_course($user, $course1, 2, 4);
712         $this->assertCount(3, $events);
713         $this->assertEquals('Event 2', $events[0]->get_name());
714         $this->assertEquals('Event 3', $events[1]->get_name());
715         $this->assertEquals('Event 4', $events[2]->get_name());
717         $events = $vault->get_action_events_by_course($user, $course1, 2, 4, null, 1);
719         $this->assertCount(1, $events);
720         $this->assertEquals('Event 2', $events[0]->get_name());
721     }
723     /**
724      * Test that get_action_events_by_course returns events between the
725      * provided timesort values and after the last seen event when one is
726      * provided.
727      */
728     public function test_get_action_events_by_course_between_time_after_event() {
729         $user = $this->getDataGenerator()->create_user();
730         $course1 = $this->getDataGenerator()->create_course();
731         $course2 = $this->getDataGenerator()->create_course();
732         $factory = new action_event_test_factory();
733         $strategy = new raw_event_retrieval_strategy();
734         $vault = new event_vault($factory, $strategy);
735         $records = [];
737         $this->resetAfterTest(true);
738         $this->setAdminuser();
739         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
740         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
742         for ($i = 1; $i < 21; $i++) {
743             $records[] = create_event([
744                 'name' => sprintf('Event %d', $i),
745                 'eventtype' => 'user',
746                 'userid' => $user->id,
747                 'timesort' => $i,
748                 'type' => CALENDAR_EVENT_TYPE_ACTION,
749                 'courseid' => $course1->id,
750             ]);
751         }
753         for ($i = 21; $i < 41; $i++) {
754             $records[] = create_event([
755                 'name' => sprintf('Event %d', $i),
756                 'eventtype' => 'user',
757                 'userid' => $user->id,
758                 'timesort' => $i,
759                 'type' => CALENDAR_EVENT_TYPE_ACTION,
760                 'courseid' => $course2->id,
761             ]);
762         }
764         $aftereventid = $records[6]->id;
765         $afterevent = $vault->get_event_by_id($aftereventid);
766         $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent);
768         $this->assertCount(8, $events);
769         $this->assertEquals('Event 8', $events[0]->get_name());
771         $events = $vault->get_action_events_by_course($user, $course1, 3, 15, $afterevent, 3);
773         $this->assertCount(3, $events);
774     }
776     /**
777      * Test that get_action_events_by_course returns events between the
778      * provided timesort values and the last seen event can be provided to
779      * get paginated results.
780      */
781     public function test_get_action_events_by_course_between_time_skip_even_records() {
782         $user = $this->getDataGenerator()->create_user();
783         $course1 = $this->getDataGenerator()->create_course();
784         $course2 = $this->getDataGenerator()->create_course();
785         // The factory will return every event that is divisible by 2.
786         $factory = new action_event_test_factory(function($actionevent) {
787             static $count = 0;
788             $count++;
789             return ($count % 2) ? true : false;
790         });
791         $strategy = new raw_event_retrieval_strategy();
792         $vault = new event_vault($factory, $strategy);
794         $this->resetAfterTest(true);
795         $this->setAdminuser();
796         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
797         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
799         for ($i = 1; $i < 41; $i++) {
800             create_event([
801                 'name' => sprintf('Event %d', $i),
802                 'eventtype' => 'user',
803                 'userid' => $user->id,
804                 'timesort' => $i,
805                 'type' => CALENDAR_EVENT_TYPE_ACTION,
806                 'courseid' => $course1->id,
807             ]);
808         }
810         for ($i = 41; $i < 81; $i++) {
811             create_event([
812                 'name' => sprintf('Event %d', $i),
813                 'eventtype' => 'user',
814                 'userid' => $user->id,
815                 'timesort' => $i,
816                 'type' => CALENDAR_EVENT_TYPE_ACTION,
817                 'courseid' => $course2->id,
818             ]);
819         }
821         $events = $vault->get_action_events_by_course($user, $course1, 3, 35, null, 5);
823         $this->assertCount(5, $events);
824         $this->assertEquals('Event 3', $events[0]->get_name());
825         $this->assertEquals('Event 5', $events[1]->get_name());
826         $this->assertEquals('Event 7', $events[2]->get_name());
827         $this->assertEquals('Event 9', $events[3]->get_name());
828         $this->assertEquals('Event 11', $events[4]->get_name());
830         $afterevent = $events[4];
831         $events = $vault->get_action_events_by_course($user, $course1, 3, 35, $afterevent, 5);
833         $this->assertCount(5, $events);
834         $this->assertEquals('Event 13', $events[0]->get_name());
835         $this->assertEquals('Event 15', $events[1]->get_name());
836         $this->assertEquals('Event 17', $events[2]->get_name());
837         $this->assertEquals('Event 19', $events[3]->get_name());
838         $this->assertEquals('Event 21', $events[4]->get_name());
839     }
841     /**
842      * Test that get_action_events_by_course returns events between the
843      * provided timesort values. The database will continue to be read until the
844      * number of events requested has been satisfied. In this case the first
845      * five events are rejected so it should require two database requests.
846      */
847     public function test_get_action_events_by_course_between_time_skip_first_records() {
848         $user = $this->getDataGenerator()->create_user();
849         $course1 = $this->getDataGenerator()->create_course();
850         $course2 = $this->getDataGenerator()->create_course();
851         $limit = 5;
852         $seen = 0;
853         // The factory will skip the first $limit events.
854         $factory = new action_event_test_factory(function($actionevent) use (&$seen, $limit) {
855             if ($seen < $limit) {
856                 $seen++;
857                 return false;
858             } else {
859                 return true;
860             }
861         });
862         $strategy = new raw_event_retrieval_strategy();
863         $vault = new event_vault($factory, $strategy);
865         $this->resetAfterTest(true);
866         $this->setAdminuser();
867         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
868         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
870         for ($i = 1; $i < 21; $i++) {
871             create_event([
872                 'name' => sprintf('Event %d', $i),
873                 'eventtype' => 'user',
874                 'userid' => $user->id,
875                 'timesort' => $i,
876                 'type' => CALENDAR_EVENT_TYPE_ACTION,
877                 'courseid' => $course1->id,
878             ]);
879         }
881         for ($i = 21; $i < 41; $i++) {
882             create_event([
883                 'name' => sprintf('Event %d', $i),
884                 'eventtype' => 'user',
885                 'userid' => $user->id,
886                 'timesort' => $i,
887                 'type' => CALENDAR_EVENT_TYPE_ACTION,
888                 'courseid' => $course2->id,
889             ]);
890         }
892         $events = $vault->get_action_events_by_course($user, $course1, 1, 20, null, $limit);
894         $this->assertCount($limit, $events);
895         $this->assertEquals(sprintf('Event %d', $limit + 1), $events[0]->get_name());
896         $this->assertEquals(sprintf('Event %d', $limit + 2), $events[1]->get_name());
897         $this->assertEquals(sprintf('Event %d', $limit + 3), $events[2]->get_name());
898         $this->assertEquals(sprintf('Event %d', $limit + 4), $events[3]->get_name());
899         $this->assertEquals(sprintf('Event %d', $limit + 5), $events[4]->get_name());
900     }
902     /**
903      * Test that get_action_events_by_course returns events between the
904      * provided timesort values and after the last seen event when one is
905      * provided. This should work even when the event ids aren't ordered the
906      * same as the timesort order.
907      */
908     public function test_get_action_events_by_course_non_consecutive_ids() {
909         $this->resetAfterTest(true);
910         $this->setAdminuser();
912         $user = $this->getDataGenerator()->create_user();
913         $course1 = $this->getDataGenerator()->create_course();
914         $course2 = $this->getDataGenerator()->create_course();
915         $factory = new action_event_test_factory();
916         $strategy = new raw_event_retrieval_strategy();
917         $vault = new event_vault($factory, $strategy);
919         $this->setAdminuser();
920         $this->getDataGenerator()->enrol_user($user->id, $course1->id);
921         $this->getDataGenerator()->enrol_user($user->id, $course2->id);
923         /*
924          * The events should be ordered by timesort as follows:
925          *
926          * 1 event 1
927          * 2 event 1
928          * 1 event 2
929          * 2 event 2
930          * 1 event 3
931          * 2 event 3
932          * 1 event 4
933          * 2 event 4
934          * 1 event 5
935          * 2 event 5
936          * 1 event 6
937          * 2 event 6
938          * 1 event 7
939          * 2 event 7
940          * 1 event 8
941          * 2 event 8
942          * 1 event 9
943          * 2 event 9
944          * 1 event 10
945          * 2 event 10
946          */
947         $records = [];
948         for ($i = 1; $i < 11; $i++) {
949             $records[] = create_event([
950                 'name' => sprintf('1 event %d', $i),
951                 'eventtype' => 'user',
952                 'userid' => $user->id,
953                 'timesort' => $i,
954                 'type' => CALENDAR_EVENT_TYPE_ACTION,
955                 'courseid' => $course1->id,
956             ]);
957         }
959         for ($i = 1; $i < 11; $i++) {
960             $records[] = create_event([
961                 'name' => sprintf('2 event %d', $i),
962                 'eventtype' => 'user',
963                 'userid' => $user->id,
964                 'timesort' => $i,
965                 'type' => CALENDAR_EVENT_TYPE_ACTION,
966                 'courseid' => $course1->id,
967             ]);
968         }
970         // Create events for the other course.
971         for ($i = 1; $i < 11; $i++) {
972             $records[] = create_event([
973                 'name' => sprintf('3 event %d', $i),
974                 'eventtype' => 'user',
975                 'userid' => $user->id,
976                 'timesort' => $i,
977                 'type' => CALENDAR_EVENT_TYPE_ACTION,
978                 'courseid' => $course2->id,
979             ]);
980         }
982         /*
983          * Expected result set:
984          *
985          * 2 event 4
986          * 1 event 5
987          * 2 event 5
988          * 1 event 6
989          * 2 event 6
990          * 1 event 7
991          * 2 event 7
992          * 1 event 8
993          * 2 event 8
994          */
995         $aftereventid = $records[3]->id;
996         $afterevent = $vault->get_event_by_id($aftereventid);
997         // Offset results by event with name "1 event 4" which has the same timesort
998         // value as the lower boundary of this query (3). Confirm that the given
999         // $afterevent is used to ignore events with the same timesortfrom values.
1000         $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1002         $this->assertCount(9, $events);
1003         $this->assertEquals('2 event 4', $events[0]->get_name());
1004         $this->assertEquals('2 event 8', $events[8]->get_name());
1006         /*
1007          * Expected result set:
1008          *
1009          * 2 event 4
1010          * 1 event 5
1011          */
1012         $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent, 2);
1014         $this->assertCount(2, $events);
1015         $this->assertEquals('2 event 4', $events[0]->get_name());
1016         $this->assertEquals('1 event 5', $events[1]->get_name());
1018         /*
1019          * Expected result set:
1020          *
1021          * 2 event 8
1022          */
1023         $aftereventid = $records[7]->id;
1024         $afterevent = $vault->get_event_by_id($aftereventid);
1025         // Offset results by event with name "1 event 8" which has the same timesort
1026         // value as the upper boundary of this query (8). Confirm that the given
1027         // $afterevent is used to ignore events with the same timesortto values.
1028         $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1030         $this->assertCount(1, $events);
1031         $this->assertEquals('2 event 8', $events[0]->get_name());
1033         /*
1034          * Expected empty result set.
1035          */
1036         $aftereventid = $records[18]->id;
1037         $afterevent = $vault->get_event_by_id($aftereventid);
1038         // Offset results by event with name "2 event 9" which has a timesort
1039         // value larger than the upper boundary of this query (9 > 8). Confirm
1040         // that the given $afterevent is used for filtering events.
1041         $events = $vault->get_action_events_by_course($user, $course1, 3, 8, $afterevent);
1043         $this->assertEmpty($events);
1044     }
1046     /**
1047      * There are subtle cases where the priority of an event override may be identical to another.
1048      * For example, if you duplicate a group override, but make it apply to a different group. Now
1049      * there are two overrides with exactly the same overridden dates. In this case the priority of
1050      * both is 1.
1051      *
1052      * In this situation:
1053      * - A user in group A should see only the A override
1054      * - A user in group B should see only the B override
1055      * - A user in both A and B should see both
1056      */
1057     public function test_get_action_events_by_course_with_identical_group_override_priorities() {
1058         $this->resetAfterTest();
1059         $this->setAdminuser();
1061         $course = $this->getDataGenerator()->create_course();
1063         // Create an assign instance.
1064         $assigngenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
1065         $assigninstance = $assigngenerator->create_instance(['course' => $course->id]);
1067         // Create users.
1068         $users = [
1069             'Only in group A'  => $this->getDataGenerator()->create_user(),
1070             'Only in group B'  => $this->getDataGenerator()->create_user(),
1071             'In group A and B' => $this->getDataGenerator()->create_user(),
1072             'In no groups'     => $this->getDataGenerator()->create_user()
1073         ];
1075         // Enrol users.
1076         foreach ($users as $user) {
1077             $this->getDataGenerator()->enrol_user($user->id, $course->id);
1078         }
1080         // Create groups.
1081         $groupa = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1082         $groupb = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1084         // Add members to groups.
1085         // Group A.
1086         $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['Only in group A']->id]);
1087         $this->getDataGenerator()->create_group_member(['groupid' => $groupa->id, 'userid' => $users['In group A and B']->id]);
1089         // Group B.
1090         $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['Only in group B']->id]);
1091         $this->getDataGenerator()->create_group_member(['groupid' => $groupb->id, 'userid' => $users['In group A and B']->id]);
1093         // Events with the same module name, instance and event type.
1094         $events = [
1095             [
1096                 'name' => 'Assignment 1 due date - Group A override',
1097                 'description' => '',
1098                 'format' => 1,
1099                 'courseid' => $course->id,
1100                 'groupid' => $groupa->id,
1101                 'userid' => 2,
1102                 'modulename' => 'assign',
1103                 'instance' => $assigninstance->id,
1104                 'eventtype' => 'due',
1105                 'type' => CALENDAR_EVENT_TYPE_ACTION,
1106                 'timestart' => 1,
1107                 'timeduration' => 0,
1108                 'visible' => 1,
1109                 'priority' => 1
1110             ],
1111             [
1112                 'name' => 'Assignment 1 due date - Group B override',
1113                 'description' => '',
1114                 'format' => 1,
1115                 'courseid' => $course->id,
1116                 'groupid' => $groupb->id,
1117                 'userid' => 2,
1118                 'modulename' => 'assign',
1119                 'instance' => $assigninstance->id,
1120                 'eventtype' => 'due',
1121                 'type' => CALENDAR_EVENT_TYPE_ACTION,
1122                 'timestart' => 1,
1123                 'timeduration' => 0,
1124                 'visible' => 1,
1125                 'priority' => 1
1126             ],
1127             [
1128                 'name' => 'Assignment 1 due date',
1129                 'description' => '',
1130                 'format' => 1,
1131                 'courseid' => $course->id,
1132                 'groupid' => 0,
1133                 'userid' => 2,
1134                 'modulename' => 'assign',
1135                 'instance' => $assigninstance->id,
1136                 'eventtype' => 'due',
1137                 'type' => CALENDAR_EVENT_TYPE_ACTION,
1138                 'timestart' => 1,
1139                 'timeduration' => 0,
1140                 'visible' => 1,
1141                 'priority' => null,
1142             ]
1143         ];
1145         foreach ($events as $event) {
1146             calendar_event::create($event, false);
1147         }
1149         $factory = new action_event_test_factory();
1150         $strategy = new raw_event_retrieval_strategy();
1151         $vault = new event_vault($factory, $strategy);
1153         $usersevents = array_reduce(array_keys($users), function($carry, $description) use ($users, $course, $vault) {
1154             // NB: This is currently needed to make get_action_events_by_timesort return the right thing.
1155             // It needs to be fixed, see MDL-58736.
1156             $this->setUser($users[$description]);
1157             return $carry + [
1158                 'For user ' . lcfirst($description) => $vault->get_action_events_by_course($users[$description], $course)
1159             ];
1160         }, []);
1162         foreach ($usersevents as $description => $userevents) {
1163             if ($description == 'For user in group A and B') {
1164                 // User is in both A and B, so they should see the override for both
1165                 // given that the priority is the same.
1166                 $this->assertCount(2, $userevents);
1167                 continue;
1168             }
1170             // Otherwise there should be only one assign event for each user.
1171             $this->assertCount(1, $userevents);
1172         }
1174         // User in only group A should see the group A override.
1175         $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user only in group A'][0]->get_name());
1177         // User in only group B should see the group B override.
1178         $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user only in group B'][0]->get_name());
1180         // User in group A and B should see see both overrides since the priorities are the same.
1181         $this->assertEquals('Assignment 1 due date - Group A override', $usersevents['For user in group A and B'][0]->get_name());
1182         $this->assertEquals('Assignment 1 due date - Group B override', $usersevents['For user in group A and B'][1]->get_name());
1184         // User in no groups should see the plain assignment event.
1185         $this->assertEquals('Assignment 1 due date', $usersevents['For user in no groups'][0]->get_name());
1186     }