IconControllerTest.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace Tests\Api\v1\Controllers;
  3. use App\Api\v1\Controllers\IconController;
  4. use App\Facades\IconStore;
  5. use App\Models\TwoFAccount;
  6. use App\Models\User;
  7. use Illuminate\Http\Testing\FileFactory;
  8. use Illuminate\Http\UploadedFile;
  9. use Illuminate\Support\Facades\Http;
  10. use Illuminate\Support\Facades\Storage;
  11. use PHPUnit\Framework\Attributes\CoversClass;
  12. use PHPUnit\Framework\Attributes\Test;
  13. use Tests\Classes\LocalFile;
  14. use Tests\Data\CommonDataProvider;
  15. use Tests\Data\HttpRequestTestData;
  16. use Tests\Data\OtpTestData;
  17. use Tests\FeatureTestCase;
  18. /**
  19. * IconController test class
  20. */
  21. #[CoversClass(IconController::class)]
  22. class IconControllerTest extends FeatureTestCase
  23. {
  24. /**
  25. * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
  26. */
  27. protected $user;
  28. protected function setUp() : void
  29. {
  30. parent::setUp();
  31. Storage::fake('icons');
  32. Storage::fake('logos');
  33. Http::preventStrayRequests();
  34. Http::fake([
  35. OtpTestData::EXTERNAL_IMAGE_URL_DECODED => Http::response((new FileFactory)->image('file.png', 10, 10)->tempFile, 200),
  36. ]);
  37. $this->user = User::factory()->create();
  38. }
  39. #[Test]
  40. public function test_upload_icon_returns_filename_using_the_iconStore()
  41. {
  42. $iconName = 'testIcon.jpg';
  43. $file = UploadedFile::fake()->image($iconName);
  44. $response = $this->actingAs($this->user, 'api-guard')
  45. ->json('POST', '/api/v1/icons', [
  46. 'icon' => $file,
  47. ])
  48. ->assertCreated()
  49. ->assertJsonStructure([
  50. 'filename',
  51. ]);
  52. }
  53. #[Test]
  54. public function test_upload_icon_stores_it_to_database()
  55. {
  56. $file = UploadedFile::fake()->image('testIcon.jpg');
  57. $response = $this->actingAs($this->user, 'api-guard')
  58. ->json('POST', '/api/v1/icons', [
  59. 'icon' => $file,
  60. ]);
  61. }
  62. #[Test]
  63. public function test_upload_with_invalid_data_returns_validation_error()
  64. {
  65. $response = $this->actingAs($this->user, 'api-guard')
  66. ->json('POST', '/api/v1/icons', [
  67. 'icon' => null,
  68. ])
  69. ->assertStatus(422);
  70. }
  71. #[Test]
  72. public function test_upload_infected_svg_data_stores_stores_sanitized_svg_content()
  73. {
  74. $file = LocalFile::fake()->infectedSvgIconFile();
  75. $response = $this->actingAs($this->user, 'api-guard')
  76. ->json('POST', '/api/v1/icons', [
  77. 'icon' => $file,
  78. ])
  79. ->assertCreated();
  80. $svgContent = IconStore::get($response->getData()->filename);
  81. $this->assertStringNotContainsString(OtpTestData::ICON_SVG_MALICIOUS_CODE, $svgContent);
  82. }
  83. #[Test]
  84. public function test_fetch_logo_returns_filename()
  85. {
  86. Http::fake([
  87. CommonDataProvider::SELFH_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
  88. ]);
  89. $response = $this->actingAs($this->user, 'api-guard')
  90. ->json('POST', '/api/v1/icons/default', [
  91. 'service' => 'service',
  92. ])
  93. ->assertStatus(201)
  94. ->assertJsonStructure([
  95. 'filename',
  96. ]);
  97. }
  98. #[Test]
  99. public function test_fetch_logo_using_specified_icon_collection_returns_filename()
  100. {
  101. Http::fake([
  102. CommonDataProvider::SELFH_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
  103. CommonDataProvider::DASHBOARDICONS_URL => Http::response(OtpTestData::ICON_SVG_DATA, 200),
  104. ]);
  105. $response = $this->actingAs($this->user, 'api-guard')
  106. ->json('POST', '/api/v1/icons/default', [
  107. 'service' => 'service',
  108. 'iconCollection' => 'dashboardicons',
  109. ])
  110. ->assertStatus(201)
  111. ->assertJsonStructure([
  112. 'filename',
  113. ]);
  114. // Don't know why but 'getData()->filename' has some unwanted spaces in it so we trim them
  115. $svgContent = trim(str_replace('> <', '><', IconStore::get($response->getData()->filename)));
  116. $this->assertEquals(OtpTestData::ICON_SVG_DATA, $svgContent);
  117. }
  118. #[Test]
  119. public function test_fetch_logo_return_validation_error()
  120. {
  121. $response = $this->actingAs($this->user, 'api-guard')
  122. ->json('POST', '/api/v1/icons/default', [
  123. 'service' => 'service',
  124. 'iconCollection' => 'not_a_valid_icon_collection',
  125. ])
  126. ->assertStatus(422);
  127. }
  128. #[Test]
  129. public function test_fetch_logo_with_infected_svg_data_stores_sanitized_svg_content()
  130. {
  131. Http::fake([
  132. CommonDataProvider::SELFH_URL => Http::response(OtpTestData::ICON_SVG_DATA_INFECTED, 200),
  133. ]);
  134. $response = $this->actingAs($this->user, 'api-guard')
  135. ->json('POST', '/api/v1/icons/default', [
  136. 'service' => 'service',
  137. ])
  138. ->assertStatus(201)
  139. ->assertJsonStructure([
  140. 'filename',
  141. ]);
  142. $svgContent = IconStore::get($response->getData()->filename);
  143. $this->assertStringNotContainsString(OtpTestData::ICON_SVG_MALICIOUS_CODE, $svgContent);
  144. }
  145. #[Test]
  146. public function test_fetch_unknown_logo_returns_nothing()
  147. {
  148. Http::fake([
  149. CommonDataProvider::SELFH_URL => Http::response('not found', 404),
  150. ]);
  151. $response = $this->actingAs($this->user, 'api-guard')
  152. ->json('POST', '/api/v1/icons/default', [
  153. 'service' => 'NameOfAnUnknownServiceForSure',
  154. ])
  155. ->assertNoContent();
  156. }
  157. #[Test]
  158. public function test_delete_icon_returns_success_using_the_iconStore()
  159. {
  160. IconStore::spy();
  161. $iconName = 'testIcon.jpg';
  162. $response = $this->actingAs($this->user, 'api-guard')
  163. ->json('DELETE', '/api/v1/icons/' . $iconName)
  164. ->assertNoContent(204);
  165. IconStore::shouldHaveReceived('delete')->once()->with($iconName);
  166. }
  167. #[Test]
  168. public function test_delete_invalid_icon_returns_success()
  169. {
  170. $response = $this->actingAs($this->user, 'api-guard')
  171. ->json('DELETE', '/api/v1/icons/null')
  172. ->assertNoContent(204);
  173. }
  174. #[Test]
  175. public function test_delete_icon_of_another_user_is_forbidden()
  176. {
  177. $anotherUser = User::factory()->create();
  178. TwoFAccount::factory()->for($anotherUser)->create([
  179. 'icon' => 'testIcon.jpg',
  180. ]);
  181. $response = $this->actingAs($this->user, 'api-guard')
  182. ->json('DELETE', '/api/v1/icons/testIcon.jpg')
  183. ->assertForbidden()
  184. ->assertJsonStructure([
  185. 'message',
  186. ]);
  187. }
  188. }