浏览代码

Update & Complete API controllers tests and Unit tests

Bubka 2 年之前
父节点
当前提交
0a8807d87a

+ 5 - 9
app/Listeners/ReleaseRadar.php

@@ -8,20 +8,14 @@ use Illuminate\Support\Facades\Log;
 
 class ReleaseRadar
 {
-    /**
-     * @var ReleaseRadarService
-     */
-    protected $releaseRadar;
-
     /**
      * Create the event listener.
      *
-     * @param  \App\Services\ReleaseRadarService  $releaseRadar
      * @return void
      */
-    public function __construct(ReleaseRadarService $releaseRadar)
+    public function __construct()
     {
-        $this->releaseRadar = $releaseRadar;
+        //
     }
 
     /**
@@ -32,7 +26,9 @@ class ReleaseRadar
      */
     public function handle(ScanForNewReleaseCalled $event)
     {
-        $this->releaseRadar->scheduledScan();
+        $releaseRadarService = app()->make(ReleaseRadarService::class);
+        $releaseRadarService::scheduledScan();
+        
         Log::info('Scheduled release scan complete');
     }
 }

+ 15 - 8
app/Models/TwoFAccount.php

@@ -414,9 +414,7 @@ class TwoFAccount extends Model implements Sortable
             $this->enforceAsSteam();
         }
 
-        $user = is_null($this->user) ? Auth::user() : $this->user;
-
-        if (! $this->icon && $user->preferences['getOfficialIcons'] && ! $skipIconFetching) {
+        if (! $this->icon && $this->shouldGetOfficialIcon() && ! $skipIconFetching) {
             $this->icon = $this->getDefaultIcon();
         }
 
@@ -467,10 +465,8 @@ class TwoFAccount extends Model implements Sortable
         if ($this->generator->hasParameter('image')) {
             self::setIcon($this->generator->getParameter('image'));
         }
-
-        $user = is_null($this->user) ? Auth::user() : $this->user;
         
-        if (! $this->icon && $user->preferences['getOfficialIcons'] && ! $skipIconFetching) {
+        if (! $this->icon && $this->shouldGetOfficialIcon() && ! $skipIconFetching) {
             $this->icon = $this->getDefaultIcon();
         }
 
@@ -707,9 +703,20 @@ class TwoFAccount extends Model implements Sortable
     private function getDefaultIcon()
     {
         $logoService = App::make(LogoService::class);
-        $user = is_null($this->user) ? Auth::user() : $this->user;
 
-        return $user->preferences['getOfficialIcons'] ? $logoService->getIcon($this->service) : null;
+        return $this->shouldGetOfficialIcon() ? $logoService->getIcon($this->service) : null;
+    }
+
+    /**
+     * Tells if an official icon should be fetched
+     * 
+     * @return bool
+     */
+    private function shouldGetOfficialIcon() : bool
+    {
+        return is_null($this->user)
+            ? (bool) config('2fauth.preferences.getOfficialIcons')
+            : (bool) $this->user->preferences['getOfficialIcons'];
     }
 
     /**

+ 7 - 7
app/Services/ReleaseRadarService.php

@@ -14,10 +14,10 @@ class ReleaseRadarService
      *
      * @return void
      */
-    public function scheduledScan() : void
+    public static function scheduledScan() : void
     {
         if ((Settings::get('lastRadarScan') + (60 * 60 * 24 * 7)) < time()) {
-            $this->newRelease();
+            self::newRelease();
         }
     }
 
@@ -26,9 +26,9 @@ class ReleaseRadarService
      *
      * @return false|string False if no new release, the new release number otherwise
      */
-    public function manualScan() : false|string
+    public static function manualScan() : false|string
     {
-        return $this->newRelease();
+        return self::newRelease();
     }
 
     /**
@@ -36,9 +36,9 @@ class ReleaseRadarService
      *
      * @return false|string False if no new release, the new release number otherwise
      */
-    protected function newRelease() : false|string
+    protected static function newRelease() : false|string
     {
-        if ($latestReleaseData = json_decode($this->getLatestReleaseData())) {
+        if ($latestReleaseData = json_decode(self::getLatestReleaseData())) {
             $githubVersion    = Helpers::cleanVersionNumber($latestReleaseData->tag_name);
             $installedVersion = Helpers::cleanVersionNumber(config('2fauth.version'));
 
@@ -61,7 +61,7 @@ class ReleaseRadarService
      *
      * @return string|null
      */
-    protected function getLatestReleaseData() : string|null
+    protected static function getLatestReleaseData() : string|null
     {
         try {
             $response = Http::retry(3, 100)

+ 177 - 14
tests/Api/v1/Controllers/Auth/UserControllerTest.php

@@ -12,10 +12,15 @@ use Tests\FeatureTestCase;
 class UserControllerTest extends FeatureTestCase
 {
     /**
-     * @var \App\Models\User
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
      */
     protected $user;
 
+    private const PREFERENCE_JSON_STRUCTURE = [
+        'key',
+        'value',
+    ];
+
     /**
      * @test
      */
@@ -35,38 +40,196 @@ class UserControllerTest extends FeatureTestCase
             ->json('GET', '/api/v1/user')
             ->assertOk()
             ->assertExactJson([
-                'name'  => $this->user->name,
-                'id'    => $this->user->id,
-                'email' => $this->user->email,
+                'name'     => $this->user->name,
+                'id'       => $this->user->id,
+                'email'    => $this->user->email,
+                'is_admin' => $this->user->is_admin,
             ]);
     }
 
     /**
      * @test
      */
-    public function test_show_existing_user_when_anonymous_returns_success()
+    public function test_allPreferences_returns_consistent_json_structure()
     {
-        $response = $this->json('GET', '/api/v1/user/name')
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/user/preferences')
             ->assertOk()
-            ->assertExactJson([
-                'name' => $this->user->name,
+            ->assertJsonStructure([
+                '*' => self::PREFERENCE_JSON_STRUCTURE,
             ]);
     }
 
     /**
      * @test
      */
-    public function test_show_missing_user_returns_success_with_null_name()
+    public function test_allPreferences_returns_preferences_with_default_values()
     {
-        User::destroy($this->user->id);
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/user/preferences')
+            ->assertJsonCount(count(config('2fauth.preferences')), $key = null);
+
+        foreach (config('2fauth.preferences') as $pref => $value) {
+            $response->assertJsonFragment([
+                'key' => $pref,
+                'value' => $value,
+            ]);
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function test_allPreferences_returns_preferences_with_user_values()
+    {
+        $userPrefs = [
+            'showTokenAsDot' => true,
+            'closeOtpOnCopy' => true,
+            'copyOtpOnDisplay' => true,
+            'useBasicQrcodeReader' => true,
+            'displayMode' => 'grid',
+            'showAccountsIcons' => false,
+            'kickUserAfter' => 5,
+            'activeGroup' => 1,
+            'rememberActiveGroup' => false,
+            'defaultGroup' => 1,
+            'defaultCaptureMode' => 'advancedForm',
+            'useDirectCapture' => true,
+            'useWebauthnAsDefault' => true,
+            'useWebauthnOnly' => true,
+            'getOfficialIcons' => false,
+            'theme' => 'dark',
+            'formatPassword' => false,
+            'formatPasswordBy' => 1,
+            'lang' => 'fr',
+        ];
+
+        $this->user['preferences->showTokenAsDot'] = $userPrefs['showTokenAsDot'];
+        $this->user['preferences->closeOtpOnCopy'] = $userPrefs['closeOtpOnCopy'];
+        $this->user['preferences->copyOtpOnDisplay'] = $userPrefs['copyOtpOnDisplay'];
+        $this->user['preferences->useBasicQrcodeReader'] = $userPrefs['useBasicQrcodeReader'];
+        $this->user['preferences->displayMode'] = $userPrefs['displayMode'];
+        $this->user['preferences->showAccountsIcons'] = $userPrefs['showAccountsIcons'];
+        $this->user['preferences->kickUserAfter'] = $userPrefs['kickUserAfter'];
+        $this->user['preferences->activeGroup'] = $userPrefs['activeGroup'];
+        $this->user['preferences->rememberActiveGroup'] = $userPrefs['rememberActiveGroup'];
+        $this->user['preferences->defaultGroup'] = $userPrefs['defaultGroup'];
+        $this->user['preferences->defaultCaptureMode'] = $userPrefs['defaultCaptureMode'];
+        $this->user['preferences->useDirectCapture'] = $userPrefs['useDirectCapture'];
+        $this->user['preferences->useWebauthnAsDefault'] = $userPrefs['useWebauthnAsDefault'];
+        $this->user['preferences->useWebauthnOnly'] = $userPrefs['useWebauthnOnly'];
+        $this->user['preferences->getOfficialIcons'] = $userPrefs['getOfficialIcons'];
+        $this->user['preferences->theme'] = $userPrefs['theme'];
+        $this->user['preferences->formatPassword'] = $userPrefs['formatPassword'];
+        $this->user['preferences->formatPasswordBy'] = $userPrefs['formatPasswordBy'];
+        $this->user['preferences->lang'] = $userPrefs['lang'];
+        $this->user->save();
 
         $response = $this->actingAs($this->user, 'api-guard')
-            ->json('GET', '/api/v1/user')
+            ->json('GET', '/api/v1/user/preferences')
+            ->assertJsonCount(count(config('2fauth.preferences')), $key = null);
+
+        foreach ($userPrefs as $pref => $value) {
+            $response->assertJsonFragment([
+                'key' => $pref,
+                'value' => $value,
+            ]);
+        }
+    }
+
+    /**
+     * @test
+     */
+    public function test_showPreference_returns_preference_with_default_value()
+    {
+        /**
+         * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
+         */
+        $this->user = User::factory()->create();
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/user/preferences/showTokenAsDot')
             ->assertOk()
             ->assertExactJson([
-                'name'  => $this->user->name,
-                'id'    => $this->user->id,
-                'email' => $this->user->email,
+                'key' => 'showTokenAsDot',
+                'value' => config('2fauth.preferences.showTokenAsDot'),
+            ]);
+    }
+
+    /**
+     * @test
+     */
+    public function test_showPreference_returns_preference_with_custom_value()
+    {
+        $showTokenAsDot = ! config('2fauth.preferences.showTokenAsDot');
+        $this->user['preferences->showTokenAsDot'] = $showTokenAsDot;
+        $this->user->save();
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/user/preferences/showTokenAsDot')
+            ->assertJsonFragment([
+                'key' => 'showTokenAsDot',
+                'value' => $showTokenAsDot,
             ]);
     }
+
+    /**
+     * @test
+     */
+    public function test_showPreference_for_missing_preference_returns_not_found()
+    {
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/user/preferences/unknown')
+            ->assertNotFound();
+    }
+
+    /**
+     * @test
+     */
+    public function test_setPreference_returns_updated_preference()
+    {
+        /**
+         * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
+         */
+        $this->user = User::factory()->create();
+
+        $showTokenAsDot = ! config('2fauth.preferences.showTokenAsDot');
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('PUT', '/api/v1/user/preferences/showTokenAsDot', [
+                'key'   => 'showTokenAsDot',
+                'value' => $showTokenAsDot,
+            ])
+            ->assertCreated()
+            ->assertExactJson([
+                'key' => 'showTokenAsDot',
+                'value' => $showTokenAsDot,
+            ]);
+    }
+
+    /**
+     * @test
+     */
+    public function test_setPreference_for_missing_preference_returns_not_found()
+    {
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('PUT', '/api/v1/user/preferences/unknown', [
+                'key'   => 'showTokenAsDot',
+                'value' => true,
+            ])
+            ->assertNotFound();
+    }
+
+    /**
+     * @test
+     */
+    public function test_setPreference_with_invalid_data_returns_validation_error()
+    {
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('PUT', '/api/v1/user/preferences/showTokenAsDot', [
+                'key'   => 'showTokenAsDot',
+                'value' => null,
+            ])
+            ->assertStatus(422);
+    }
 }

+ 55 - 16
tests/Api/v1/Controllers/IconControllerTest.php

@@ -2,7 +2,8 @@
 
 namespace Tests\Api\v1\Controllers;
 
-use Illuminate\Foundation\Testing\WithoutMiddleware;
+use App\Models\TwoFAccount;
+use App\Models\User;
 use Illuminate\Http\UploadedFile;
 use Tests\FeatureTestCase;
 
@@ -11,7 +12,20 @@ use Tests\FeatureTestCase;
  */
 class IconControllerTest extends FeatureTestCase
 {
-    use WithoutMiddleware;
+    /**
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
+     */
+    protected $user;
+
+    /**
+     *
+     */
+    public function setUp() : void
+    {
+        parent::setUp();
+
+        $this->user = User::factory()->create();
+    }
 
     /**
      * @test
@@ -20,9 +34,10 @@ class IconControllerTest extends FeatureTestCase
     {
         $file = UploadedFile::fake()->image('testIcon.jpg');
 
-        $response = $this->json('POST', '/api/v1/icons', [
-            'icon' => $file,
-        ])
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons', [
+                'icon' => $file,
+            ])
             ->assertCreated()
             ->assertJsonStructure([
                 'filename',
@@ -34,9 +49,10 @@ class IconControllerTest extends FeatureTestCase
      */
     public function test_upload_with_invalid_data_returns_validation_error()
     {
-        $response = $this->json('POST', '/api/v1/icons', [
-            'icon' => null,
-        ])
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons', [
+                'icon' => null,
+            ])
             ->assertStatus(422);
     }
 
@@ -45,9 +61,10 @@ class IconControllerTest extends FeatureTestCase
      */
     public function test_fetch_logo_returns_filename()
     {
-        $response = $this->json('POST', '/api/v1/icons/default', [
-            'service' => 'twitter',
-        ])
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons/default', [
+                'service' => 'twitter',
+            ])
             ->assertStatus(201)
             ->assertJsonStructure([
                 'filename',
@@ -59,9 +76,10 @@ class IconControllerTest extends FeatureTestCase
      */
     public function test_fetch_unknown_logo_returns_nothing()
     {
-        $response = $this->json('POST', '/api/v1/icons/default', [
-            'service' => 'unknown_company',
-        ])
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('POST', '/api/v1/icons/default', [
+                'service' => 'unknown_company',
+            ])
             ->assertNoContent();
     }
 
@@ -70,7 +88,8 @@ class IconControllerTest extends FeatureTestCase
      */
     public function test_delete_icon_returns_success()
     {
-        $response = $this->json('DELETE', '/api/v1/icons/testIcon.jpg')
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('DELETE', '/api/v1/icons/testIcon.jpg')
             ->assertNoContent(204);
     }
 
@@ -79,7 +98,27 @@ class IconControllerTest extends FeatureTestCase
      */
     public function test_delete_invalid_icon_returns_success()
     {
-        $response = $this->json('DELETE', '/api/v1/icons/null')
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('DELETE', '/api/v1/icons/null')
             ->assertNoContent(204);
     }
+
+    /**
+     * @test
+     */
+    public function test_delete_icon_of_another_user_is_forbidden()
+    {
+        $anotherUser = User::factory()->create();
+
+        TwoFAccount::factory()->for($anotherUser)->create([
+            'icon' => 'testIcon.jpg',
+        ]);
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('DELETE', '/api/v1/icons/testIcon.jpg')
+            ->assertForbidden()
+            ->assertJsonStructure([
+                'message',
+            ]);
+    }
 }

+ 29 - 10
tests/Api/v1/Controllers/QrCodeControllerTest.php

@@ -13,9 +13,14 @@ use Tests\FeatureTestCase;
 class QrCodeControllerTest extends FeatureTestCase
 {
     /**
-     * @var \App\Models\User
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
      */
-    protected $user;
+    protected $user, $anotherUser;
+
+    /**
+     * @var App\Models\TwoFAccount
+     */
+    protected $twofaccount;
 
     /**
      * @test
@@ -25,14 +30,9 @@ class QrCodeControllerTest extends FeatureTestCase
         parent::setUp();
 
         $this->user = User::factory()->create();
-    }
+        $this->anotherUser = User::factory()->create();
 
-    /**
-     * @test
-     */
-    public function test_show_qrcode_returns_base64_image()
-    {
-        $twofaccount = TwoFAccount::factory()->create([
+        $this->twofaccount = TwoFAccount::factory()->for($this->user)->create([
             'otp_type'   => 'totp',
             'account'    => 'account',
             'service'    => 'service',
@@ -42,9 +42,15 @@ class QrCodeControllerTest extends FeatureTestCase
             'period'     => 30,
             'legacy_uri' => 'otpauth://hotp/service:account?secret=A4GRFHZVRBGY7UIW&issuer=service',
         ]);
+    }
 
+    /**
+     * @test
+     */
+    public function test_show_qrcode_returns_base64_image()
+    {
         $response = $this->actingAs($this->user, 'api-guard')
-            ->json('GET', '/api/v1/twofaccounts/' . $twofaccount->id . '/qrcode')
+            ->json('GET', '/api/v1/twofaccounts/' . $this->twofaccount->id . '/qrcode')
             ->assertJsonStructure([
                 'qrcode',
             ])
@@ -66,6 +72,19 @@ class QrCodeControllerTest extends FeatureTestCase
             ]);
     }
 
+    /**
+     * @test
+     */
+    public function test_show_qrcode_of_another_user_is_forbidden()
+    {
+        $response = $this->actingAs($this->anotherUser, 'api-guard')
+            ->json('GET', '/api/v1/twofaccounts/' . $this->twofaccount->id . '/qrcode')
+            ->assertForbidden()
+            ->assertJsonStructure([
+                'message',
+            ]);
+    }
+
     /**
      * @test
      */

+ 61 - 19
tests/Api/v1/Controllers/SettingControllerTest.php

@@ -12,20 +12,20 @@ use Tests\FeatureTestCase;
 class SettingControllerTest extends FeatureTestCase
 {
     /**
-     * @var \App\Models\User
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
      */
-    protected $user;
+    protected $user, $admin;
 
     private const SETTING_JSON_STRUCTURE = [
         'key',
         'value',
     ];
 
-    private const TWOFAUTH_NATIVE_SETTING = 'showTokenAsDot';
+    private const TWOFAUTH_NATIVE_SETTING = 'checkForUpdate';
 
-    private const TWOFAUTH_NATIVE_SETTING_DEFAULT_VALUE = false;
+    private const TWOFAUTH_NATIVE_SETTING_DEFAULT_VALUE = true;
 
-    private const TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE = true;
+    private const TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE = false;
 
     private const USER_DEFINED_SETTING = 'mySetting';
 
@@ -41,6 +41,7 @@ class SettingControllerTest extends FeatureTestCase
         parent::setUp();
 
         $this->user = User::factory()->create();
+        $this->admin = User::factory()->administrator()->create();
     }
 
     /**
@@ -48,7 +49,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_index_returns_setting_collection()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('GET', '/api/v1/settings')
             ->assertOk()
             ->assertJsonStructure([
@@ -59,9 +60,22 @@ class SettingControllerTest extends FeatureTestCase
     /**
      * @test
      */
-    public function test_show_native_unchanged_setting_returns_consistent_value()
+    public function test_index_is_forbidden_to_users()
     {
         $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/settings')
+            ->assertForbidden()
+            ->assertJsonStructure([
+                'message',
+            ]);
+    }
+
+    /**
+     * @test
+     */
+    public function test_show_native_unchanged_setting_returns_consistent_value()
+    {
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
             ->assertOk()
             ->assertExactJson([
@@ -77,7 +91,7 @@ class SettingControllerTest extends FeatureTestCase
     {
         Settings::set(self::TWOFAUTH_NATIVE_SETTING, self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE);
 
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
             ->assertOk()
             ->assertExactJson([
@@ -93,7 +107,7 @@ class SettingControllerTest extends FeatureTestCase
     {
         Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE);
 
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('GET', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
             ->assertOk()
             ->assertExactJson([
@@ -107,7 +121,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_show_missing_setting_returns_not_found()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('GET', '/api/v1/settings/missing')
             ->assertNotFound();
     }
@@ -115,9 +129,22 @@ class SettingControllerTest extends FeatureTestCase
     /**
      * @test
      */
-    public function test_store_custom_user_setting_returns_success()
+    public function test_show_setting_is_forbidden_to_users()
     {
         $response = $this->actingAs($this->user, 'api-guard')
+            ->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
+            ->assertForbidden()
+            ->assertJsonStructure([
+                'message',
+            ]);
+    }
+
+    /**
+     * @test
+     */
+    public function test_store_custom_user_setting_returns_success()
+    {
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('POST', '/api/v1/settings', [
                 'key'   => self::USER_DEFINED_SETTING,
                 'value' => self::USER_DEFINED_SETTING_VALUE,
@@ -134,7 +161,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_store_invalid_custom_user_setting_returns_validation_error()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('POST', '/api/v1/settings', [
                 'key'   => null,
                 'value' => null,
@@ -149,7 +176,7 @@ class SettingControllerTest extends FeatureTestCase
     {
         Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE);
 
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('POST', '/api/v1/settings', [
                 'key'   => self::USER_DEFINED_SETTING,
                 'value' => self::USER_DEFINED_SETTING_VALUE,
@@ -162,7 +189,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_update_unchanged_native_setting_returns_updated_setting()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('PUT', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING, [
                 'value' => self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE,
             ])
@@ -180,7 +207,7 @@ class SettingControllerTest extends FeatureTestCase
     {
         Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE);
 
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('PUT', '/api/v1/settings/' . self::USER_DEFINED_SETTING, [
                 'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
             ])
@@ -196,7 +223,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_update_missing_user_setting_returns_created_setting()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('PUT', '/api/v1/settings/' . self::USER_DEFINED_SETTING, [
                 'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
             ])
@@ -214,7 +241,7 @@ class SettingControllerTest extends FeatureTestCase
     {
         Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE);
 
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
             ->assertNoContent();
     }
@@ -224,7 +251,7 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_destroy_native_setting_returns_bad_request()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('DELETE', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
             ->assertStatus(400)
             ->assertJsonStructure([
@@ -238,8 +265,23 @@ class SettingControllerTest extends FeatureTestCase
      */
     public function test_destroy_missing_user_setting_returns_not_found()
     {
-        $response = $this->actingAs($this->user, 'api-guard')
+        $response = $this->actingAs($this->admin, 'api-guard')
             ->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
             ->assertNotFound();
     }
+
+    /**
+     * @test
+     */
+    public function test_destroy_is_forbidden_to_users()
+    {
+        Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE);
+
+        $response = $this->actingAs($this->user, 'api-guard')
+            ->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
+            ->assertForbidden()
+            ->assertJsonStructure([
+                'message',
+            ]);
+    }
 }

+ 24 - 4
tests/Api/v1/Requests/GroupStoreRequestTest.php

@@ -4,9 +4,11 @@ namespace Tests\Api\v1\Requests;
 
 use App\Api\v1\Requests\GroupStoreRequest;
 use App\Models\Group;
+use App\Models\User;
 use Illuminate\Foundation\Testing\WithoutMiddleware;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Validator;
+use Mockery;
 use Tests\FeatureTestCase;
 
 /**
@@ -15,9 +17,23 @@ use Tests\FeatureTestCase;
 class GroupStoreRequestTest extends FeatureTestCase
 {
     use WithoutMiddleware;
+    /**
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
+     */
+    protected $user;
 
     protected String $uniqueGroupName = 'MyGroup';
 
+    /**
+     * @test
+     */
+    public function setUp() : void
+    {
+        parent::setUp();
+
+        $this->user = User::factory()->create();
+    }
+
     /**
      * @test
      */
@@ -37,7 +53,10 @@ class GroupStoreRequestTest extends FeatureTestCase
      */
     public function test_valid_data(array $data) : void
     {
-        $request   = new GroupStoreRequest();
+        $request = Mockery::mock(GroupStoreRequest::class)->makePartial();
+        $request->shouldReceive('user')
+            ->andReturn($this->user);
+
         $validator = Validator::make($data, $request->rules());
 
         $this->assertFalse($validator->fails());
@@ -60,13 +79,14 @@ class GroupStoreRequestTest extends FeatureTestCase
      */
     public function test_invalid_data(array $data) : void
     {
-        $group = new Group([
+        $group = Group::factory()->for($this->user)->create([
             'name' => $this->uniqueGroupName,
         ]);
 
-        $group->save();
+        $request = Mockery::mock(GroupStoreRequest::class)->makePartial();
+        $request->shouldReceive('user')
+            ->andReturn($this->user);
 
-        $request   = new GroupStoreRequest();
         $validator = Validator::make($data, $request->rules());
 
         $this->assertTrue($validator->fails());

+ 1 - 1
tests/Feature/Http/Auth/LoginTest.php

@@ -17,7 +17,7 @@ use Tests\FeatureTestCase;
 class LoginTest extends FeatureTestCase
 {
     /**
-     * @var \App\Models\User
+     * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
      */
     protected $user;
 

+ 24 - 0
tests/Unit/Events/GroupDeletedTest.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Tests\Unit\Events;
+
+use App\Events\GroupDeleted;
+use App\Models\Group;
+use Tests\TestCase;
+
+/**
+ * @covers \App\Events\GroupDeleted
+ */
+class GroupDeletedTest extends TestCase
+{
+    /**
+     * @test
+     */
+    public function test_event_constructor()
+    {
+        $group = Group::factory()->make();
+        $event = new GroupDeleted($group);
+
+        $this->assertSame($group, $event->group);
+    }
+}

+ 1 - 1
tests/Unit/Extensions/RemoteUserProviderTest.php

@@ -20,6 +20,6 @@ class RemoteUserProviderTest extends TestCase
         ]);
 
         $this->assertInstanceOf('\App\Models\User', $user);
-        $this->assertEquals(false, $user->exists);
+        $this->assertEquals(true, $user->exists);
     }
 }

+ 21 - 3
tests/Unit/GroupModelTest.php

@@ -2,9 +2,12 @@
 
 namespace Tests\Unit;
 
+use App\Events\GroupDeleted;
 use App\Events\GroupDeleting;
+use App\Models\User;
 use App\Models\Group;
 use App\Models\TwoFAccount;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Tests\ModelTestCase;
 
 /**
@@ -23,18 +26,33 @@ class GroupModelTest extends ModelTestCase
             ['created_at', 'updated_at'],
             ['*'],
             [],
-            ['id'       => 'int', 'twofaccounts_count' => 'integer'],
-            ['deleting' => GroupDeleting::class]
+            ['id' => 'int', 'twofaccounts_count' => 'integer'],
+            [
+                'deleting' => GroupDeleting::class,
+                'deleted' => GroupDeleted::class
+            ]
         );
     }
 
     /**
      * @test
      */
-    public function test_groups_relation()
+    public function test_twofaccounts_relation()
     {
         $group    = new Group();
         $accounts = $group->twofaccounts();
         $this->assertHasManyRelation($accounts, $group, new TwoFAccount());
     }
+
+    /**
+     * @test
+     */
+    public function test_user_relation()
+    {
+        $model = new Group;
+        $relation = $model->user();
+
+        $this->assertInstanceOf(BelongsTo::class, $relation);
+        $this->assertEquals('user_id', $relation->getForeignKeyName());
+    }
 }

+ 1 - 1
tests/Unit/Listeners/CleanIconStorageTest.php

@@ -19,7 +19,7 @@ class CleanIconStorageTest extends TestCase
     /**
      * @test
      */
-    public function test_it_deletes_icon_file_on_twofaccount_deletion()
+    public function test_it_deletes_icon_file_using_storage_facade()
     {
         $settingService = $this->mock(SettingService::class, function (MockInterface $settingService) {
             $settingService->shouldReceive('get')

+ 44 - 0
tests/Unit/Listeners/ReleaseRadarTest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Tests\Unit\Listeners;
+
+use App\Events\ScanForNewReleaseCalled;
+use App\Listeners\ReleaseRadar;
+use App\Services\ReleaseRadarService;
+use Illuminate\Support\Facades\Event;
+use Mockery\MockInterface;
+use Tests\TestCase;
+
+/**
+ * @covers \App\Listeners\ReleaseRadar
+ */
+class ReleaseRadarTest extends TestCase
+{
+    /**
+     * @test
+     */
+    public function test_it_starts_release_scan()
+    {
+        $this->mock(ReleaseRadarService::class, function (MockInterface $releaseRadarService) {
+            $releaseRadarService->shouldReceive('scheduledScan');
+        });
+
+        $event       = new ScanForNewReleaseCalled();
+        $listener    = new ReleaseRadar();
+
+        $this->assertNull($listener->handle($event));
+    }
+
+    /**
+     * @test
+     */
+    public function test_ReleaseRadar_listen_to_ScanForNewReleaseCalled_event()
+    {
+        Event::fake();
+
+        Event::assertListening(
+            ScanForNewReleaseCalled::class,
+            ReleaseRadar::class
+        );
+    }
+}

+ 28 - 0
tests/Unit/Listeners/ResetUsersPreferenceTest.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace Tests\Unit\Listeners;
+
+use App\Events\GroupDeleted;
+use App\Listeners\ResetUsersPreference;
+use Illuminate\Support\Facades\Event;
+use Mockery\MockInterface;
+use Tests\TestCase;
+
+/**
+ * @covers \App\Listeners\ResetUsersPreference
+ */
+class ResetUsersPreferenceTest extends TestCase
+{
+    /**
+     * @test
+     */
+    public function test_ResetUsersPreference_listen_to_GroupDeleted_event()
+    {
+        Event::fake();
+
+        Event::assertListening(
+            GroupDeleted::class,
+            ResetUsersPreference::class
+        );
+    }
+}

+ 9 - 4
tests/Unit/MigratorTest.php

@@ -7,6 +7,8 @@ use App\Exceptions\InvalidMigrationDataException;
 use App\Exceptions\UnsupportedMigrationException;
 use App\Factories\MigratorFactory;
 use App\Models\TwoFAccount;
+use App\Models\User;
+use App\Services\LogoService;
 use App\Services\Migrators\AegisMigrator;
 use App\Services\Migrators\GoogleAuthMigrator;
 use App\Services\Migrators\Migrator;
@@ -57,7 +59,7 @@ class MigratorTest extends TestCase
     /**
      * App\Models\TwoFAccount $GAuthTotpBisTwofaccount
      */
-    protected $GAuthTotpBisTwofaccount;
+    protected $GAuthTotpBisTwofaccount, $fakeTwofaccount;
 
     public function setUp() : void
     {
@@ -67,11 +69,14 @@ class MigratorTest extends TestCase
             $settingService->allows()
                 ->get('useEncryption')
                 ->andReturn(false);
+        });
 
-            $settingService->allows()
-                ->get('getOfficialIcons')
-                ->andReturn(false);
+        $this->mock(LogoService::class, function (MockInterface $logoService) {
+            $logoService->allows([
+                'getIcon' => null,
+            ]);
         });
+        
 
         $this->totpTwofaccount             = new TwoFAccount;
         $this->totpTwofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG;

+ 13 - 0
tests/Unit/TwoFAccountModelTest.php

@@ -6,6 +6,7 @@ use App\Events\TwoFAccountDeleted;
 use App\Helpers\Helpers;
 use App\Models\TwoFAccount;
 use App\Services\SettingService;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use Illuminate\Support\Facades\Crypt;
 use Mockery\MockInterface;
 use Tests\ModelTestCase;
@@ -138,4 +139,16 @@ class TwoFAccountModelTest extends ModelTestCase
 
         $this->assertEquals('YYYY====', $twofaccount->secret);
     }
+
+    /**
+     * @test
+     */
+    public function test_user_relation()
+    {
+        $model = new TwoFAccount();
+        $relation = $model->user();
+
+        $this->assertInstanceOf(BelongsTo::class, $relation);
+        $this->assertEquals('user_id', $relation->getForeignKeyName());
+    }
 }

+ 29 - 1
tests/Unit/UserModelTest.php

@@ -2,6 +2,8 @@
 
 namespace Tests\Unit;
 
+use App\Models\Group;
+use App\Models\TwoFAccount;
 use App\Models\User;
 use Tests\ModelTestCase;
 
@@ -20,7 +22,13 @@ class UserModelTest extends ModelTestCase
             ['password', 'remember_token'],
             ['*'],
             [],
-            ['id' => 'int', 'email_verified_at' => 'datetime']
+            [
+                'id' => 'int',
+                'email_verified_at'  => 'datetime',
+                'is_admin'           => 'boolean',
+                'twofaccounts_count' => 'integer',
+                'groups_count'       => 'integer',
+            ]
         );
     }
 
@@ -35,4 +43,24 @@ class UserModelTest extends ModelTestCase
 
         $this->assertEquals(strtolower('UPPERCASE@example.COM'), $user->email);
     }
+
+    /**
+     * @test
+     */
+    public function test_twofaccounts_relation()
+    {
+        $user    = new User();
+        $accounts = $user->twofaccounts();
+        $this->assertHasManyRelation($accounts, $user, new TwoFAccount());
+    }
+
+    /**
+     * @test
+     */
+    public function test_groups_relation()
+    {
+        $user   = new User();
+        $groups = $user->groups();
+        $this->assertHasManyRelation($groups, $user, new Group());
+    }
 }