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