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/>.
16
17/**
18 * Event factory test.
19 *
20 * @package    core_calendar
21 * @copyright  2017 Cameron Ball <cameron@cameron1729.xyz>
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27global $CFG;
28require_once($CFG->dirroot . '/calendar/lib.php');
29
30use core_calendar\local\event\factories\event_factory;
31use core_calendar\local\event\entities\event_interface;
32
33/**
34 * Event factory testcase.
35 *
36 * @copyright 2017 Cameron Ball <cameron@cameron1729.xyz>
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 */
39class core_calendar_event_factory_testcase extends advanced_testcase {
40    /**
41     * Test event class getters.
42     *
43     * @dataProvider create_instance_testcases()
44     * @param \stdClass $dbrow Row from the event table.
45     * @param callable  $actioncallbackapplier     Action callback applier.
46     * @param callable  $visibilitycallbackapplier Visibility callback applier.
47     * @param callable  $bailoutcheck              Early bail out check function.
48     * @param string    $expectedclass             Class the factory is expected to produce.
49     * @param mixed     $expectedattributevalue    Expected value of the modified attribute.
50     */
51    public function test_create_instance(
52        $dbrow,
53        callable $actioncallbackapplier,
54        callable $visibilitycallbackapplier,
55        callable $bailoutcheck,
56        $expectedclass,
57        $expectedattributevalue
58    ) {
59        $this->resetAfterTest(true);
60        $this->setAdminUser();
61        $event = $this->create_event();
62        $coursecache = [];
63        $modulecache = [];
64        $factory = new event_factory(
65            $actioncallbackapplier,
66            $visibilitycallbackapplier,
67            $bailoutcheck,
68            $coursecache,
69            $modulecache
70        );
71        $dbrow->id = $event->id;
72        $instance = $factory->create_instance($dbrow);
73
74        if ($expectedclass) {
75            $this->assertInstanceOf($expectedclass, $instance);
76        }
77
78        if (is_null($expectedclass)) {
79            $this->assertNull($instance);
80        }
81
82        if ($expectedattributevalue) {
83            $this->assertEquals($instance->testattribute, $expectedattributevalue);
84        }
85    }
86
87    /**
88     * Test invalid callback exception.
89     */
90    public function test_invalid_action_callback() {
91        $this->resetAfterTest(true);
92        $this->setAdminUser();
93        $event = $this->create_event();
94        $coursecache = [];
95        $modulecache = [];
96        $factory = new event_factory(
97            function () {
98                return 'hello';
99            },
100            function () {
101                return true;
102            },
103            function () {
104                return false;
105            },
106            $coursecache,
107            $modulecache
108        );
109
110        $this->expectException('\core_calendar\local\event\exceptions\invalid_callback_exception');
111        $factory->create_instance(
112            (object)[
113                'id' => $event->id,
114                'name' => 'test',
115                'description' => 'Test description',
116                'format' => 2,
117                'categoryid' => 0,
118                'courseid' => 1,
119                'groupid' => 1,
120                'userid' => 1,
121                'repeatid' => 0,
122                'modulename' => 'assign',
123                'instance' => 1,
124                'eventtype' => 'due',
125                'type' => CALENDAR_EVENT_TYPE_ACTION,
126                'timestart' => 123456789,
127                'timeduration' => 12,
128                'timemodified' => 123456789,
129                'timesort' => 123456789,
130                'visible' => 1,
131                'subscriptionid' => 1,
132                'location' => 'Test location',
133            ]
134        );
135    }
136
137    /**
138     * Test invalid callback exception.
139     */
140    public function test_invalid_visibility_callback() {
141        $this->resetAfterTest(true);
142        $this->setAdminUser();
143        $event = $this->create_event();
144        $coursecache = [];
145        $modulecache = [];
146        $factory = new event_factory(
147            function ($event) {
148                return $event;
149            },
150            function () {
151                return 'asdf';
152            },
153            function () {
154                return false;
155            },
156            $coursecache,
157            $modulecache
158        );
159
160        $this->expectException('\core_calendar\local\event\exceptions\invalid_callback_exception');
161        $factory->create_instance(
162            (object)[
163                'id' => $event->id,
164                'name' => 'test',
165                'description' => 'Test description',
166                'format' => 2,
167                'categoryid' => 0,
168                'courseid' => 1,
169                'groupid' => 1,
170                'userid' => 1,
171                'repeatid' => 0,
172                'modulename' => 'assign',
173                'instance' => 1,
174                'eventtype' => 'due',
175                'type' => CALENDAR_EVENT_TYPE_ACTION,
176                'timestart' => 123456789,
177                'timeduration' => 12,
178                'timemodified' => 123456789,
179                'timesort' => 123456789,
180                'visible' => 1,
181                'subscriptionid' => 1,
182                'location' => 'Test location',
183            ]
184        );
185    }
186
187    /**
188     * Test invalid callback exception.
189     */
190    public function test_invalid_bail_callback() {
191        $this->resetAfterTest(true);
192        $this->setAdminUser();
193        $event = $this->create_event();
194        $coursecache = [];
195        $modulecache = [];
196        $factory = new event_factory(
197            function ($event) {
198                return $event;
199            },
200            function () {
201                return true;
202            },
203            function () {
204                return 'asdf';
205            },
206            $coursecache,
207            $modulecache
208        );
209
210        $this->expectException('\core_calendar\local\event\exceptions\invalid_callback_exception');
211        $factory->create_instance(
212            (object)[
213                'id' => $event->id,
214                'name' => 'test',
215                'description' => 'Test description',
216                'format' => 2,
217                'categoryid' => 0,
218                'courseid' => 1,
219                'groupid' => 1,
220                'userid' => 1,
221                'repeatid' => 0,
222                'modulename' => 'assign',
223                'instance' => 1,
224                'eventtype' => 'due',
225                'type' => CALENDAR_EVENT_TYPE_ACTION,
226                'timestart' => 123456789,
227                'timeduration' => 12,
228                'timemodified' => 123456789,
229                'timesort' => 123456789,
230                'visible' => 1,
231                'subscriptionid' => 1,
232                'location' => 'Test location',
233            ]
234        );
235    }
236
237    /**
238     * Test the factory's course cache.
239     */
240    public function test_course_cache() {
241        $this->resetAfterTest(true);
242        $this->setAdminUser();
243        $course = self::getDataGenerator()->create_course();
244        $event = $this->create_event(['courseid' => $course->id]);
245        $coursecache = [];
246        $modulecache = [];
247        $factory = new event_factory(
248            function ($event) {
249                return $event;
250            },
251            function () {
252                return true;
253            },
254            function () {
255                return false;
256            },
257            $coursecache,
258            $modulecache
259        );
260
261        $instance = $factory->create_instance(
262            (object)[
263                'id' => $event->id,
264                'name' => 'test',
265                'description' => 'Test description',
266                'format' => 2,
267                'categoryid' => 0,
268                'courseid' => $course->id,
269                'groupid' => 1,
270                'userid' => 1,
271                'repeatid' => 0,
272                'modulename' => 'assign',
273                'instance' => 1,
274                'eventtype' => 'due',
275                'type' => CALENDAR_EVENT_TYPE_ACTION,
276                'timestart' => 123456789,
277                'timeduration' => 12,
278                'timemodified' => 123456789,
279                'timesort' => 123456789,
280                'visible' => 1,
281                'subscriptionid' => 1,
282                'location' => 'Test location',
283            ]
284        );
285
286        $instance->get_course()->get('fullname');
287        $this->assertArrayHasKey($course->id, $coursecache);
288    }
289
290    /**
291     * Test the factory's module cache.
292     */
293    public function test_module_cache() {
294        $this->resetAfterTest(true);
295        $this->setAdminUser();
296        $course = self::getDataGenerator()->create_course();
297        $event = $this->create_event(['courseid' => $course->id]);
298        $plugingenerator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
299        $assigninstance = $plugingenerator->create_instance(['course' => $course->id]);
300
301        $coursecache = [];
302        $modulecache = [];
303        $factory = new event_factory(
304            function ($event) {
305                return $event;
306            },
307            function () {
308                return true;
309            },
310            function () {
311                return false;
312            },
313            $coursecache,
314            $modulecache
315        );
316
317        $instance = $factory->create_instance(
318            (object)[
319                'id' => $event->id,
320                'name' => 'test',
321                'description' => 'Test description',
322                'format' => 2,
323                'categoryid' => 0,
324                'courseid' => 0,
325                'groupid' => 1,
326                'userid' => 1,
327                'repeatid' => 0,
328                'modulename' => 'assign',
329                'instance' => $assigninstance->id,
330                'eventtype' => 'due',
331                'type' => CALENDAR_EVENT_TYPE_ACTION,
332                'timestart' => 123456789,
333                'timeduration' => 12,
334                'timemodified' => 123456789,
335                'timesort' => 123456789,
336                'visible' => 1,
337                'subscriptionid' => 1,
338                'location' => 'Test location',
339            ]
340        );
341
342        $instance->get_course_module()->get('course');
343        $this->assertArrayHasKey('assign' . '_' . $assigninstance->id, $modulecache);
344    }
345
346    /**
347     * Testcases for the create instance test.
348     *
349     * @return array Array of testcases.
350     */
351    public function create_instance_testcases() {
352        return [
353            'Sample event record with event exposed' => [
354                'dbrow' => (object)[
355                    'name' => 'Test event',
356                    'description' => 'Hello',
357                    'format' => 1,
358                    'categoryid' => 0,
359                    'courseid' => 1,
360                    'groupid' => 1,
361                    'userid' => 1,
362                    'repeatid' => 0,
363                    'modulename' => 'Test module',
364                    'instance' => 1,
365                    'eventtype' => 'Due',
366                    'type' => CALENDAR_EVENT_TYPE_ACTION,
367                    'timestart' => 123456789,
368                    'timeduration' => 123456789,
369                    'timemodified' => 123456789,
370                    'timesort' => 123456789,
371                    'visible' => true,
372                    'subscriptionid' => 1,
373                    'location' => 'Test location',
374                ],
375                'actioncallbackapplier' => function(event_interface $event) {
376                    $event->testattribute = 'Hello';
377                    return $event;
378                },
379                'visibilitycallbackapplier' => function(event_interface $event) {
380                    return true;
381                },
382                'bailoutcheck' => function() {
383                    return false;
384                },
385                event_interface::class,
386                'Hello'
387            ],
388            'Sample event record with event hidden' => [
389                'dbrow' => (object)[
390                    'name' => 'Test event',
391                    'description' => 'Hello',
392                    'format' => 1,
393                    'categoryid' => 0,
394                    'courseid' => 1,
395                    'groupid' => 1,
396                    'userid' => 1,
397                    'repeatid' => 0,
398                    'modulename' => 'Test module',
399                    'instance' => 1,
400                    'eventtype' => 'Due',
401                    'type' => CALENDAR_EVENT_TYPE_ACTION,
402                    'timestart' => 123456789,
403                    'timeduration' => 123456789,
404                    'timemodified' => 123456789,
405                    'timesort' => 123456789,
406                    'visible' => true,
407                    'subscriptionid' => 1,
408                    'location' => 'Test location',
409                ],
410                'actioncallbackapplier' => function(event_interface $event) {
411                    $event->testattribute = 'Hello';
412                    return $event;
413                },
414                'visibilitycallbackapplier' => function(event_interface $event) {
415                    return false;
416                },
417                'bailoutcheck' => function() {
418                    return false;
419                },
420                null,
421                null
422            ],
423            'Sample event record with early bail' => [
424                'dbrow' => (object)[
425                    'name' => 'Test event',
426                    'description' => 'Hello',
427                    'format' => 1,
428                    'categoryid' => 0,
429                    'courseid' => 1,
430                    'groupid' => 1,
431                    'userid' => 1,
432                    'repeatid' => 0,
433                    'modulename' => 'Test module',
434                    'instance' => 1,
435                    'eventtype' => 'Due',
436                    'type' => CALENDAR_EVENT_TYPE_ACTION,
437                    'timestart' => 123456789,
438                    'timeduration' => 123456789,
439                    'timemodified' => 123456789,
440                    'timesort' => 123456789,
441                    'visible' => true,
442                    'subscriptionid' => 1,
443                    'location' => 'Test location',
444                ],
445                'actioncallbackapplier' => function(event_interface $event) {
446                    $event->testattribute = 'Hello';
447                    return $event;
448                },
449                'visibilitycallbackapplier' => function(event_interface $event) {
450                    return true;
451                },
452                'bailoutcheck' => function() {
453                    return true;
454                },
455                null,
456                null
457            ]
458        ];
459    }
460
461    /**
462     * Helper function to create calendar events using the old code.
463     *
464     * @param array $properties A list of calendar event properties to set
465     * @return calendar_event
466     */
467    protected function create_event($properties = []) {
468        $record = new \stdClass();
469        $record->name = 'event name';
470        $record->eventtype = 'site';
471        $record->timestart = time();
472        $record->timeduration = 0;
473        $record->timesort = 0;
474        $record->type = 1;
475        $record->courseid = 0;
476        $record->categoryid = 0;
477
478        foreach ($properties as $name => $value) {
479            $record->$name = $value;
480        }
481
482        $event = new calendar_event($record);
483        return $event->create($record, false);
484    }
485}
486