See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 namespace core_favourites; 18 19 use core_favourites\local\entity\favourite; 20 21 /** 22 * Test class covering the component_favourite_service within the service layer of favourites. 23 * 24 * @package core_favourites 25 * @category test 26 * @copyright 2019 Jake Dallimore <jrhdallimore@gmail.com> 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 class component_favourite_service_test extends \advanced_testcase { 30 31 public function setUp(): void { 32 $this->resetAfterTest(); 33 } 34 35 // Basic setup stuff to be reused in most tests. 36 protected function setup_users_and_courses() { 37 $user1 = self::getDataGenerator()->create_user(); 38 $user1context = \context_user::instance($user1->id); 39 $user2 = self::getDataGenerator()->create_user(); 40 $user2context = \context_user::instance($user2->id); 41 $course1 = self::getDataGenerator()->create_course(); 42 $course2 = self::getDataGenerator()->create_course(); 43 $course1context = \context_course::instance($course1->id); 44 $course2context = \context_course::instance($course2->id); 45 return [$user1context, $user2context, $course1context, $course2context]; 46 } 47 48 /** 49 * Generates an in-memory repository for testing, using an array store for CRUD stuff. 50 * 51 * @param array $mockstore 52 * @return \PHPUnit\Framework\MockObject\MockObject 53 */ 54 protected function get_mock_repository(array $mockstore) { 55 // This mock will just store data in an array. 56 $mockrepo = $this->getMockBuilder(\core_favourites\local\repository\favourite_repository_interface::class) 57 ->onlyMethods([]) 58 ->getMock(); 59 $mockrepo->expects($this->any()) 60 ->method('add') 61 ->will($this->returnCallback(function(favourite $favourite) use (&$mockstore) { 62 // Mock implementation of repository->add(), where an array is used instead of the DB. 63 // Duplicates are confirmed via the unique key, and exceptions thrown just like a real repo. 64 $key = $favourite->userid . $favourite->component . $favourite->itemtype . $favourite->itemid 65 . $favourite->contextid; 66 67 // Check the objects for the unique key. 68 foreach ($mockstore as $item) { 69 if ($item->uniquekey == $key) { 70 throw new \moodle_exception('Favourite already exists'); 71 } 72 } 73 $index = count($mockstore); // Integer index. 74 $favourite->uniquekey = $key; // Simulate the unique key constraint. 75 $favourite->id = $index; 76 $mockstore[$index] = $favourite; 77 return $mockstore[$index]; 78 }) 79 ); 80 $mockrepo->expects($this->any()) 81 ->method('find_by') 82 ->will($this->returnCallback(function(array $criteria, int $limitfrom = 0, int $limitnum = 0) use (&$mockstore) { 83 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 84 foreach ($mockstore as $index => $mockrow) { 85 $mockrowarr = (array)$mockrow; 86 if (array_diff_assoc($criteria, $mockrowarr) == []) { 87 $returns[$index] = $mockrow; 88 } 89 } 90 // Return a subset of the records, according to the paging options, if set. 91 if ($limitnum != 0) { 92 return array_slice($returns, $limitfrom, $limitnum); 93 } 94 // Otherwise, just return the full set. 95 return $returns; 96 }) 97 ); 98 $mockrepo->expects($this->any()) 99 ->method('find_favourite') 100 ->will($this->returnCallback(function(int $userid, string $comp, string $type, int $id, int $ctxid) use (&$mockstore) { 101 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 102 $crit = ['userid' => $userid, 'component' => $comp, 'itemtype' => $type, 'itemid' => $id, 'contextid' => $ctxid]; 103 foreach ($mockstore as $fakerow) { 104 $fakerowarr = (array)$fakerow; 105 if (array_diff_assoc($crit, $fakerowarr) == []) { 106 return $fakerow; 107 } 108 } 109 throw new \dml_missing_record_exception("Item not found"); 110 }) 111 ); 112 $mockrepo->expects($this->any()) 113 ->method('find') 114 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 115 return $mockstore[$id]; 116 }) 117 ); 118 $mockrepo->expects($this->any()) 119 ->method('exists') 120 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 121 return array_key_exists($id, $mockstore); 122 }) 123 ); 124 $mockrepo->expects($this->any()) 125 ->method('count_by') 126 ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { 127 $count = 0; 128 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 129 foreach ($mockstore as $index => $mockrow) { 130 $mockrowarr = (array)$mockrow; 131 if (array_diff_assoc($criteria, $mockrowarr) == []) { 132 $count++; 133 } 134 } 135 return $count; 136 }) 137 ); 138 $mockrepo->expects($this->any()) 139 ->method('delete') 140 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 141 foreach ($mockstore as $mockrow) { 142 if ($mockrow->id == $id) { 143 unset($mockstore[$id]); 144 } 145 } 146 }) 147 ); 148 $mockrepo->expects($this->any()) 149 ->method('delete_by') 150 ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { 151 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 152 foreach ($mockstore as $index => $mockrow) { 153 $mockrowarr = (array)$mockrow; 154 if (array_diff_assoc($criteria, $mockrowarr) == []) { 155 unset($mockstore[$index]); 156 } 157 } 158 }) 159 ); 160 $mockrepo->expects($this->any()) 161 ->method('exists_by') 162 ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { 163 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 164 foreach ($mockstore as $index => $mockrow) { 165 $mockrowarr = (array)$mockrow; 166 echo "Here"; 167 if (array_diff_assoc($criteria, $mockrowarr) == []) { 168 return true; 169 } 170 } 171 return false; 172 }) 173 ); 174 return $mockrepo; 175 } 176 177 /** 178 * Test confirming the deletion of favourites by type and item, but with no optional context filter provided. 179 */ 180 public function test_delete_favourites_by_type_and_item() { 181 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 182 183 // Get a user_favourite_service for each user. 184 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 185 $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 186 $user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo); 187 188 // Favourite both courses for both users. 189 $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 190 $fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 191 $fav3 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); 192 $fav4 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); 193 $this->assertTrue($repo->exists($fav1->id)); 194 $this->assertTrue($repo->exists($fav2->id)); 195 $this->assertTrue($repo->exists($fav3->id)); 196 $this->assertTrue($repo->exists($fav4->id)); 197 198 // Favourite something else arbitrarily. 199 $fav5 = $user2service->create_favourite('core_user', 'course', $course2context->instanceid, $course2context); 200 $fav6 = $user2service->create_favourite('core_course', 'whatnow', $course2context->instanceid, $course2context); 201 202 // Get a component_favourite_service to perform the type based deletion. 203 $service = new \core_favourites\local\service\component_favourite_service('core_course', $repo); 204 205 // Delete all 'course' type favourites (for all users who have favourited course1). 206 $service->delete_favourites_by_type_and_item('course', $course1context->instanceid); 207 208 // Delete all 'course' type favourites (for all users who have favourited course2). 209 $service->delete_favourites_by_type_and_item('course', $course2context->instanceid); 210 211 // Verify the favourites don't exist. 212 $this->assertFalse($repo->exists($fav1->id)); 213 $this->assertFalse($repo->exists($fav2->id)); 214 $this->assertFalse($repo->exists($fav3->id)); 215 $this->assertFalse($repo->exists($fav4->id)); 216 217 // Verify favourites of other types or for other components are not affected. 218 $this->assertTrue($repo->exists($fav5->id)); 219 $this->assertTrue($repo->exists($fav6->id)); 220 221 // Try to delete favourites for a type which we know doesn't exist. Verify no exception. 222 $this->assertNull($service->delete_favourites_by_type_and_item('course', $course1context->instanceid)); 223 } 224 225 /** 226 * Test confirming the deletion of favourites by type and item and with the optional context filter provided. 227 */ 228 public function test_delete_favourites_by_type_and_item_with_context() { 229 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 230 231 // Get a user_favourite_service for each user. 232 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 233 $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 234 $user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo); 235 236 // Favourite both courses for both users. 237 $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 238 $fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 239 $fav3 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); 240 $fav4 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); 241 $this->assertTrue($repo->exists($fav1->id)); 242 $this->assertTrue($repo->exists($fav2->id)); 243 $this->assertTrue($repo->exists($fav3->id)); 244 $this->assertTrue($repo->exists($fav4->id)); 245 246 // Favourite something else arbitrarily. 247 $fav5 = $user2service->create_favourite('core_user', 'course', $course1context->instanceid, $course1context); 248 $fav6 = $user2service->create_favourite('core_course', 'whatnow', $course1context->instanceid, $course1context); 249 250 // Favourite the courses again, but this time in another context. 251 $fav7 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, \context_system::instance()); 252 $fav8 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, \context_system::instance()); 253 $fav9 = $user1service->create_favourite('core_course', 'course', $course2context->instanceid, \context_system::instance()); 254 $fav10 = $user2service->create_favourite('core_course', 'course', $course2context->instanceid, \context_system::instance()); 255 256 // Get a component_favourite_service to perform the type based deletion. 257 $service = new \core_favourites\local\service\component_favourite_service('core_course', $repo); 258 259 // Delete all 'course' type favourites (for all users at ONLY the course 1 context). 260 $service->delete_favourites_by_type_and_item('course', $course1context->instanceid, $course1context); 261 262 // Verify the favourites for course 1 context don't exist. 263 $this->assertFalse($repo->exists($fav1->id)); 264 $this->assertFalse($repo->exists($fav2->id)); 265 266 // Verify the favourites for the same component and type, but NOT for the same contextid and unaffected. 267 $this->assertTrue($repo->exists($fav3->id)); 268 $this->assertTrue($repo->exists($fav4->id)); 269 270 // Verify favourites of other types or for other components are not affected. 271 $this->assertTrue($repo->exists($fav5->id)); 272 $this->assertTrue($repo->exists($fav6->id)); 273 274 // Verify the course favourite at the system context are unaffected. 275 $this->assertTrue($repo->exists($fav7->id)); 276 $this->assertTrue($repo->exists($fav8->id)); 277 $this->assertTrue($repo->exists($fav9->id)); 278 $this->assertTrue($repo->exists($fav10->id)); 279 280 // Try to delete favourites for a type which we know doesn't exist. Verify no exception. 281 $this->assertNull($service->delete_favourites_by_type_and_item('course', $course1context->instanceid, $course1context)); 282 } 283 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body