Update & Complete API controllers tests and Unit tests

This commit is contained in:
Bubka 2023-03-08 17:43:26 +01:00
parent 823acde49d
commit 0a8807d87a
18 changed files with 545 additions and 99 deletions

View File

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

View File

@ -414,9 +414,7 @@ public function fillWithOtpParameters(array $parameters, bool $skipIconFetching
$this->enforceAsSteam(); $this->enforceAsSteam();
} }
$user = is_null($this->user) ? Auth::user() : $this->user; if (! $this->icon && $this->shouldGetOfficialIcon() && ! $skipIconFetching) {
if (! $this->icon && $user->preferences['getOfficialIcons'] && ! $skipIconFetching) {
$this->icon = $this->getDefaultIcon(); $this->icon = $this->getDefaultIcon();
} }
@ -467,10 +465,8 @@ public function fillWithURI(string $uri, bool $isSteamTotp = false, bool $skipIc
if ($this->generator->hasParameter('image')) { if ($this->generator->hasParameter('image')) {
self::setIcon($this->generator->getParameter('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(); $this->icon = $this->getDefaultIcon();
} }
@ -707,9 +703,20 @@ private function storeRemoteImageAsIcon(string $url) : string|null
private function getDefaultIcon() private function getDefaultIcon()
{ {
$logoService = App::make(LogoService::class); $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'];
} }
/** /**

View File

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

View File

@ -12,10 +12,15 @@
class UserControllerTest extends FeatureTestCase class UserControllerTest extends FeatureTestCase
{ {
/** /**
* @var \App\Models\User * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
*/ */
protected $user; protected $user;
private const PREFERENCE_JSON_STRUCTURE = [
'key',
'value',
];
/** /**
* @test * @test
*/ */
@ -35,38 +40,196 @@ public function test_show_existing_user_when_authenticated_returns_success()
->json('GET', '/api/v1/user') ->json('GET', '/api/v1/user')
->assertOk() ->assertOk()
->assertExactJson([ ->assertExactJson([
'name' => $this->user->name, 'name' => $this->user->name,
'id' => $this->user->id, 'id' => $this->user->id,
'email' => $this->user->email, 'email' => $this->user->email,
'is_admin' => $this->user->is_admin,
]); ]);
} }
/** /**
* @test * @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() ->assertOk()
->assertExactJson([ ->assertJsonStructure([
'name' => $this->user->name, '*' => self::PREFERENCE_JSON_STRUCTURE,
]); ]);
} }
/** /**
* @test * @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') $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() ->assertOk()
->assertExactJson([ ->assertExactJson([
'name' => $this->user->name, 'key' => 'showTokenAsDot',
'id' => $this->user->id, 'value' => config('2fauth.preferences.showTokenAsDot'),
'email' => $this->user->email,
]); ]);
} }
/**
* @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);
}
} }

View File

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

View File

@ -13,9 +13,14 @@
class QrCodeControllerTest extends 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 * @test
@ -25,14 +30,9 @@ public function setUp() : void
parent::setUp(); parent::setUp();
$this->user = User::factory()->create(); $this->user = User::factory()->create();
} $this->anotherUser = User::factory()->create();
/** $this->twofaccount = TwoFAccount::factory()->for($this->user)->create([
* @test
*/
public function test_show_qrcode_returns_base64_image()
{
$twofaccount = TwoFAccount::factory()->create([
'otp_type' => 'totp', 'otp_type' => 'totp',
'account' => 'account', 'account' => 'account',
'service' => 'service', 'service' => 'service',
@ -42,9 +42,15 @@ public function test_show_qrcode_returns_base64_image()
'period' => 30, 'period' => 30,
'legacy_uri' => 'otpauth://hotp/service:account?secret=A4GRFHZVRBGY7UIW&issuer=service', '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') $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([ ->assertJsonStructure([
'qrcode', 'qrcode',
]) ])
@ -66,6 +72,19 @@ public function test_show_missing_qrcode_returns_not_found()
]); ]);
} }
/**
* @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 * @test
*/ */

View File

@ -12,20 +12,20 @@
class SettingControllerTest extends 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 = [ private const SETTING_JSON_STRUCTURE = [
'key', 'key',
'value', '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'; private const USER_DEFINED_SETTING = 'mySetting';
@ -41,6 +41,7 @@ public function setUp() : void
parent::setUp(); parent::setUp();
$this->user = User::factory()->create(); $this->user = User::factory()->create();
$this->admin = User::factory()->administrator()->create();
} }
/** /**
@ -48,7 +49,7 @@ public function setUp() : void
*/ */
public function test_index_returns_setting_collection() 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') ->json('GET', '/api/v1/settings')
->assertOk() ->assertOk()
->assertJsonStructure([ ->assertJsonStructure([
@ -59,9 +60,22 @@ public function test_index_returns_setting_collection()
/** /**
* @test * @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') $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) ->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
->assertOk() ->assertOk()
->assertExactJson([ ->assertExactJson([
@ -77,7 +91,7 @@ public function test_show_native_changed_setting_returns_consistent_value()
{ {
Settings::set(self::TWOFAUTH_NATIVE_SETTING, self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE); 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) ->json('GET', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
->assertOk() ->assertOk()
->assertExactJson([ ->assertExactJson([
@ -93,7 +107,7 @@ public function test_show_custom_user_setting_returns_consistent_value()
{ {
Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE); 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) ->json('GET', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
->assertOk() ->assertOk()
->assertExactJson([ ->assertExactJson([
@ -107,7 +121,7 @@ public function test_show_custom_user_setting_returns_consistent_value()
*/ */
public function test_show_missing_setting_returns_not_found() 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') ->json('GET', '/api/v1/settings/missing')
->assertNotFound(); ->assertNotFound();
} }
@ -115,9 +129,22 @@ public function test_show_missing_setting_returns_not_found()
/** /**
* @test * @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') $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', [ ->json('POST', '/api/v1/settings', [
'key' => self::USER_DEFINED_SETTING, 'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE, 'value' => self::USER_DEFINED_SETTING_VALUE,
@ -134,7 +161,7 @@ public function test_store_custom_user_setting_returns_success()
*/ */
public function test_store_invalid_custom_user_setting_returns_validation_error() 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', [ ->json('POST', '/api/v1/settings', [
'key' => null, 'key' => null,
'value' => null, 'value' => null,
@ -149,7 +176,7 @@ public function test_store_existing_custom_user_setting_returns_validation_error
{ {
Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE); 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', [ ->json('POST', '/api/v1/settings', [
'key' => self::USER_DEFINED_SETTING, 'key' => self::USER_DEFINED_SETTING,
'value' => self::USER_DEFINED_SETTING_VALUE, 'value' => self::USER_DEFINED_SETTING_VALUE,
@ -162,7 +189,7 @@ public function test_store_existing_custom_user_setting_returns_validation_error
*/ */
public function test_update_unchanged_native_setting_returns_updated_setting() 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, [ ->json('PUT', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING, [
'value' => self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE, 'value' => self::TWOFAUTH_NATIVE_SETTING_CHANGED_VALUE,
]) ])
@ -180,7 +207,7 @@ public function test_update_custom_user_setting_returns_updated_setting()
{ {
Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE); 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, [ ->json('PUT', '/api/v1/settings/' . self::USER_DEFINED_SETTING, [
'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE, 'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
]) ])
@ -196,7 +223,7 @@ public function test_update_custom_user_setting_returns_updated_setting()
*/ */
public function test_update_missing_user_setting_returns_created_setting() 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, [ ->json('PUT', '/api/v1/settings/' . self::USER_DEFINED_SETTING, [
'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE, 'value' => self::USER_DEFINED_SETTING_CHANGED_VALUE,
]) ])
@ -214,7 +241,7 @@ public function test_destroy_user_setting_returns_success()
{ {
Settings::set(self::USER_DEFINED_SETTING, self::USER_DEFINED_SETTING_VALUE); 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) ->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
->assertNoContent(); ->assertNoContent();
} }
@ -224,7 +251,7 @@ public function test_destroy_user_setting_returns_success()
*/ */
public function test_destroy_native_setting_returns_bad_request() 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) ->json('DELETE', '/api/v1/settings/' . self::TWOFAUTH_NATIVE_SETTING)
->assertStatus(400) ->assertStatus(400)
->assertJsonStructure([ ->assertJsonStructure([
@ -238,8 +265,23 @@ public function test_destroy_native_setting_returns_bad_request()
*/ */
public function test_destroy_missing_user_setting_returns_not_found() 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) ->json('DELETE', '/api/v1/settings/' . self::USER_DEFINED_SETTING)
->assertNotFound(); ->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',
]);
}
} }

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -20,6 +20,6 @@ public function test_retreiving_a_user_returns_a_non_persisted_user_instance()
]); ]);
$this->assertInstanceOf('\App\Models\User', $user); $this->assertInstanceOf('\App\Models\User', $user);
$this->assertEquals(false, $user->exists); $this->assertEquals(true, $user->exists);
} }
} }

View File

@ -2,9 +2,12 @@
namespace Tests\Unit; namespace Tests\Unit;
use App\Events\GroupDeleted;
use App\Events\GroupDeleting; use App\Events\GroupDeleting;
use App\Models\User;
use App\Models\Group; use App\Models\Group;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Tests\ModelTestCase; use Tests\ModelTestCase;
/** /**
@ -23,18 +26,33 @@ public function test_model_configuration()
['created_at', 'updated_at'], ['created_at', 'updated_at'],
['*'], ['*'],
[], [],
['id' => 'int', 'twofaccounts_count' => 'integer'], ['id' => 'int', 'twofaccounts_count' => 'integer'],
['deleting' => GroupDeleting::class] [
'deleting' => GroupDeleting::class,
'deleted' => GroupDeleted::class
]
); );
} }
/** /**
* @test * @test
*/ */
public function test_groups_relation() public function test_twofaccounts_relation()
{ {
$group = new Group(); $group = new Group();
$accounts = $group->twofaccounts(); $accounts = $group->twofaccounts();
$this->assertHasManyRelation($accounts, $group, new TwoFAccount()); $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());
}
} }

View File

@ -19,7 +19,7 @@ class CleanIconStorageTest extends TestCase
/** /**
* @test * @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 = $this->mock(SettingService::class, function (MockInterface $settingService) {
$settingService->shouldReceive('get') $settingService->shouldReceive('get')

View File

@ -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
);
}
}

View File

@ -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
);
}
}

View File

@ -7,6 +7,8 @@
use App\Exceptions\UnsupportedMigrationException; use App\Exceptions\UnsupportedMigrationException;
use App\Factories\MigratorFactory; use App\Factories\MigratorFactory;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Models\User;
use App\Services\LogoService;
use App\Services\Migrators\AegisMigrator; use App\Services\Migrators\AegisMigrator;
use App\Services\Migrators\GoogleAuthMigrator; use App\Services\Migrators\GoogleAuthMigrator;
use App\Services\Migrators\Migrator; use App\Services\Migrators\Migrator;
@ -57,7 +59,7 @@ class MigratorTest extends TestCase
/** /**
* App\Models\TwoFAccount $GAuthTotpBisTwofaccount * App\Models\TwoFAccount $GAuthTotpBisTwofaccount
*/ */
protected $GAuthTotpBisTwofaccount; protected $GAuthTotpBisTwofaccount, $fakeTwofaccount;
public function setUp() : void public function setUp() : void
{ {
@ -67,12 +69,15 @@ public function setUp() : void
$settingService->allows() $settingService->allows()
->get('useEncryption') ->get('useEncryption')
->andReturn(false); ->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 = new TwoFAccount;
$this->totpTwofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG; $this->totpTwofaccount->legacy_uri = OtpTestData::TOTP_FULL_CUSTOM_URI_NO_IMG;
$this->totpTwofaccount->service = OtpTestData::SERVICE; $this->totpTwofaccount->service = OtpTestData::SERVICE;

View File

@ -6,6 +6,7 @@
use App\Helpers\Helpers; use App\Helpers\Helpers;
use App\Models\TwoFAccount; use App\Models\TwoFAccount;
use App\Services\SettingService; use App\Services\SettingService;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Mockery\MockInterface; use Mockery\MockInterface;
use Tests\ModelTestCase; use Tests\ModelTestCase;
@ -138,4 +139,16 @@ public function test_secret_is_uppercased_and_padded_at_setup()
$this->assertEquals('YYYY====', $twofaccount->secret); $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());
}
} }

View File

@ -2,6 +2,8 @@
namespace Tests\Unit; namespace Tests\Unit;
use App\Models\Group;
use App\Models\TwoFAccount;
use App\Models\User; use App\Models\User;
use Tests\ModelTestCase; use Tests\ModelTestCase;
@ -20,7 +22,13 @@ public function test_model_configuration()
['password', 'remember_token'], ['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 @@ public function test_email_is_set_lowercased()
$this->assertEquals(strtolower('UPPERCASE@example.COM'), $user->email); $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());
}
} }