MDL-63658 core_favourites: adding paging support to the service layer
[moodle.git] / favourites / tests / repository_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  * Testing the repository objects within core_favourites.
19  *
20  * @package    core_favourites
21  * @category   test
22  * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 use \core_favourites\local\repository\favourites_repository;
30 /**
31  * Test class covering the favourites_repository.
32  *
33  * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class favourites_repository_testcase extends advanced_testcase {
38     public function setUp() {
39         $this->resetAfterTest();
40     }
42     // Basic setup stuff to be reused in most tests.
43     protected function setup_users_and_courses() {
44         $user1 = self::getDataGenerator()->create_user();
45         $user1context = \context_user::instance($user1->id);
46         $user2 = self::getDataGenerator()->create_user();
47         $user2context = \context_user::instance($user2->id);
48         $course1 = self::getDataGenerator()->create_course();
49         $course2 = self::getDataGenerator()->create_course();
50         $course1context = context_course::instance($course1->id);
51         $course2context = context_course::instance($course2->id);
52         return [$user1context, $user2context, $course1context, $course2context];
53     }
55     /**
56      * Verify the basic create operation can create records, and is validated.
57      */
58     public function test_add() {
59         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
61         // Create a favourites repository and favourite a course.
62         $favouritesrepo = new favourites_repository($user1context);
64         $favcourse = (object)[
65             'userid' => $user1context->instanceid,
66             'component' => 'core_course',
67             'itemtype' => 'course',
68             'itemid' => $course1context->instanceid,
69             'contextid' => $course1context->id,
70         ];
71         $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this.
72         $favourite = $favouritesrepo->add($favcourse);
74         // Verify we get the record back.
75         $this->assertInstanceOf(\stdClass::class, $favourite);
76         $this->assertEquals('core_course', $favourite->component);
77         $this->assertEquals('course', $favourite->itemtype);
79         // Verify the returned object has additional properties, created as part of the add.
80         $this->assertObjectHasAttribute('ordering', $favourite);
81         $this->assertObjectHasAttribute('timecreated', $favourite);
82         $this->assertGreaterThanOrEqual($timenow, $favourite->timecreated);
84         // Try to save the same record again and confirm the store throws an exception.
85         $this->expectException('dml_write_exception');
86         $favouritesrepo->add($favcourse);
87     }
89     /**
90      * Tests that malformed favourites cannot be saved.
91      */
92     public function test_add_malformed_favourite() {
93         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
95         // Create a favourites repository and favourite a course.
96         $favouritesrepo = new favourites_repository($user1context);
98         $favcourse = (object)[
99             'userid' => $user1context->instanceid,
100             'component' => 'core_course',
101             'itemtype' => 'course',
102             'itemid' => $course1context->instanceid,
103             'contextid' => $course1context->id,
104             'anotherfield' => 'cat'
105         ];
107         $this->expectException('moodle_exception');
108         $favouritesrepo->add($favcourse);
109     }
111     /**
112      * Tests that incomplete favourites cannot be saved.
113      */
114     public function test_add_incomplete_favourite() {
115         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
117         // Create a favourites repository and favourite a course.
118         $favouritesrepo = new favourites_repository($user1context);
120         $favcourse = (object)[
121             'component' => 'core_course',
122             'itemtype' => 'course',
123             'itemid' => $course1context->instanceid
124         ];
126         $this->expectException('moodle_exception');
127         $favouritesrepo->add($favcourse);
128     }
130     public function test_add_all_basic() {
131         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
133         // Create a favourites repository and favourite several courses.
134         $favouritesrepo = new favourites_repository($user1context);
135         $favcourses = [];
137         $favcourses[] = (object)[
138             'userid' => $user1context->instanceid,
139             'component' => 'core_course',
140             'itemtype' => 'course',
141             'itemid' => $course1context->instanceid,
142             'contextid' => $course1context->id,
143         ];
144         $favcourses[] = (object)[
145             'userid' => $user1context->instanceid,
146             'component' => 'core_course',
147             'itemtype' => 'course',
148             'itemid' => $course2context->instanceid,
149             'contextid' => $course2context->id,
150         ];
151         $timenow = time(); // Reference only, to check that the created item has a time equal to or greater than this.
152         $favourites = $favouritesrepo->add_all($favcourses);
154         $this->assertInternalType('array', $favourites);
155         $this->assertCount(2, $favourites);
156         foreach ($favourites as $favourite) {
157             // Verify we get the record back.
158             $this->assertEquals('core_course', $favourite->component);
159             $this->assertEquals('course', $favourite->itemtype);
161             // Verify the returned object has additional properties, created as part of the add.
162             $this->assertObjectHasAttribute('ordering', $favourite);
163             $this->assertObjectHasAttribute('timecreated', $favourite);
164             $this->assertGreaterThanOrEqual($timenow, $favourite->timecreated);
165         }
167         // Try to save the same record again and confirm the store throws an exception.
168         $this->expectException('dml_write_exception');
169         $favouritesrepo->add_all($favcourses);
170     }
172     /**
173      * Tests reading from the repository by instance id.
174      */
175     public function test_find() {
176         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
178         // Create a favourites repository and favourite a course.
179         $favouritesrepo = new favourites_repository($user1context);
180         $favourite = (object) [
181             'userid' => $user1context->instanceid,
182             'component' => 'core_course',
183             'itemtype' => 'course',
184             'itemid' => $course1context->instanceid,
185             'contextid' => $course1context->id
186         ];
187         $favourite = $favouritesrepo->add($favourite);
189         // Now, from the repo, get the single favourite we just created, by id.
190         $userfavourite = $favouritesrepo->find($favourite->id);
191         $this->assertInstanceOf(\stdClass::class, $userfavourite);
192         $this->assertObjectHasAttribute('timecreated', $userfavourite);
194         // Try to get a favourite we know doesn't exist.
195         // We expect an exception in this case.
196         $this->expectException(dml_exception::class);
197         $favouritesrepo->find(1);
198     }
200     /**
201      * Test verifying that find_all() returns all favourites, or an empty array.
202      */
203     public function test_find_all() {
204         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
206         $favouritesrepo = new favourites_repository($user1context);
208         // Verify that for an empty repository, find_all returns an empty array.
209         $this->assertEquals([], $favouritesrepo->find_all());
211         // Save a favourite for 2 courses, in different areas.
212         $favourite = (object) [
213             'userid' => $user1context->instanceid,
214             'component' => 'core_course',
215             'itemtype' => 'course',
216             'itemid' => $course1context->instanceid,
217             'contextid' => $course1context->id
218         ];
219         $favourite2 = (object) [
220             'userid' => $user1context->instanceid,
221             'component' => 'core_course',
222             'itemtype' => 'anothertype',
223             'itemid' => $course2context->instanceid,
224             'contextid' => $course2context->id
225         ];
226         $favouritesrepo->add($favourite);
227         $favouritesrepo->add($favourite2);
229         // Verify that find_all returns both of our favourites.
230         $favourites = $favouritesrepo->find_all();
231         $this->assertCount(2, $favourites);
232         foreach ($favourites as $fav) {
233             $this->assertObjectHasAttribute('id', $fav);
234             $this->assertObjectHasAttribute('timecreated', $fav);
235         }
236     }
238     /**
239      * Testing the pagination of the find_all method.
240      */
241     public function test_find_all_pagination() {
242         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
244         $favouritesrepo = new favourites_repository($user1context);
246         // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
247         $this->assertEquals([], $favouritesrepo->find_all(0, 0));
248         $this->assertEquals([], $favouritesrepo->find_all(0, 10));
249         $this->assertEquals([], $favouritesrepo->find_all(1, 0));
250         $this->assertEquals([], $favouritesrepo->find_all(1, 10));
252         // Save 10 arbitrary favourites to the repo.
253         foreach (range(1, 10) as $i) {
254             $favourite = (object) [
255                 'userid' => $user1context->instanceid,
256                 'component' => 'core_course',
257                 'itemtype' => 'course',
258                 'itemid' => $i,
259                 'contextid' => $course1context->id
260             ];
261             $favouritesrepo->add($favourite);
262         }
264         // Verify we have 10 favourites.
265         $this->assertEquals(10, $favouritesrepo->count());
267         // Verify we can fetch the first page of 5 records.
268         $favourites = $favouritesrepo->find_all(0, 5);
269         $this->assertCount(5, $favourites);
271         // Verify we can fetch the second page.
272         $favourites = $favouritesrepo->find_all(5, 5);
273         $this->assertCount(5, $favourites);
275         // Verify the third page request ends with an empty array.
276         $favourites = $favouritesrepo->find_all(10, 5);
277         $this->assertCount(0, $favourites);
278     }
280     /**
281      * Test retrieval of a user's favourites for a given criteria, in this case, area.
282      */
283     public function test_find_by() {
284         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
286         // Create a favourites repository and favourite a course.
287         $favouritesrepo = new favourites_repository($user1context);
288         $favourite = (object) [
289             'userid' => $user1context->instanceid,
290             'component' => 'core_course',
291             'itemtype' => 'course',
292             'itemid' => $course1context->instanceid,
293             'contextid' => $course1context->id
294         ];
295         $favouritesrepo->add($favourite);
297         // From the repo, get the list of favourites for the 'core_course/course' area.
298         $userfavourites = $favouritesrepo->find_by(['component' => 'core_course', 'itemtype' => 'course']);
299         $this->assertInternalType('array', $userfavourites);
300         $this->assertCount(1, $userfavourites);
302         // Try to get a list of favourites for a non-existent area.
303         $userfavourites = $favouritesrepo->find_by(['component' => 'core_cannibalism', 'itemtype' => 'course']);
304         $this->assertInternalType('array', $userfavourites);
305         $this->assertCount(0, $userfavourites);
306     }
308     /**
309      * Testing the pagination of the find_by method.
310      */
311     public function test_find_by_pagination() {
312         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
314         $favouritesrepo = new favourites_repository($user1context);
316         // Verify that for an empty repository, find_all with any combination of page options returns an empty array.
317         $this->assertEquals([], $favouritesrepo->find_by([], 0, 0));
318         $this->assertEquals([], $favouritesrepo->find_by([], 0, 10));
319         $this->assertEquals([], $favouritesrepo->find_by([], 1, 0));
320         $this->assertEquals([], $favouritesrepo->find_by([], 1, 10));
322         // Save 10 arbitrary favourites to the repo.
323         foreach (range(1, 10) as $i) {
324             $favourite = (object) [
325                 'userid' => $user1context->instanceid,
326                 'component' => 'core_course',
327                 'itemtype' => 'course',
328                 'itemid' => $i,
329                 'contextid' => $course1context->id
330             ];
331             $favouritesrepo->add($favourite);
332         }
334         // Verify we have 10 favourites.
335         $this->assertEquals(10, $favouritesrepo->count());
337         // Verify a request for a page, when no criteria match, results in an empty array.
338         $favourites = $favouritesrepo->find_by(['component' => 'core_message'], 0, 5);
339         $this->assertCount(0, $favourites);
341         // Verify we can fetch a the first page of 5 records.
342         $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 0, 5);
343         $this->assertCount(5, $favourites);
345         // Verify we can fetch the second page.
346         $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 5, 5);
347         $this->assertCount(5, $favourites);
349         // Verify the third page request ends with an empty array.
350         $favourites = $favouritesrepo->find_by(['component' => 'core_course'], 10, 5);
351         $this->assertCount(0, $favourites);
352     }
354     /**
355      * Test the count_by() method.
356      */
357     public function test_count_by() {
358         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
360         // Create a favourites repository and add 2 favourites in different areas.
361         $favouritesrepo = new favourites_repository($user1context);
362         $favourite = (object) [
363             'userid' => $user1context->instanceid,
364             'component' => 'core_course',
365             'itemtype' => 'course',
366             'itemid' => $course1context->instanceid,
367             'contextid' => $course1context->id
368         ];
369         $favourite2 = (object) [
370             'userid' => $user1context->instanceid,
371             'component' => 'core_course',
372             'itemtype' => 'anothertype',
373             'itemid' => $course2context->instanceid,
374             'contextid' => $course2context->id
375         ];
376         $favouritesrepo->add($favourite);
377         $favouritesrepo->add($favourite2);
379         // Verify counts can be restricted by criteria.
380         $this->assertEquals(1, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course',
381                 'itemtype' => 'course']));
382         $this->assertEquals(1, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course',
383             'itemtype' => 'anothertype']));
384         $this->assertEquals(0, $favouritesrepo->count_by(['userid' => $user1context->instanceid, 'component' => 'core_course',
385             'itemtype' => 'nonexistenttype']));
386     }
388     public function test_exists() {
389         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
391         // Create a favourites repository and favourite a course.
392         $favouritesrepo = new favourites_repository($user1context);
393         $favourite = (object) [
394             'userid' => $user1context->instanceid,
395             'component' => 'core_course',
396             'itemtype' => 'course',
397             'itemid' => $course1context->instanceid,
398             'contextid' => $course1context->id
399         ];
400         $createdfavourite = $favouritesrepo->add($favourite);
402         // Verify the existence of the favourite in the repo.
403         $this->assertTrue($favouritesrepo->exists($createdfavourite->id));
405         // Verify exists returns false for non-existent favourite.
406         $this->assertFalse($favouritesrepo->exists(1));
407     }
409     public function test_exists_by_area() {
410         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
412         // Create a favourites repository and favourite two courses, in different areas.
413         $favouritesrepo = new favourites_repository($user1context);
414         $favourite = (object) [
415             'userid' => $user1context->instanceid,
416             'component' => 'core_course',
417             'itemtype' => 'course',
418             'itemid' => $course1context->instanceid,
419             'contextid' => $course1context->id
420         ];
421         $favourite2 = (object) [
422             'userid' => $user1context->instanceid,
423             'component' => 'core_course',
424             'itemtype' => 'anothertype',
425             'itemid' => $course2context->instanceid,
426             'contextid' => $course2context->id
427         ];
428         $favourite1 = $favouritesrepo->add($favourite);
429         $favourite2 = $favouritesrepo->add($favourite2);
431         // Verify the existence of the favourites.
432         $this->assertTrue($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'course', $favourite1->itemid,
433             $favourite1->contextid));
434         $this->assertTrue($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype',
435             $favourite2->itemid, $favourite2->contextid));
437         // Verify that we can't find a favourite from one area, in another.
438         $this->assertFalse($favouritesrepo->exists_by_area($user1context->instanceid, 'core_course', 'anothertype',
439             $favourite1->itemid, $favourite1->contextid));
440     }
442     /**
443      * Test the update() method, by simulating a user changing the ordering of a favourite.
444      */
445     public function test_update() {
446         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
448         // Create a favourites repository and favourite a course.
449         $favouritesrepo = new favourites_repository($user1context);
450         $favourite = (object) [
451             'userid' => $user1context->instanceid,
452             'component' => 'core_course',
453             'itemtype' => 'course',
454             'itemid' => $course1context->instanceid,
455             'contextid' => $course1context->id
456         ];
457         $favourite1 = $favouritesrepo->add($favourite);
459         // Verify we can update the ordering for 2 favourites.
460         $favourite1->ordering = 1;
461         $favourite1 = $favouritesrepo->update($favourite1);
462         $this->assertInstanceOf(stdClass::class, $favourite1);
463         $this->assertAttributeEquals('1', 'ordering', $favourite1);
464     }
466     public function test_delete() {
467         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
469         // Create a favourites repository and favourite a course.
470         $favouritesrepo = new favourites_repository($user1context);
471         $favourite = (object) [
472             'userid' => $user1context->instanceid,
473             'component' => 'core_course',
474             'itemtype' => 'course',
475             'itemid' => $course1context->instanceid,
476             'contextid' => $course1context->id
477         ];
478         $favourite = $favouritesrepo->add($favourite);
480         // Verify the existence of the favourite in the repo.
481         $this->assertTrue($favouritesrepo->exists($favourite->id));
483         // Now, delete the favourite and confirm it's not retrievable.
484         $favouritesrepo->delete($favourite->id);
485         $this->assertFalse($favouritesrepo->exists($favourite->id));
486     }
488     public function test_delete_by_area() {
489         list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses();
491         // Create a favourites repository and favourite two courses, in different areas.
492         $favouritesrepo = new favourites_repository($user1context);
493         $favourite = (object) [
494             'userid' => $user1context->instanceid,
495             'component' => 'core_course',
496             'itemtype' => 'course',
497             'itemid' => $course1context->instanceid,
498             'contextid' => $course1context->id
499         ];
500         $favourite2 = (object) [
501             'userid' => $user1context->instanceid,
502             'component' => 'core_course',
503             'itemtype' => 'anothertype',
504             'itemid' => $course2context->instanceid,
505             'contextid' => $course2context->id
506         ];
507         $favourite1 = $favouritesrepo->add($favourite);
508         $favourite2 = $favouritesrepo->add($favourite2);
510         // Verify we have 2 items in the repo.
511         $this->assertEquals(2, $favouritesrepo->count());
513         // Try to delete by a non-existent area, and confirm it doesn't remove anything.
514         $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'donaldduck');
515         $this->assertEquals(2, $favouritesrepo->count());
517         // Try to delete by a non-existent area, and confirm it doesn't remove anything.
518         $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'cat');
519         $this->assertEquals(2, $favouritesrepo->count());
521         // Delete by area, and confirm we have one record left, from the 'core_course/anothertype' area.
522         $favouritesrepo->delete_by_area($user1context->instanceid, 'core_course', 'course');
523         $this->assertEquals(1, $favouritesrepo->count());
524         $this->assertFalse($favouritesrepo->exists($favourite1->id));
525         $this->assertTrue($favouritesrepo->exists($favourite2->id));
526     }