PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/favourites/tests/component_favourite_service_test.php

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