GroupControllerTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <?php
  2. namespace Tests\Api\v1\Controllers;
  3. use App\Api\v1\Controllers\GroupController;
  4. use App\Api\v1\Resources\GroupResource;
  5. use App\Listeners\DissociateTwofaccountFromGroup;
  6. use App\Listeners\ResetUsersPreference;
  7. use App\Models\Group;
  8. use App\Models\TwoFAccount;
  9. use App\Models\User;
  10. use App\Policies\GroupPolicy;
  11. use PHPUnit\Framework\Attributes\CoversClass;
  12. use PHPUnit\Framework\Attributes\Test;
  13. use Tests\FeatureTestCase;
  14. #[CoversClass(GroupController::class)]
  15. #[CoversClass(GroupResource::class)]
  16. #[CoversClass(ResetUsersPreference::class)]
  17. #[CoversClass(GroupPolicy::class)]
  18. #[CoversClass(Group::class)]
  19. #[CoversClass(DissociateTwofaccountFromGroup::class)]
  20. class GroupControllerTest extends FeatureTestCase
  21. {
  22. /**
  23. * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
  24. */
  25. protected $user;
  26. protected $anotherUser;
  27. /**
  28. * @var App\Models\Group
  29. */
  30. protected $userGroupA;
  31. protected $userGroupB;
  32. protected $anotherUserGroupA;
  33. protected $anotherUserGroupB;
  34. /**
  35. * @var App\Models\TwoFAccount
  36. */
  37. protected $twofaccountA;
  38. protected $twofaccountB;
  39. protected $twofaccountC;
  40. protected $twofaccountD;
  41. private const NEW_GROUP_NAME = 'MyNewGroup';
  42. protected function setUp() : void
  43. {
  44. parent::setUp();
  45. $this->user = User::factory()->create();
  46. $this->userGroupA = Group::factory()->for($this->user)->create();
  47. $this->userGroupB = Group::factory()->for($this->user)->create();
  48. $this->twofaccountA = TwoFAccount::factory()->for($this->user)->create([
  49. 'group_id' => $this->userGroupA->id,
  50. ]);
  51. $this->twofaccountB = TwoFAccount::factory()->for($this->user)->create([
  52. 'group_id' => $this->userGroupA->id,
  53. ]);
  54. $this->anotherUser = User::factory()->create();
  55. $this->anotherUserGroupA = Group::factory()->for($this->anotherUser)->create();
  56. $this->anotherUserGroupB = Group::factory()->for($this->anotherUser)->create();
  57. $this->twofaccountC = TwoFAccount::factory()->for($this->anotherUser)->create([
  58. 'group_id' => $this->anotherUserGroupA->id,
  59. ]);
  60. $this->twofaccountD = TwoFAccount::factory()->for($this->anotherUser)->create([
  61. 'group_id' => $this->anotherUserGroupB->id,
  62. ]);
  63. }
  64. #[Test]
  65. public function test_index_returns_user_groups_only_with_pseudo_group()
  66. {
  67. $this->actingAs($this->user, 'api-guard')
  68. ->json('GET', '/api/v1/groups')
  69. ->assertOk()
  70. ->assertExactJson([
  71. '0' => [
  72. 'id' => 0,
  73. 'name' => 'All',
  74. 'twofaccounts_count' => 2,
  75. ],
  76. '1' => [
  77. 'id' => $this->userGroupA->id,
  78. 'name' => $this->userGroupA->name,
  79. 'twofaccounts_count' => 2,
  80. ],
  81. '2' => [
  82. 'id' => $this->userGroupB->id,
  83. 'name' => $this->userGroupB->name,
  84. 'twofaccounts_count' => 0,
  85. ],
  86. ]);
  87. }
  88. #[Test]
  89. public function test_orphan_groups_are_reassign_to_the_only_user()
  90. {
  91. config(['auth.defaults.guard' => 'reverse-proxy-guard']);
  92. $this->anotherUser->delete();
  93. $this->userGroupA->user_id = null;
  94. $this->userGroupA->save();
  95. $this->assertCount(1, User::all());
  96. $this->assertNull($this->userGroupA->user_id);
  97. $this->actingAs($this->user, 'reverse-proxy-guard')
  98. ->json('GET', '/api/v1/groups')
  99. ->assertOk();
  100. $this->userGroupA->refresh();
  101. $this->assertNotNull($this->userGroupA->user_id);
  102. }
  103. #[Test]
  104. public function test_store_returns_created_group_resource()
  105. {
  106. $this->actingAs($this->user, 'api-guard')
  107. ->json('POST', '/api/v1/groups', [
  108. 'name' => self::NEW_GROUP_NAME,
  109. ])
  110. ->assertCreated()
  111. ->assertJsonFragment([
  112. 'name' => self::NEW_GROUP_NAME,
  113. 'twofaccounts_count' => 0,
  114. ]);
  115. $this->assertDatabaseHas('groups', [
  116. 'name' => self::NEW_GROUP_NAME,
  117. 'user_id' => $this->user->id,
  118. ]);
  119. }
  120. #[Test]
  121. public function test_store_with_existing_group_name_returns_validation_error()
  122. {
  123. $this->actingAs($this->user, 'api-guard')
  124. ->json('POST', '/api/v1/groups', [
  125. 'name' => $this->userGroupA->name,
  126. ])
  127. ->assertStatus(422);
  128. }
  129. #[Test]
  130. public function test_store_with_all_group_name_returns_validation_error()
  131. {
  132. $this->actingAs($this->user, 'api-guard')
  133. ->json('POST', '/api/v1/groups', [
  134. 'name' => __('commons.all'),
  135. ])
  136. ->assertStatus(422);
  137. }
  138. #[Test]
  139. public function test_store_invalid_data_returns_validation_error()
  140. {
  141. $this->actingAs($this->user, 'api-guard')
  142. ->json('POST', '/api/v1/groups', [
  143. 'name' => null,
  144. ])
  145. ->assertStatus(422);
  146. }
  147. #[Test]
  148. public function test_show_returns_group_resource()
  149. {
  150. $group = Group::factory()->for($this->user)->create([
  151. 'name' => 'My group',
  152. ]);
  153. $response = $this->actingAs($this->user, 'api-guard')
  154. ->json('GET', '/api/v1/groups/' . $group->id)
  155. ->assertOk()
  156. ->assertJsonFragment([
  157. 'name' => 'My group',
  158. 'twofaccounts_count' => 0,
  159. ]);
  160. }
  161. #[Test]
  162. public function test_show_missing_group_returns_not_found()
  163. {
  164. $response = $this->actingAs($this->user, 'api-guard')
  165. ->json('GET', '/api/v1/groups/1000')
  166. ->assertNotFound()
  167. ->assertJsonStructure([
  168. 'message',
  169. ]);
  170. }
  171. #[Test]
  172. public function test_show_group_of_another_user_is_forbidden()
  173. {
  174. $response = $this->actingAs($this->anotherUser, 'api-guard')
  175. ->json('GET', '/api/v1/groups/' . $this->userGroupA->id)
  176. ->assertForbidden()
  177. ->assertJsonStructure([
  178. 'message',
  179. ]);
  180. }
  181. #[Test]
  182. public function test_show_missing_group_with_id_0_returns_the_virtual_all_group_resource()
  183. {
  184. $userTwofaccounts = $this->user->twofaccounts;
  185. $response = $this->actingAs($this->user, 'api-guard')
  186. ->json('GET', '/api/v1/groups/0')
  187. ->assertOk()
  188. ->assertJsonFragment([
  189. 'name' => __('commons.all'),
  190. 'twofaccounts_count' => $userTwofaccounts->count(),
  191. ]);
  192. }
  193. #[Test]
  194. public function test_update_returns_updated_group_resource()
  195. {
  196. $group = Group::factory()->for($this->user)->create();
  197. $response = $this->actingAs($this->user, 'api-guard')
  198. ->json('PUT', '/api/v1/groups/' . $group->id, [
  199. 'name' => 'name updated',
  200. ])
  201. ->assertOk()
  202. ->assertJsonFragment([
  203. 'name' => 'name updated',
  204. 'twofaccounts_count' => 0,
  205. ]);
  206. }
  207. #[Test]
  208. public function test_update_missing_group_returns_not_found()
  209. {
  210. $response = $this->actingAs($this->user, 'api-guard')
  211. ->json('PUT', '/api/v1/groups/1000', [
  212. 'name' => 'testUpdate',
  213. ])
  214. ->assertNotFound()
  215. ->assertJsonStructure([
  216. 'message',
  217. ]);
  218. }
  219. #[Test]
  220. public function test_update_with_invalid_data_returns_validation_error()
  221. {
  222. $group = Group::factory()->for($this->user)->create();
  223. $response = $this->actingAs($this->user, 'api-guard')
  224. ->json('PUT', '/api/v1/groups/' . $group->id, [
  225. 'name' => null,
  226. ])
  227. ->assertStatus(422);
  228. }
  229. #[Test]
  230. public function test_update_group_of_another_user_is_forbidden()
  231. {
  232. $response = $this->actingAs($this->anotherUser, 'api-guard')
  233. ->json('PUT', '/api/v1/groups/' . $this->userGroupA->id, [
  234. 'name' => 'name updated',
  235. ])
  236. ->assertForbidden()
  237. ->assertJsonStructure([
  238. 'message',
  239. ]);
  240. }
  241. #[Test]
  242. public function test_assign_accounts_returns_updated_group_resource()
  243. {
  244. $group = Group::factory()->for($this->user)->create();
  245. $accounts = TwoFAccount::factory()->count(2)->for($this->user)->create();
  246. $response = $this->actingAs($this->user, 'api-guard')
  247. ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [
  248. 'ids' => [$accounts[0]->id, $accounts[1]->id],
  249. ])
  250. ->assertOk()
  251. ->assertExactJson([
  252. 'id' => $group->id,
  253. 'name' => $group->name,
  254. 'twofaccounts_count' => 2,
  255. ]);
  256. }
  257. #[Test]
  258. public function test_assign_accounts_to_missing_group_returns_not_found()
  259. {
  260. $accounts = TwoFAccount::factory()->count(2)->for($this->user)->create();
  261. $response = $this->actingAs($this->user, 'api-guard')
  262. ->json('POST', '/api/v1/groups/1000/assign', [
  263. 'ids' => [$accounts[0]->id, $accounts[1]->id],
  264. ])
  265. ->assertNotFound()
  266. ->assertJsonStructure([
  267. 'message',
  268. ]);
  269. }
  270. #[Test]
  271. public function test_assign_invalid_accounts_returns_validation_error()
  272. {
  273. $group = Group::factory()->for($this->user)->create();
  274. $accounts = TwoFAccount::factory()->count(2)->for($this->user)->create();
  275. $response = $this->actingAs($this->user, 'api-guard')
  276. ->json('POST', '/api/v1/groups/' . $group->id . '/assign', [
  277. 'ids' => 1,
  278. ])
  279. ->assertStatus(422);
  280. }
  281. #[Test]
  282. public function test_assign_to_group_of_another_user_is_forbidden()
  283. {
  284. $response = $this->actingAs($this->anotherUser, 'api-guard')
  285. ->json('POST', '/api/v1/groups/' . $this->userGroupA->id . '/assign', [
  286. 'ids' => [$this->twofaccountC->id, $this->twofaccountD->id],
  287. ])
  288. ->assertForbidden()
  289. ->assertJsonStructure([
  290. 'message',
  291. ]);
  292. }
  293. #[Test]
  294. public function test_assign_accounts_of_another_user_is_forbidden()
  295. {
  296. $response = $this->actingAs($this->user, 'api-guard')
  297. ->json('POST', '/api/v1/groups/' . $this->userGroupA->id . '/assign', [
  298. 'ids' => [$this->twofaccountC->id, $this->twofaccountD->id],
  299. ])
  300. ->assertForbidden()
  301. ->assertJsonStructure([
  302. 'message',
  303. ]);
  304. }
  305. #[Test]
  306. public function test_accounts_returns_twofaccounts_collection()
  307. {
  308. $response = $this->actingAs($this->user, 'api-guard')
  309. ->json('GET', '/api/v1/groups/' . $this->userGroupA->id . '/twofaccounts')
  310. ->assertOk()
  311. ->assertJsonCount(2)
  312. ->assertJsonStructure([
  313. '*' => [
  314. 'group_id',
  315. 'service',
  316. 'account',
  317. 'icon',
  318. 'otp_type',
  319. 'digits',
  320. 'algorithm',
  321. 'period',
  322. 'counter',
  323. ],
  324. ])
  325. ->assertJsonFragment([
  326. 'account' => $this->twofaccountA->account,
  327. ])
  328. ->assertJsonFragment([
  329. 'account' => $this->twofaccountB->account,
  330. ]);
  331. }
  332. #[Test]
  333. public function test_accounts_returns_twofaccounts_collection_with_secret()
  334. {
  335. $response = $this->actingAs($this->user, 'api-guard')
  336. ->json('GET', '/api/v1/groups/' . $this->userGroupA->id . '/twofaccounts?withSecret=1')
  337. ->assertOk()
  338. ->assertJsonCount(2)
  339. ->assertJsonStructure([
  340. '*' => [
  341. 'group_id',
  342. 'service',
  343. 'account',
  344. 'icon',
  345. 'secret',
  346. 'otp_type',
  347. 'digits',
  348. 'algorithm',
  349. 'period',
  350. 'counter',
  351. ],
  352. ]);
  353. }
  354. #[Test]
  355. public function test_accounts_of_missing_group_returns_not_found()
  356. {
  357. $response = $this->actingAs($this->user, 'api-guard')
  358. ->json('GET', '/api/v1/groups/1000/twofaccounts')
  359. ->assertNotFound()
  360. ->assertJsonStructure([
  361. 'message',
  362. ]);
  363. }
  364. #[Test]
  365. public function test_accounts_of_another_user_group_is_forbidden()
  366. {
  367. $response = $this->actingAs($this->anotherUser, 'api-guard')
  368. ->json('GET', '/api/v1/groups/' . $this->userGroupA->id . '/twofaccounts')
  369. ->assertForbidden()
  370. ->assertJsonStructure([
  371. 'message',
  372. ]);
  373. }
  374. #[Test]
  375. public function test_accounts_of_the_all_group_returns_user_twofaccounts_collection()
  376. {
  377. $response = $this->actingAs($this->user, 'api-guard')
  378. ->json('GET', '/api/v1/groups/0/twofaccounts')
  379. ->assertOk()
  380. ->assertJsonCount(2);
  381. }
  382. /**
  383. * test Group deletion via API
  384. */
  385. #[Test]
  386. public function test_destroy_group_returns_success()
  387. {
  388. $group = Group::factory()->for($this->user)->create();
  389. $this->actingAs($this->user, 'api-guard')
  390. ->json('DELETE', '/api/v1/groups/' . $group->id)
  391. ->assertNoContent();
  392. }
  393. /**
  394. * test Group deletion via API
  395. */
  396. #[Test]
  397. public function test_destroy_missing_group_returns_not_found()
  398. {
  399. $this->actingAs($this->user, 'api-guard')
  400. ->json('DELETE', '/api/v1/groups/1000')
  401. ->assertNotFound()
  402. ->assertJsonStructure([
  403. 'message',
  404. ]);
  405. }
  406. #[Test]
  407. public function test_destroy_group_of_another_user_is_forbidden()
  408. {
  409. $response = $this->actingAs($this->anotherUser, 'api-guard')
  410. ->json('DELETE', '/api/v1/groups/' . $this->userGroupA->id)
  411. ->assertForbidden()
  412. ->assertJsonStructure([
  413. 'message',
  414. ]);
  415. }
  416. #[Test]
  417. public function test_destroy_the_all_group_is_forbidden()
  418. {
  419. $response = $this->actingAs($this->anotherUser, 'api-guard')
  420. ->json('DELETE', '/api/v1/groups/0')
  421. ->assertForbidden()
  422. ->assertJsonStructure([
  423. 'message',
  424. ]);
  425. }
  426. #[Test]
  427. public function test_destroy_group_resets_user_preferences()
  428. {
  429. // Set the default group to a specific one
  430. $this->user['preferences->defaultGroup'] = $this->userGroupA->id;
  431. // Set the active group
  432. $this->user['preferences->activeGroup'] = $this->userGroupA->id;
  433. $this->user->save();
  434. $this->assertEquals($this->userGroupA->id, $this->user->preferences['defaultGroup']);
  435. $this->assertEquals($this->userGroupA->id, $this->user->preferences['activeGroup']);
  436. $this->actingAs($this->user, 'api-guard')
  437. ->json('DELETE', '/api/v1/groups/' . $this->userGroupA->id);
  438. $this->user->refresh();
  439. $this->assertEquals(0, $this->user->preferences['defaultGroup']);
  440. $this->assertEquals(0, $this->user->preferences['activeGroup']);
  441. }
  442. #[Test]
  443. public function test_twofaccount_is_released_on_group_destroy()
  444. {
  445. $this->actingAs($this->user, 'api-guard')
  446. ->json('DELETE', '/api/v1/groups/' . $this->userGroupA->id)
  447. ->assertNoContent();
  448. $this->twofaccountA->refresh();
  449. $this->twofaccountB->refresh();
  450. $this->assertNull($this->twofaccountA->group_id);
  451. $this->assertNull($this->twofaccountB->group_id);
  452. }
  453. }