GroupControllerTest.php 13 KB

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