Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 2018 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 user_favourite_service within the service layer of favourites. 30 * 31 * @copyright 2018 Jake Dallimore <jrhdallimore@gmail.com> 32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 33 */ 34 class user_favourite_service_testcase extends advanced_testcase { 35 36 public function setUp(): void { 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 for single value key pair vs multiple. 89 $multipleconditions = []; 90 foreach ($criteria as $key => $value) { 91 if (is_array($value)) { 92 $multipleconditions[$key] = $value; 93 unset($criteria[$key]); 94 } 95 } 96 97 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 98 foreach ($mockstore as $index => $mockrow) { 99 $mockrowarr = (array)$mockrow; 100 if (array_diff_assoc($criteria, $mockrowarr) == []) { 101 $found = true; 102 foreach ($multipleconditions as $key => $value) { 103 if (!in_array($mockrowarr[$key], $value)) { 104 $found = false; 105 break; 106 } 107 } 108 if ($found) { 109 $returns[$index] = $mockrow; 110 } 111 } 112 } 113 // Return a subset of the records, according to the paging options, if set. 114 if ($limitnum != 0) { 115 return array_slice($returns, $limitfrom, $limitnum); 116 } 117 // Otherwise, just return the full set. 118 return $returns; 119 }) 120 ); 121 $mockrepo->expects($this->any()) 122 ->method('find_favourite') 123 ->will($this->returnCallback(function(int $userid, string $comp, string $type, int $id, int $ctxid) use (&$mockstore) { 124 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 125 $crit = ['userid' => $userid, 'component' => $comp, 'itemtype' => $type, 'itemid' => $id, 'contextid' => $ctxid]; 126 foreach ($mockstore as $fakerow) { 127 $fakerowarr = (array)$fakerow; 128 if (array_diff_assoc($crit, $fakerowarr) == []) { 129 return $fakerow; 130 } 131 } 132 throw new \dml_missing_record_exception("Item not found"); 133 }) 134 ); 135 $mockrepo->expects($this->any()) 136 ->method('find') 137 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 138 return $mockstore[$id]; 139 }) 140 ); 141 $mockrepo->expects($this->any()) 142 ->method('exists') 143 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 144 return array_key_exists($id, $mockstore); 145 }) 146 ); 147 $mockrepo->expects($this->any()) 148 ->method('count_by') 149 ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { 150 $count = 0; 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 $count++; 156 } 157 } 158 return $count; 159 }) 160 ); 161 $mockrepo->expects($this->any()) 162 ->method('delete') 163 ->will($this->returnCallback(function(int $id) use (&$mockstore) { 164 foreach ($mockstore as $mockrow) { 165 if ($mockrow->id == $id) { 166 unset($mockstore[$id]); 167 } 168 } 169 }) 170 ); 171 $mockrepo->expects($this->any()) 172 ->method('exists_by') 173 ->will($this->returnCallback(function(array $criteria) use (&$mockstore) { 174 // Check the mockstore for all objects with properties matching the key => val pairs in $criteria. 175 foreach ($mockstore as $index => $mockrow) { 176 $mockrowarr = (array)$mockrow; 177 if (array_diff_assoc($criteria, $mockrowarr) == []) { 178 return true; 179 } 180 } 181 return false; 182 }) 183 ); 184 return $mockrepo; 185 } 186 187 /** 188 * Test getting a user_favourite_service from the static locator. 189 */ 190 public function test_get_service_for_user_context() { 191 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 192 $userservice = \core_favourites\service_factory::get_service_for_user_context($user1context); 193 $this->assertInstanceOf(\core_favourites\local\service\user_favourite_service::class, $userservice); 194 } 195 196 /** 197 * Test confirming an item can be favourited only once. 198 */ 199 public function test_create_favourite_basic() { 200 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 201 202 // Get a user_favourite_service for a user. 203 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 204 $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 205 206 // Favourite a course. 207 $favourite1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 208 $this->assertObjectHasAttribute('id', $favourite1); 209 210 // Try to favourite the same course again. 211 $this->expectException('moodle_exception'); 212 $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 213 } 214 215 /** 216 * Test confirming that an exception is thrown if trying to favourite an item for a non-existent component. 217 */ 218 public function test_create_favourite_nonexistent_component() { 219 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 220 221 // Get a user_favourite_service for the user. 222 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 223 $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 224 225 // Try to favourite something in a non-existent component. 226 $this->expectException('moodle_exception'); 227 $user1service->create_favourite('core_cccourse', 'my_area', $course1context->instanceid, $course1context); 228 } 229 230 /** 231 * Test fetching favourites for single user, by area. 232 */ 233 public function test_find_favourites_by_type_single_user() { 234 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 235 236 // Get a user_favourite_service for the user. 237 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 238 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 239 240 // Favourite 2 courses, in separate areas. 241 $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 242 $fav2 = $service->create_favourite('core_course', 'anothertype', $course2context->instanceid, $course2context); 243 244 // Verify we can get favourites by area. 245 $favourites = $service->find_favourites_by_type('core_course', 'course'); 246 $this->assertIsArray($favourites); 247 $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area. 248 $this->assertEquals($fav1->id, $favourites[$fav1->id]->id); 249 250 $favourites = $service->find_favourites_by_type('core_course', 'anothertype'); 251 $this->assertIsArray($favourites); 252 $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area. 253 $this->assertEquals($fav2->id, $favourites[$fav2->id]->id); 254 } 255 256 /** 257 * Test fetching favourites for single user, by area. 258 */ 259 public function test_find_all_favourites() { 260 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 261 262 // Get a user_favourite_service for the user. 263 $repo = $this->get_mock_repository([]); // Mock repository, using the array as a mock DB. 264 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 265 266 // Favourite 2 courses, in separate areas. 267 $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 268 $fav2 = $service->create_favourite('core_course', 'anothertype', $course2context->instanceid, $course2context); 269 $fav3 = $service->create_favourite('core_course', 'yetanothertype', $course2context->instanceid, $course2context); 270 271 // Verify we can get favourites by area. 272 $favourites = $service->find_all_favourites('core_course', ['course']); 273 $this->assertIsArray($favourites); 274 $this->assertCount(1, $favourites); // We only get favourites for the 'core_course/course' area. 275 $this->assertEquals($fav1->id, $favourites[$fav1->id]->id); 276 277 $favourites = $service->find_all_favourites('core_course', ['course', 'anothertype']); 278 $this->assertIsArray($favourites); 279 // We only get favourites for the 'core_course/course' and 'core_course/anothertype area. 280 $this->assertCount(2, $favourites); 281 $this->assertEquals($fav1->id, $favourites[$fav1->id]->id); 282 $this->assertEquals($fav2->id, $favourites[$fav2->id]->id); 283 284 $favourites = $service->find_all_favourites('core_course'); 285 $this->assertIsArray($favourites); 286 $this->assertCount(3, $favourites); // We only get favourites for the 'core_cours' area. 287 $this->assertEquals($fav2->id, $favourites[$fav2->id]->id); 288 $this->assertEquals($fav1->id, $favourites[$fav1->id]->id); 289 $this->assertEquals($fav3->id, $favourites[$fav3->id]->id); 290 } 291 292 /** 293 * Make sure the find_favourites_by_type() method only returns favourites for the scoped user. 294 */ 295 public function test_find_favourites_by_type_multiple_users() { 296 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 297 298 // Get a user_favourite_service for 2 users. 299 $repo = $this->get_mock_repository([]); 300 $user1service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 301 $user2service = new \core_favourites\local\service\user_favourite_service($user2context, $repo); 302 303 // Now, as each user, favourite the same course. 304 $fav1 = $user1service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 305 $fav2 = $user2service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 306 307 // Verify find_favourites_by_type only returns results for the user to which the service is scoped. 308 $user1favourites = $user1service->find_favourites_by_type('core_course', 'course'); 309 $this->assertIsArray($user1favourites); 310 $this->assertCount(1, $user1favourites); // We only get favourites for the 'core_course/course' area for $user1. 311 $this->assertEquals($fav1->id, $user1favourites[$fav1->id]->id); 312 313 $user2favourites = $user2service->find_favourites_by_type('core_course', 'course'); 314 $this->assertIsArray($user2favourites); 315 $this->assertCount(1, $user2favourites); // We only get favourites for the 'core_course/course' area for $user2. 316 $this->assertEquals($fav2->id, $user2favourites[$fav2->id]->id); 317 } 318 319 /** 320 * Test confirming that an exception is thrown if trying to get favourites for a non-existent component. 321 */ 322 public function test_find_favourites_by_type_nonexistent_component() { 323 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 324 325 // Get a user_favourite_service for the user. 326 $repo = $this->get_mock_repository([]); 327 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 328 329 // Verify we get an exception if we try to search for favourites in an invalid component. 330 $this->expectException('moodle_exception'); 331 $service->find_favourites_by_type('cccore_notreal', 'something'); 332 } 333 334 /** 335 * Test confirming the pagination support for the find_favourites_by_type() method. 336 */ 337 public function test_find_favourites_by_type_pagination() { 338 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 339 340 // Get a user_favourite_service for the user. 341 $repo = $this->get_mock_repository([]); 342 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 343 344 // Favourite 10 arbitrary items. 345 foreach (range(1, 10) as $i) { 346 $service->create_favourite('core_course', 'course', $i, $course1context); 347 } 348 349 // Verify we have 10 favourites. 350 $this->assertCount(10, $service->find_favourites_by_type('core_course', 'course')); 351 352 // Verify we get back 5 favourites for page 1. 353 $favourites = $service->find_favourites_by_type('core_course', 'course', 0, 5); 354 $this->assertCount(5, $favourites); 355 356 // Verify we get back 5 favourites for page 2. 357 $favourites = $service->find_favourites_by_type('core_course', 'course', 5, 5); 358 $this->assertCount(5, $favourites); 359 360 // Verify we get back an empty array if querying page 3. 361 $favourites = $service->find_favourites_by_type('core_course', 'course', 10, 5); 362 $this->assertCount(0, $favourites); 363 } 364 365 /** 366 * Test confirming the basic deletion behaviour. 367 */ 368 public function test_delete_favourite_basic() { 369 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 370 371 // Get a user_favourite_service for the user. 372 $repo = $this->get_mock_repository([]); 373 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 374 375 // Favourite a course. 376 $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 377 $this->assertTrue($repo->exists($fav1->id)); 378 379 // Delete the favourite. 380 $service->delete_favourite('core_course', 'course', $course1context->instanceid, $course1context); 381 382 // Verify the favourite doesn't exist. 383 $this->assertFalse($repo->exists($fav1->id)); 384 385 // Try to delete a favourite which we know doesn't exist. 386 $this->expectException(\moodle_exception::class); 387 $service->delete_favourite('core_course', 'course', $course1context->instanceid, $course1context); 388 } 389 390 /** 391 * Test confirming the behaviour of the favourite_exists() method. 392 */ 393 public function test_favourite_exists() { 394 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 395 396 // Get a user_favourite_service for the user. 397 $repo = $this->get_mock_repository([]); 398 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 399 400 // Favourite a course. 401 $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 402 403 // Verify we can check existence of the favourite. 404 $this->assertTrue( 405 $service->favourite_exists( 406 'core_course', 407 'course', 408 $course1context->instanceid, 409 $course1context 410 ) 411 ); 412 413 // And one that we know doesn't exist. 414 $this->assertFalse( 415 $service->favourite_exists( 416 'core_course', 417 'someothertype', 418 $course1context->instanceid, 419 $course1context 420 ) 421 ); 422 } 423 424 /** 425 * Test confirming the behaviour of the get_favourite() method. 426 */ 427 public function test_get_favourite() { 428 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 429 430 // Get a user_favourite_service for the user. 431 $repo = $this->get_mock_repository([]); 432 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 433 434 // Favourite a course. 435 $fav1 = $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 436 437 $result = $service->get_favourite( 438 'core_course', 439 'course', 440 $course1context->instanceid, 441 $course1context 442 ); 443 // Verify we can get the favourite. 444 $this->assertEquals($fav1->id, $result->id); 445 446 // And one that we know doesn't exist. 447 $this->assertNull( 448 $service->get_favourite( 449 'core_course', 450 'someothertype', 451 $course1context->instanceid, 452 $course1context 453 ) 454 ); 455 } 456 457 /** 458 * Test confirming the behaviour of the count_favourites_by_type() method. 459 */ 460 public function test_count_favourites_by_type() { 461 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 462 463 // Get a user_favourite_service for the user. 464 $repo = $this->get_mock_repository([]); 465 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 466 467 $this->assertEquals(0, $service->count_favourites_by_type('core_course', 'course', $course1context)); 468 // Favourite a course. 469 $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 470 471 $this->assertEquals(1, $service->count_favourites_by_type('core_course', 'course', $course1context)); 472 473 // Favourite another course. 474 $service->create_favourite('core_course', 'course', $course2context->instanceid, $course1context); 475 476 $this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context)); 477 478 // Favourite a course in another context. 479 $service->create_favourite('core_course', 'course', $course2context->instanceid, $course2context); 480 481 // Doesn't affect original context. 482 $this->assertEquals(2, $service->count_favourites_by_type('core_course', 'course', $course1context)); 483 // Gets counted if we include all contexts. 484 $this->assertEquals(3, $service->count_favourites_by_type('core_course', 'course')); 485 } 486 487 /** 488 * Verify that the join sql generated by get_join_sql_by_type is valid and can be used to include favourite information. 489 */ 490 public function test_get_join_sql_by_type() { 491 global $DB; 492 list($user1context, $user2context, $course1context, $course2context) = $this->setup_users_and_courses(); 493 494 // Get a user_favourite_service for the user. 495 // We need to use a real (DB) repository, as we want to run the SQL. 496 $repo = new \core_favourites\local\repository\favourite_repository(); 497 $service = new \core_favourites\local\service\user_favourite_service($user1context, $repo); 498 499 // Favourite the first course only. 500 $service->create_favourite('core_course', 'course', $course1context->instanceid, $course1context); 501 502 // Generate the join snippet. 503 list($favsql, $favparams) = $service->get_join_sql_by_type('core_course', 'course', 'favalias', 'c.id'); 504 505 // Join against a simple select, including the 2 courses only. 506 $params = ['courseid1' => $course1context->instanceid, 'courseid2' => $course2context->instanceid]; 507 $params = $params + $favparams; 508 $records = $DB->get_records_sql("SELECT c.id, favalias.component 509 FROM {course} c $favsql 510 WHERE c.id = :courseid1 OR c.id = :courseid2", $params); 511 512 // Verify the favourite information is returned, but only for the favourited course. 513 $this->assertCount(2, $records); 514 $this->assertEquals('core_course', $records[$course1context->instanceid]->component); 515 $this->assertEmpty($records[$course2context->instanceid]->component); 516 } 517 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body