Sfoglia il codice sorgente

Update & Complete tests

Bubka 1 mese fa
parent
commit
48cc7d76e4
29 ha cambiato i file con 544 aggiunte e 192 eliminazioni
  1. 3 4
      app/Services/LogoLib/AbstractLogoLib.php
  2. 49 13
      tests/Api/v1/Controllers/IconControllerTest.php
  3. 3 2
      tests/Api/v1/Controllers/TwoFAccountControllerTest.php
  4. 6 2
      tests/Api/v1/Requests/GroupAssignRequestTest.php
  5. 96 11
      tests/Api/v1/Requests/IconFetchRequestTest.php
  6. 6 2
      tests/Api/v1/Requests/QrCodeDecodeRequestTest.php
  7. 6 2
      tests/Api/v1/Requests/SettingStoreRequestTest.php
  8. 6 2
      tests/Api/v1/Requests/SettingUpdateRequestTest.php
  9. 6 2
      tests/Api/v1/Requests/TwoFAccountBatchRequestTest.php
  10. 6 2
      tests/Api/v1/Requests/TwoFAccountExportRequestTest.php
  11. 6 2
      tests/Api/v1/Requests/TwoFAccountImportRequestTest.php
  12. 6 2
      tests/Api/v1/Requests/TwoFAccountIndexRequestTest.php
  13. 6 2
      tests/Api/v1/Requests/TwoFAccountReorderRequestTest.php
  14. 6 2
      tests/Api/v1/Requests/TwoFAccountStoreRequestTest.php
  15. 6 2
      tests/Api/v1/Requests/TwoFAccountUpdateRequestTest.php
  16. 6 2
      tests/Api/v1/Requests/TwoFAccountUriRequestTest.php
  17. 6 2
      tests/Api/v1/Requests/UserManagerPromoteRequestTest.php
  18. 6 2
      tests/Api/v1/Requests/UserManagerStoreRequestTest.php
  19. 23 9
      tests/Data/CommonDataProvider.php
  20. 6 0
      tests/Data/HttpRequestTestData.php
  21. 6 2
      tests/Feature/Http/Requests/LoginRequestTest.php
  22. 6 2
      tests/Feature/Http/Requests/UserDeleteRequestTest.php
  23. 6 2
      tests/Feature/Http/Requests/UserPatchPwdRequestTest.php
  24. 6 2
      tests/Feature/Http/Requests/UserStoreRequestTest.php
  25. 6 2
      tests/Feature/Http/Requests/WebauthnAssertedRequestTest.php
  26. 6 2
      tests/Feature/Http/Requests/WebauthnRenameRequestTest.php
  27. 4 4
      tests/Feature/Services/IconServiceTest.php
  28. 0 109
      tests/Feature/Services/LogoLib/TfaLogoLibTest.php
  29. 240 0
      tests/Feature/Services/LogoLibsTest.php

+ 3 - 4
app/Services/LogoLib/AbstractLogoLib.php

@@ -139,10 +139,9 @@ abstract class AbstractLogoLib implements LogoLibInterface
         $url = $this->logoUrl($logoFilename);
 
         try {
-            // $response = Http::withOptions([
-            //     'proxy' => config('2fauth.config.outgoingProxy'),
-            // ])->retry(3, 100)->get($url);
-            $response = Http::get($url);
+            $response = Http::withOptions([
+                'proxy' => config('2fauth.config.outgoingProxy'),
+            ])->retry(3, 100)->get($url);
 
             if ($response->successful()) {
                 $filename = $this->cachePrefix . $logoFilename;

+ 49 - 13
tests/Api/v1/Controllers/IconControllerTest.php

@@ -6,13 +6,11 @@ use App\Api\v1\Controllers\IconController;
 use App\Facades\IconStore;
 use App\Models\TwoFAccount;
 use App\Models\User;
-use App\Services\LogoLib\TfaLogoLib;
 use Illuminate\Http\Testing\FileFactory;
 use Illuminate\Http\UploadedFile;
 use Illuminate\Support\Facades\Http;
 use Illuminate\Support\Facades\Storage;
 use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\DataProviderExternal;
 use PHPUnit\Framework\Attributes\Test;
 use Tests\Classes\LocalFile;
 use Tests\Data\CommonDataProvider;
@@ -39,12 +37,6 @@ class IconControllerTest extends FeatureTestCase
         Storage::fake('logos');
 
         Http::preventStrayRequests();
-        Http::fake([
-            'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            'https://cdn.jsdelivr.net/gh/selfhst/icons/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            'https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfalogoLib::TFA_URL           => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
-        ]);
         Http::fake([
             OtpTestData::EXTERNAL_IMAGE_URL_DECODED => Http::response((new FileFactory)->image('file.png', 10, 10)->tempFile, 200),
         ]);
@@ -105,13 +97,15 @@ class IconControllerTest extends FeatureTestCase
     }
 
     #[Test]
-    #[DataProviderExternal(CommonDataProvider::class, 'iconsCollectionProvider')]
-    public function test_fetch_logo_returns_filename($iconCollection)
+    public function test_fetch_logo_returns_filename()
     {
+        Http::fake([
+            CommonDataProvider::SELFH_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+        ]);
+
         $response = $this->actingAs($this->user, 'api-guard')
             ->json('POST', '/api/v1/icons/default', [
                 'service' => 'service',
-                'iconCollection' => $iconCollection,
             ])
             ->assertStatus(201)
             ->assertJsonStructure([
@@ -119,13 +113,51 @@ class IconControllerTest extends FeatureTestCase
             ]);
     }
 
+    #[Test]
+    public function test_fetch_logo_using_specified_icon_collection_returns_filename()
+    {
+        Http::fake([
+            CommonDataProvider::SELFH_URL          => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            CommonDataProvider::DASHBOARDICONS_URL => Http::response(OtpTestData::ICON_SVG_DATA, 200),
+        ]);
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons/default', [
+                'service'        => 'service',
+                'iconCollection' => 'dashboardicons',
+            ])
+            ->assertStatus(201)
+            ->assertJsonStructure([
+                'filename',
+            ]);
+
+        // Don't know why but 'getData()->filename' has some unwanted spaces in it so we trim them
+        $svgContent = trim(str_replace('> <', '><', IconStore::get($response->getData()->filename)));
+
+        $this->assertEquals(OtpTestData::ICON_SVG_DATA, $svgContent);
+    }
+
+    #[Test]
+    public function test_fetch_logo_return_validation_error()
+    {
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons/default', [
+                'service'        => 'service',
+                'iconCollection' => 'not_a_valid_icon_collection',
+            ])
+            ->assertStatus(422);
+    }
+
     #[Test]
     public function test_fetch_logo_with_infected_svg_data_stores_sanitized_svg_content()
     {
+        Http::fake([
+            CommonDataProvider::SELFH_URL => Http::response(OtpTestData::ICON_SVG_DATA_INFECTED, 200),
+        ]);
+
         $response = $this->actingAs($this->user, 'api-guard')
             ->json('POST', '/api/v1/icons/default', [
                 'service' => 'service',
-                'iconCollection' => 'tfa',
             ])
             ->assertStatus(201)
             ->assertJsonStructure([
@@ -139,9 +171,13 @@ class IconControllerTest extends FeatureTestCase
     #[Test]
     public function test_fetch_unknown_logo_returns_nothing()
     {
+        Http::fake([
+            CommonDataProvider::SELFH_URL => Http::response('not found', 404),
+        ]);
+
         $response = $this->actingAs($this->user, 'api-guard')
             ->json('POST', '/api/v1/icons/default', [
-                'service' => 'unknown_company',
+                'service' => 'NameOfAnUnknownServiceForSure',
             ])
             ->assertNoContent();
     }

+ 3 - 2
tests/Api/v1/Controllers/TwoFAccountControllerTest.php

@@ -25,6 +25,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
 use PHPUnit\Framework\Attributes\DataProvider;
 use PHPUnit\Framework\Attributes\Test;
 use Tests\Classes\LocalFile;
+use Tests\Data\CommonDataProvider;
 use Tests\Data\HttpRequestTestData;
 use Tests\Data\MigrationTestData;
 use Tests\Data\OtpTestData;
@@ -242,8 +243,8 @@ class TwoFAccountControllerTest extends FeatureTestCase
 
         Http::preventStrayRequests();
         Http::fake([
-            TfaLogoLib::IMG_URL . '*'                   => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfaLogoLib::TFA_URL                             => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
+            CommonDataProvider::TFA_URL                     => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            TfaLogoLib::TFA_JSON_URL                             => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
             OtpTestData::EXTERNAL_IMAGE_URL_DECODED          => Http::response((new FileFactory)->image('file.png', 10, 10)->tempFile, 200),
             OtpTestData::EXTERNAL_INFECTED_IMAGE_URL_DECODED => Http::response((new FileFactory)->createWithContent('infected.svg', OtpTestData::ICON_SVG_DATA_INFECTED)->tempFile, 200),
             'example.com/*'                                  => Http::response(null, 400),

+ 6 - 2
tests/Api/v1/Requests/GroupAssignRequestTest.php

@@ -35,7 +35,9 @@ class GroupAssignRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new GroupAssignRequest;
+        $request = new GroupAssignRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -59,7 +61,9 @@ class GroupAssignRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new GroupAssignRequest;
+        $request = new GroupAssignRequest;
+        $request->merge($data);
+
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 96 - 11
tests/Api/v1/Requests/IconFetchRequestTest.php

@@ -35,7 +35,9 @@ class IconFetchRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new IconFetchRequest;
+        $request = new IconFetchRequest;
+        $request->merge($data);
+
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -47,15 +49,59 @@ class IconFetchRequestTest extends TestCase
     public static function provideValidData() : array
     {
         return [
-            [[
+            'VALID_SERVICE_AS_STRING' => [[
                 'service' => 'validWord',
             ]],
-            [[
-                'service' => '0',
-            ]],
-            [[
+            'VALID_SERVCE_WITH_SPECIAL_CHARS' => [[
                 'service' => '~string.with-sp3ci@l-ch4rs',
             ]],
+            'VALID_SELFH_ICON_COLLECTION' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'selfh',
+            ]],
+            'VALID_DASHBOARDICONS_ICON_COLLECTION' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'dashboardicons',
+            ]],
+            'VALID_TFA_ICON_COLLECTION' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'tfa',
+            ]],
+            'VALID_SELFH_ICON_COLLECTION_WITH_VALID_REGULAR_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'selfh',
+                'variant' => 'regular',
+            ]],
+            'VALID_SELFH_ICON_COLLECTION_WITH_VALID_LIGHT_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'selfh',
+                'variant' => 'light',
+            ]],
+            'VALID_SELFH_ICON_COLLECTION_WITH_VALID_DARK_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'selfh',
+                'variant' => 'dark',
+            ]],
+            'VALID_DASHBOARDICONS_ICON_COLLECTION_WITH_VALID_REGULAR_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'dashboardicons',
+                'variant' => 'regular',
+            ]],
+            'VALID_DASHBOARDICONS_ICON_COLLECTION_WITH_VALID_LIGHT_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'dashboardicons',
+                'variant' => 'light',
+            ]],
+            'VALID_DASHBOARDICONS_ICON_COLLECTION_WITH_VALID_DARK_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'dashboardicons',
+                'variant' => 'dark',
+            ]],
+            'VALID_TFA_ICON_COLLECTION_WITH_VALID_REGULAR_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'tfa',
+                'variant' => 'regular',
+            ]],
         ];
     }
 
@@ -63,7 +109,9 @@ class IconFetchRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new IconFetchRequest;
+        $request = new IconFetchRequest;
+        $request->merge($data);
+
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());
@@ -75,18 +123,55 @@ class IconFetchRequestTest extends TestCase
     public static function provideInvalidData() : array
     {
         return [
-            [[
+            'NULL_SERVICE' => [[
                 'service' => null,
             ]],
-            [[
+            'NULL_ICON_COLLECTION' => [[
+                'service' => 'validWord',
+                'iconCollection' => null,
+            ]],
+            'NULL_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'tfa',
+                'variant' => null,
+            ]],
+            'EMPTY_ICON_COLLECTION' => [[
+                'service' => 'validWord',
+                'iconCollection' => '',
+            ]],
+            'EMPTY_VARIANT' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'tfa',
+                'variant' => '',
+            ]],
+            'SERVICE_AS_INT' => [[
                 'service' => 0,
             ]],
-            [[
+            'SERVICE_AS_BOOL' => [[
                 'service' => true,
             ]],
-            [[
+            'SERVICE_AS_ARRAY' => [[
                 'service' => [],
             ]],
+            'NOT_IN_ICON_COLLECTION_LIST' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'string_not_in_icon_collection_list',
+            ]],
+            'NOT_IN_SELFH_VARIANT_LIST' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'selfh',
+                'variant' => 'string_not_in_selfh_variant_list',
+            ]],
+            'NOT_IN_DASHBOARDICONS_VARIANT_LIST' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'dashboardicons',
+                'variant' => 'string_not_in_dashboardicons_variant_list',
+            ]],
+            'NOT_IN_TFA_VARIANT_LIST' => [[
+                'service' => 'validWord',
+                'iconCollection' => 'tfa',
+                'variant' => 'string_not_in_tfa_variant_list',
+            ]],
         ];
     }
 }

+ 6 - 2
tests/Api/v1/Requests/QrCodeDecodeRequestTest.php

@@ -36,7 +36,9 @@ class QrCodeDecodeRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new QrCodeDecodeRequest;
+        $request = new QrCodeDecodeRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -60,7 +62,9 @@ class QrCodeDecodeRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new QrCodeDecodeRequest;
+        $request = new QrCodeDecodeRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/SettingStoreRequestTest.php

@@ -38,7 +38,9 @@ class SettingStoreRequestTest extends FeatureTestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new SettingStoreRequest;
+        $request = new SettingStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -71,7 +73,9 @@ class SettingStoreRequestTest extends FeatureTestCase
     {
         Settings::set(self::UNIQUE_KEY, 'uniqueValue');
 
-        $request   = new SettingStoreRequest;
+        $request = new SettingStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/SettingUpdateRequestTest.php

@@ -35,7 +35,9 @@ class SettingUpdateRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new SettingUpdateRequest;
+        $request = new SettingUpdateRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -63,7 +65,9 @@ class SettingUpdateRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new SettingUpdateRequest;
+        $request = new SettingUpdateRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountBatchRequestTest.php

@@ -36,7 +36,9 @@ class TwoFAccountBatchRequestTest extends TestCase
     #[DataProviderExternal(TwoFAccountDataProvider::class, 'validIdsProvider')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountBatchRequest;
+        $request = new TwoFAccountBatchRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -46,7 +48,9 @@ class TwoFAccountBatchRequestTest extends TestCase
     #[DataProviderExternal(TwoFAccountDataProvider::class, 'invalidIdsProvider')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountBatchRequest;
+        $request = new TwoFAccountBatchRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountExportRequestTest.php

@@ -35,7 +35,9 @@ class TwoFAccountExportRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountExportRequest;
+        $request = new TwoFAccountExportRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -81,7 +83,9 @@ class TwoFAccountExportRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountExportRequest;
+        $request = new TwoFAccountExportRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountImportRequestTest.php

@@ -35,7 +35,9 @@ class TwoFAccountImportRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountImportRequest;
+        $request = new TwoFAccountImportRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -57,7 +59,9 @@ class TwoFAccountImportRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountImportRequest;
+        $request = new TwoFAccountImportRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountIndexRequestTest.php

@@ -36,7 +36,9 @@ class TwoFAccountIndexRequestTest extends TestCase
     #[DataProviderExternal(TwoFAccountDataProvider::class, 'validIdsProvider')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountIndexRequest;
+        $request = new TwoFAccountIndexRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -46,7 +48,9 @@ class TwoFAccountIndexRequestTest extends TestCase
     #[DataProviderExternal(TwoFAccountDataProvider::class, 'invalidIdsProvider')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountIndexRequest;
+        $request = new TwoFAccountIndexRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountReorderRequestTest.php

@@ -35,7 +35,9 @@ class TwoFAccountReorderRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountReorderRequest;
+        $request = new TwoFAccountReorderRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -60,7 +62,9 @@ class TwoFAccountReorderRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountReorderRequest;
+        $request = new TwoFAccountReorderRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountStoreRequestTest.php

@@ -37,7 +37,9 @@ class TwoFAccountStoreRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountStoreRequest;
+        $request = new TwoFAccountStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -111,7 +113,9 @@ class TwoFAccountStoreRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountStoreRequest;
+        $request = new TwoFAccountStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountUpdateRequestTest.php

@@ -37,7 +37,9 @@ class TwoFAccountUpdateRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountUpdateRequest;
+        $request = new TwoFAccountUpdateRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -87,7 +89,9 @@ class TwoFAccountUpdateRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountUpdateRequest;
+        $request = new TwoFAccountUpdateRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/TwoFAccountUriRequestTest.php

@@ -35,7 +35,9 @@ class TwoFAccountUriRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new TwoFAccountUriRequest;
+        $request = new TwoFAccountUriRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -64,7 +66,9 @@ class TwoFAccountUriRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new TwoFAccountUriRequest;
+        $request = new TwoFAccountUriRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/UserManagerPromoteRequestTest.php

@@ -35,7 +35,9 @@ class UserManagerPromoteRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new UserManagerPromoteRequest;
+        $request = new UserManagerPromoteRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -66,7 +68,9 @@ class UserManagerPromoteRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new UserManagerPromoteRequest;
+        $request = new UserManagerPromoteRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Api/v1/Requests/UserManagerStoreRequestTest.php

@@ -41,7 +41,9 @@ class UserManagerStoreRequestTest extends FeatureTestCase
             'email' => 'jane@example.com',
         ]);
 
-        $request   = new UserManagerStoreRequest;
+        $request = new UserManagerStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -79,7 +81,9 @@ class UserManagerStoreRequestTest extends FeatureTestCase
             'email' => 'john@example.com',
         ]);
 
-        $request   = new UserManagerStoreRequest;
+        $request = new UserManagerStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 23 - 9
tests/Data/CommonDataProvider.php

@@ -4,20 +4,34 @@ declare(strict_types=1);
 
 namespace Tests\Data;
 
+use App\Services\LogoLib\DashboardiconsLogoLib;
+use App\Services\LogoLib\SelfhLogoLib;
+use App\Services\LogoLib\TfaLogoLib;
+
 final class CommonDataProvider
 {
+    const TFA_URL = 'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*';
+    const SELFH_URL_ROOT = 'https://cdn.jsdelivr.net/gh/selfhst/icons/';
+    const SELFH_URL = self::SELFH_URL_ROOT . '*';
+    const DASHBOARDICONS_URL_ROOT = 'https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/';
+    const DASHBOARDICONS_URL = self::DASHBOARDICONS_URL_ROOT . '*';
+
     public static function iconsCollectionProvider() : array
     {
         return [
-            'TFA' => [
-                'tfa',
-            ],
-            'SELFH' => [
-                'selfh',
-            ],
-            'DASHBOARDICONS' => [
-                'dashboardicons',
-            ],
+            'TFA' => [[
+                'name'  => 'tfa',
+                'class' => TfaLogoLib::class,
+                
+            ]],
+            'SELFH' => [[
+                'name'  => 'selfh',
+                'class' => SelfhLogoLib::class,
+            ]],
+            'DASHBOARDICONS' => [[
+                'name'  => 'dashboardicons',
+                'class' => DashboardiconsLogoLib::class,
+            ]],
         ];
     }
 

+ 6 - 0
tests/Data/HttpRequestTestData.php

@@ -10,6 +10,12 @@ class HttpRequestTestData
 
     const SVG_LOGO_BODY = '<svg xmlns="http://www.w3.org/2000/svg" class="r-k200y r-13gxpu9 r-4qtqp9 r-yyyyoo r-np7d94 r-dnmrzs r-bnwqim r-1plcrui r-lrvibr" width="22.706" height="22.706"><path d="M22.706 4.311c-.835.37-1.732.62-2.675.733a4.67 4.67 0 002.048-2.578 9.3 9.3 0 01-2.958 1.13 4.66 4.66 0 00-7.938 4.25 13.229 13.229 0 01-9.602-4.868c-.4.69-.63 1.49-.63 2.342a4.66 4.66 0 002.072 3.878 4.647 4.647 0 01-2.11-.583v.06a4.66 4.66 0 003.737 4.568 4.692 4.692 0 01-2.104.08 4.661 4.661 0 004.352 3.234 9.348 9.348 0 01-5.786 1.995A9.5 9.5 0 010 18.487a13.175 13.175 0 007.14 2.093c8.57 0 13.255-7.098 13.255-13.254 0-.2-.005-.402-.014-.602a9.47 9.47 0 002.323-2.41z" fill="#1da1f2"/></svg>';
 
+    const SVG_LOGO_BODY_VARIANT_REGULAR = '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 124 124" fill="none"><rect width="124" height="124"></rect></svg>';
+
+    const SVG_LOGO_BODY_VARIANT_LIGHT = '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 124 124" fill="none"><rect width="124" height="124" fill="white"></rect></svg>';
+
+    const SVG_LOGO_BODY_VARIANT_DARK = '<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 124 124" fill="none"><rect width="124" height="124" fill="black"></rect></svg>';
+
     const TFA_JSON_BODY = '
     [
         [

+ 6 - 2
tests/Feature/Http/Requests/LoginRequestTest.php

@@ -35,7 +35,9 @@ class LoginRequestTest extends FeatureTestCase
             'email' => 'JOHN.DOE@example.com',
         ]);
 
-        $request   = new LoginRequest;
+        $request = new LoginRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -66,7 +68,9 @@ class LoginRequestTest extends FeatureTestCase
             'email' => 'JOHN.DOE@example.com',
         ]);
 
-        $request   = new LoginRequest;
+        $request = new LoginRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Feature/Http/Requests/UserDeleteRequestTest.php

@@ -35,7 +35,9 @@ class UserDeleteRequestTest extends FeatureTestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new UserDeleteRequest;
+        $request = new UserDeleteRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -57,7 +59,9 @@ class UserDeleteRequestTest extends FeatureTestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new UserDeleteRequest;
+        $request = new UserDeleteRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Feature/Http/Requests/UserPatchPwdRequestTest.php

@@ -35,7 +35,9 @@ class UserPatchPwdRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new UserPatchPwdRequest;
+        $request = new UserPatchPwdRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -59,7 +61,9 @@ class UserPatchPwdRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new UserPatchPwdRequest;
+        $request = new UserPatchPwdRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Feature/Http/Requests/UserStoreRequestTest.php

@@ -36,7 +36,9 @@ class UserStoreRequestTest extends FeatureTestCase
             'email' => 'jane@example.com',
         ]);
 
-        $request   = new UserStoreRequest;
+        $request = new UserStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -72,7 +74,9 @@ class UserStoreRequestTest extends FeatureTestCase
             'email' => 'john@example.com',
         ]);
 
-        $request   = new UserStoreRequest;
+        $request = new UserStoreRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Feature/Http/Requests/WebauthnAssertedRequestTest.php

@@ -22,7 +22,9 @@ class WebauthnAssertedRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new WebauthnAssertedRequest;
+        $request = new WebauthnAssertedRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -53,7 +55,9 @@ class WebauthnAssertedRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new WebauthnAssertedRequest;
+        $request = new WebauthnAssertedRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 6 - 2
tests/Feature/Http/Requests/WebauthnRenameRequestTest.php

@@ -35,7 +35,9 @@ class WebauthnRenameRequestTest extends TestCase
     #[DataProvider('provideValidData')]
     public function test_valid_data(array $data) : void
     {
-        $request   = new WebauthnRenameRequest;
+        $request = new WebauthnRenameRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -57,7 +59,9 @@ class WebauthnRenameRequestTest extends TestCase
     #[DataProvider('provideInvalidData')]
     public function test_invalid_data(array $data) : void
     {
-        $request   = new WebauthnRenameRequest;
+        $request = new WebauthnRenameRequest;
+        $request->merge($data);
+        
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 4 - 4
tests/Feature/Services/IconServiceTest.php

@@ -40,10 +40,10 @@ class IconServiceTest extends FeatureTestCase
 
         Http::preventStrayRequests();
         Http::fake([
-            'https://raw.githubusercontent.com/2factorauth/twofactorauth/master/img/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            'https://cdn.jsdelivr.net/gh/selfhst/icons/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            'https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfaLogoLib::TFA_URL           => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
+            CommonDataProvider::TFA_URL            => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            CommonDataProvider::SELFH_URL          => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            CommonDataProvider::DASHBOARDICONS_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            TfaLogoLib::TFA_JSON_URL                    => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
         ]);
         Http::fake([
             OtpTestData::EXTERNAL_IMAGE_URL_DECODED => Http::response((new FileFactory)->image('file.png', 10, 10)->tempFile, 200),

+ 0 - 109
tests/Feature/Services/LogoLib/TfaLogoLibTest.php

@@ -1,109 +0,0 @@
-<?php
-
-namespace Tests\Feature\Services;
-
-use App\Services\LogoLib\TfaLogoLib;
-use Illuminate\Foundation\Testing\WithoutMiddleware;
-use Illuminate\Support\Facades\Http;
-use Illuminate\Support\Facades\Storage;
-use PHPUnit\Framework\Attributes\CoversClass;
-use PHPUnit\Framework\Attributes\Test;
-use Tests\Data\HttpRequestTestData;
-use Tests\FeatureTestCase;
-
-/**
- * TfalogoLibTest test class
- */
-#[CoversClass(TfalogoLib::class)]
-class TfaLogoLibTest extends FeatureTestCase
-{
-    use WithoutMiddleware;
-
-    protected TfaLogoLib $tfaLogoLib;
-
-    public function setUp() : void
-    {
-        parent::setUp();
-
-        Storage::fake('icons');
-        Storage::fake('logos');
-        Storage::fake('imagesLink');
-    }
-
-    #[Test]
-    public function test_getIcon_returns_stored_icon_file_when_logo_exists()
-    {
-        Http::preventStrayRequests();
-        Http::fake([
-            TfalogoLib::IMG_URL . '*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfalogoLib::TFA_URL           => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
-        ]);
-
-        $this->tfaLogoLib = $this->app->make(TfalogoLib::class);
-        $icon              = $this->tfaLogoLib->getIcon('service');
-
-        $this->assertNotNull($icon);
-        Storage::disk('icons')->assertExists($icon);
-    }
-
-    #[Test]
-    public function test_getIcon_returns_null_when_github_request_fails()
-    {
-        Http::preventStrayRequests();
-        Http::fake([
-            TfalogoLib::IMG_URL . '*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfalogoLib::TFA_URL           => Http::response('not found', 404),
-        ]);
-
-        $this->tfaLogoLib = $this->app->make(TfalogoLib::class);
-        $icon              = $this->tfaLogoLib->getIcon('service');
-
-        $this->assertEquals(null, $icon);
-    }
-
-    #[Test]
-    public function test_getIcon_returns_null_when_logo_fetching_fails()
-    {
-        Http::preventStrayRequests();
-        Http::fake([
-            TfalogoLib::IMG_URL . '*' => Http::response('not found', 404),
-            TfalogoLib::TFA_URL           => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
-        ]);
-
-        $this->tfaLogoLib = $this->app->make(TfalogoLib::class);
-        $icon              = $this->tfaLogoLib->getIcon('service');
-
-        $this->assertEquals(null, $icon);
-    }
-
-    #[Test]
-    public function test_getIcon_returns_null_when_no_logo_exists()
-    {
-        Http::preventStrayRequests();
-        Http::fake([
-            TfalogoLib::IMG_URL . '*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfalogoLib::TFA_URL           => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
-        ]);
-
-        $this->tfaLogoLib = $this->app->make(TfalogoLib::class);
-        $icon              = $this->tfaLogoLib->getIcon('no_logo_should_exists_with_this_name');
-
-        $this->assertEquals(null, $icon);
-    }
-
-    #[Test]
-    public function test_TfalogoLib_loads_empty_collection_when_tfajson_fetching_fails()
-    {
-        Http::preventStrayRequests();
-        Http::fake([
-            TfalogoLib::IMG_URL . '*' => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
-            TfalogoLib::TFA_URL           => Http::response('not found', 404),
-        ]);
-
-        $this->tfaLogoLib = $this->app->make(TfalogoLib::class);
-        $icon              = $this->tfaLogoLib->getIcon('service');
-
-        $this->assertNull($icon);
-        Storage::disk('logos')->assertMissing(TfalogoLib::TFA_JSON);
-    }
-}

+ 240 - 0
tests/Feature/Services/LogoLibsTest.php

@@ -0,0 +1,240 @@
+<?php
+
+namespace Tests\Feature\Services;
+
+use App\Models\User;
+use App\Services\LogoLib\AbstractLogoLib;
+use App\Services\LogoLib\DashboardiconsLogoLib;
+use App\Services\LogoLib\LogoLibInterface;
+use App\Services\LogoLib\SelfhLogoLib;
+use App\Services\LogoLib\TfaLogoLib;
+use Illuminate\Foundation\Testing\WithoutMiddleware;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Storage;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\Attributes\DataProviderExternal;
+use PHPUnit\Framework\Attributes\Test;
+use Tests\Data\CommonDataProvider;
+use Tests\Data\HttpRequestTestData;
+use Tests\Data\OtpTestData;
+use Tests\FeatureTestCase;
+
+/**
+ * LogoLibsTest test class
+ */
+#[CoversClass(AbstractLogoLib::class)]
+#[CoversClass(SelfhLogoLib::class)]
+#[CoversClass(DashboardiconsLogoLib::class)]
+#[CoversClass(TfalogoLib::class)]
+class LogoLibsTest extends FeatureTestCase
+{
+    use WithoutMiddleware;
+
+    protected LogoLibInterface $logoLib;
+
+    public function setUp() : void
+    {
+        parent::setUp();
+
+        Storage::fake('icons');
+        Storage::fake('logos');
+        Storage::fake('imagesLink');
+    }
+
+    #[Test]
+    #[DataProvider('provideLogoData')]
+    public function test_getIcon_returns_stored_icon_file_in_requested_variant_when_logo_exists(array $logo)
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            $logo['url'] => Http::response($logo['svg'], 200),
+        ]);
+
+        $this->logoLib = $this->app->make($logo['class']);
+        $icon          = $this->logoLib->getIcon('myservice', $logo['variant']);
+
+        $this->assertNotNull($icon);
+        Storage::disk('icons')->assertExists($icon);
+
+        // Don't know why but with a faked storage, when an svg file is put to the disk, the svg is prefixed with an xml tag
+        // so we prepend it manually for the assert to work as expected.
+        $this->assertEquals(trim(Storage::disk('icons')->get($icon)), '<?xml version="1.0" encoding="UTF-8"?> ' . $logo['svg']);
+    }
+
+    /**
+     * Provide Valid data for validation test
+     */
+    public static function provideLogoData() : array
+    {
+        return [
+            'SELFH_REGULAR' => [[
+                'variant' => 'regular',
+                'url'     => CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_REGULAR,
+                'class'   => SelfhLogoLib::class,
+            ]],
+            'SELFH_LIGHT' => [[
+                'variant' => 'light',
+                'url'     => CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice-light.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_LIGHT,
+                'class'   => SelfhLogoLib::class,
+            ]],
+            'SELFH_DARK' => [[
+                'variant' => 'dark',
+                'url'     => CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice-dark.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_DARK,
+                'class'   => SelfhLogoLib::class,
+            ]],
+            'DASHBOARDICONS_REGULAR' => [[
+                'variant' => 'regular',
+                'url'     => CommonDataProvider::DASHBOARDICONS_URL_ROOT . 'svg/myservice.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_LIGHT,
+                'class'   => DashboardiconsLogoLib::class,
+            ]],
+            'DASHBOARDICONS_LIGHT' => [[
+                'variant' => 'light',
+                'url'     => CommonDataProvider::DASHBOARDICONS_URL_ROOT . 'svg/myservice-light.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_LIGHT,
+                'class'   => DashboardiconsLogoLib::class,
+            ]],
+            'DASHBOARDICONS_DARK' => [[
+                'variant' => 'dark',
+                'url'     => CommonDataProvider::DASHBOARDICONS_URL_ROOT . 'svg/myservice-dark.svg',
+                'svg'     => HttpRequestTestData::SVG_LOGO_BODY_VARIANT_DARK,
+                'class'   => DashboardiconsLogoLib::class,
+            ]],
+        ];
+    }
+
+    #[Test]
+    public function test_getIcon_fetches_png_when_svg_is_not_available()
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice.svg' => Http::response('not found', 404),
+            CommonDataProvider::SELFH_URL_ROOT . 'png/myservice.png' => Http::response(base64_decode(OtpTestData::ICON_PNG_DATA), 200),
+        ]);
+
+        $this->logoLib = $this->app->make(SelfhLogoLib::class);
+        $icon          = $this->logoLib->getIcon('myservice');
+
+        $this->assertStringEndsWith('.png', $icon);
+    }
+
+    #[Test]
+    public function test_getIcon_fallbacks_to_user_preferences_when_variant_is_omitted()
+    {
+        $user = User::factory()->create();
+        $user['preferences->iconVariant'] = 'dark';
+        $user->save();
+
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice.svg' => Http::response(HttpRequestTestData::SVG_LOGO_BODY_VARIANT_REGULAR, 200),
+            CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice-dark.svg'  => Http::response(HttpRequestTestData::SVG_LOGO_BODY_VARIANT_DARK, 200),
+        ]);
+
+        $this->logoLib = $this->app->make(SelfhLogoLib::class);
+        $icon          = $this->actingAs($user)->logoLib->getIcon('myservice');
+
+        $this->assertEquals(trim(Storage::disk('icons')->get($icon)), '<?xml version="1.0" encoding="UTF-8"?> ' . HttpRequestTestData::SVG_LOGO_BODY_VARIANT_DARK);
+    }
+
+    #[Test]
+    #[DataProvider('provideVariantInvalidData')]
+    public function test_getIcon_fallbacks_to_regular_when_variant_is_invalid_without_auth($variant)
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::SELFH_URL_ROOT . 'svg/myservice.svg' => Http::response(HttpRequestTestData::SVG_LOGO_BODY_VARIANT_REGULAR, 200),
+        ]);
+
+        $this->logoLib = $this->app->make(SelfhLogoLib::class);
+        $icon          = $this->logoLib->getIcon('myservice', $variant);
+
+        $this->assertNotNull($icon);
+    }
+
+    /**
+     * Provide invalid variant data for validation test
+     */
+    public static function provideVariantInvalidData() : array
+    {
+        return [
+            'INVALID_VARIANT' => [
+                'not_a_valid_variant'
+            ],
+            'NULL_VARIANT' => [
+                null
+            ],
+            'EMPTY_VARIANT' => [
+                ''
+            ],
+        ];
+    }
+
+    #[Test]
+    public function test_getIcon_returns_null_when_tfa_github_request_fails()
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::TFA_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            TfalogoLib::TFA_JSON_URL    => Http::response('not found', 404),
+        ]);
+
+        $this->logoLib = $this->app->make(TfalogoLib::class);
+        $icon          = $this->logoLib->getIcon('service');
+
+        $this->assertEquals(null, $icon);
+    }
+
+    #[Test]
+    #[DataProviderExternal(CommonDataProvider::class, 'iconsCollectionProvider')]
+    public function test_getIcon_returns_null_when_logo_fetching_fails(array $iconProvider)
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::TFA_URL            => Http::response('not found', 404),
+            CommonDataProvider::SELFH_URL          => Http::response('not found', 404),
+            CommonDataProvider::DASHBOARDICONS_URL => Http::response('not found', 404),
+            TfalogoLib::TFA_JSON_URL               => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
+        ]);
+
+        $this->logoLib = $this->app->make($iconProvider['class']);
+        $icon          = $this->logoLib->getIcon('service');
+
+        $this->assertEquals(null, $icon);
+    }
+
+    #[Test]
+    public function test_getIcon_returns_null_when_no_logo_exists_in_the_tfa_collection()
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::TFA_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            TfalogoLib::TFA_JSON_URL    => Http::response(HttpRequestTestData::TFA_JSON_BODY, 200),
+        ]);
+
+        $this->logoLib = $this->app->make(TfalogoLib::class);
+        $icon          = $this->logoLib->getIcon('no_logo_should_exists_with_this_name');
+
+        $this->assertEquals(null, $icon);
+    }
+
+    #[Test]
+    public function test_TfalogoLib_loads_empty_collection_when_tfajson_fetching_fails()
+    {
+        Http::preventStrayRequests();
+        Http::fake([
+            CommonDataProvider::TFA_URL => Http::response(HttpRequestTestData::SVG_LOGO_BODY, 200),
+            TfalogoLib::TFA_JSON_URL    => Http::response('not found', 404),
+        ]);
+
+        $this->logoLib = $this->app->make(TfalogoLib::class);
+        $icon          = $this->logoLib->getIcon('service');
+
+        $this->assertNull($icon);
+        Storage::disk('logos')->assertMissing(TfalogoLib::TFA_JSON);
+    }
+}