mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-11-25 09:44:04 +01:00
Enhance test coverage & Fix small issues & Refactoring
This commit is contained in:
parent
413e2c4ba9
commit
0f1372e8bd
@ -42,10 +42,6 @@ public function index(TwoFAccountIndexRequest $request)
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
// if ($request->has('withOtp')) {
|
||||
// $request->merge(['at' => time()]);
|
||||
// }
|
||||
|
||||
return Arr::has($validated, 'ids')
|
||||
? new TwoFAccountCollection($request->user()->twofaccounts()->whereIn('id', Helpers::commaSeparatedToArray($validated['ids']))->get()->sortBy('order_column'))
|
||||
: new TwoFAccountCollection($request->user()->twofaccounts->sortBy('order_column'));
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use App\Api\v1\Requests\UserManagerPromoteRequest;
|
||||
use App\Api\v1\Requests\UserManagerStoreRequest;
|
||||
use App\Api\v1\Resources\UserAuthentication;
|
||||
use App\Api\v1\Resources\UserAuthenticationResource;
|
||||
use App\Api\v1\Resources\UserManagerResource;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
@ -231,7 +231,7 @@ public function authentications(Request $request, User $user)
|
||||
$authentications = $request->has('period') ? $user->authenticationsByPeriod($validated['period']) : $user->authentications;
|
||||
$authentications = $request->has('limit') ? $authentications->take($validated['limit']) : $authentications;
|
||||
|
||||
return UserAuthentication::collection($authentications);
|
||||
return UserAuthenticationResource::collection($authentications);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ public function authorize()
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'service' => 'string|regex:/^[^:]+$/i',
|
||||
'service' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public function rules()
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'service' => strip_tags($this->service),
|
||||
'service' => strip_tags(strval($this->service)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -29,8 +29,11 @@ public function toArray($request)
|
||||
$request->merge(['withSecret' => false]);
|
||||
}
|
||||
|
||||
// Here we add a timestamp to the request if OTPs have to be in the response.
|
||||
// The 'at' parameter is used by the TwoFAccountReadResource class to obtain
|
||||
// all OTPs at the same timestamps
|
||||
if ($request->has('withOtp')) {
|
||||
$request->merge(['at' => time()]);
|
||||
$request->merge(['at' => now()->timestamp]);
|
||||
}
|
||||
|
||||
return $this->collection;
|
||||
|
@ -20,7 +20,7 @@
|
||||
* @property string|null $duration
|
||||
* @property string|null $login_method
|
||||
*/
|
||||
class UserAuthentication extends JsonResource
|
||||
class UserAuthenticationResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* A user agent parser instance.
|
@ -24,7 +24,7 @@
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Notifications\FailedLogin;
|
||||
use App\Notifications\FailedLoginNotification;
|
||||
use Illuminate\Auth\Events\Failed;
|
||||
|
||||
class FailedLoginListener extends AbstractAccessListener
|
||||
@ -56,7 +56,7 @@ public function handle(mixed $event) : void
|
||||
]);
|
||||
|
||||
if ($user->preferences['notifyOnFailedLogin'] == true) {
|
||||
$user->notify(new FailedLogin($log));
|
||||
$user->notify(new FailedLoginNotification($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
namespace App\Listeners\Authentication;
|
||||
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use App\Notifications\SignedInWithNewDeviceNotification;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
@ -59,7 +59,7 @@ public function handle(mixed $event) : void
|
||||
]);
|
||||
|
||||
if (! $known && ! $newUser && $user->preferences['notifyOnNewAuthDevice'] == true) {
|
||||
$user->notify(new SignedInWithNewDevice($log));
|
||||
$user->notify(new SignedInWithNewDeviceNotification($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
use App\Events\VisitedByProxyUser;
|
||||
use App\Extensions\RemoteUserProvider;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use App\Notifications\SignedInWithNewDeviceNotification;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class VisitedByProxyUserListener extends AbstractAccessListener
|
||||
@ -37,7 +37,7 @@ public function handle(mixed $event) : void
|
||||
]);
|
||||
|
||||
if (! $known && ! $newUser && ! str_ends_with($user->email, RemoteUserProvider::FAKE_REMOTE_DOMAIN) && $user->preferences['notifyOnNewAuthDevice']) {
|
||||
$user->notify(new SignedInWithNewDevice($log));
|
||||
$user->notify(new SignedInWithNewDeviceNotification($log));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
use Illuminate\Notifications\Events\NotificationSent;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LogNotification
|
||||
class LogNotificationListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
@ -9,7 +9,7 @@
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Jenssegers\Agent\Agent;
|
||||
|
||||
class FailedLogin extends Notification implements ShouldQueue
|
||||
class FailedLoginNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
@ -26,7 +26,7 @@ class FailedLogin extends Notification implements ShouldQueue
|
||||
public AuthLog $authLog;
|
||||
|
||||
/**
|
||||
* Create a new FailedLogin instance
|
||||
* Create a new FailedLoginNotification instance
|
||||
*/
|
||||
public function __construct(AuthLog $authLog)
|
||||
{
|
@ -9,7 +9,7 @@
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Jenssegers\Agent\Agent;
|
||||
|
||||
class SignedInWithNewDevice extends Notification implements ShouldQueue
|
||||
class SignedInWithNewDeviceNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
@ -26,7 +26,7 @@ class SignedInWithNewDevice extends Notification implements ShouldQueue
|
||||
protected $agent;
|
||||
|
||||
/**
|
||||
* Create a new SignedInWithNewDevice instance
|
||||
* Create a new SignedInWithNewDeviceNotification instance
|
||||
*/
|
||||
public function __construct(AuthLog $authLog)
|
||||
{
|
||||
@ -47,7 +47,7 @@ public function toMail(mixed $notifiable) : MailMessage
|
||||
{
|
||||
return (new MailMessage())
|
||||
->subject(__('notifications.new_device.subject'))
|
||||
->markdown('emails.SignedInWithNewDevice', [
|
||||
->markdown('emails.signedInWithNewDevice', [
|
||||
'account' => $notifiable,
|
||||
'time' => $this->authLog->login_at,
|
||||
'ipAddress' => $this->authLog->ip_address,
|
@ -24,13 +24,16 @@ public function before(User $user, string $ability) : ?bool
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user) : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// public function viewAny(User $user) : bool
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* Ignored as long as the before() method restrict the access to admins only
|
||||
*/
|
||||
public function view(User $user, User $model) : bool
|
||||
{
|
||||
@ -45,6 +48,9 @@ public function view(User $user, User $model) : bool
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* Ignored as long as the before() method restrict the access to admins only
|
||||
*/
|
||||
public function create(?User $user) : bool
|
||||
{
|
||||
@ -53,6 +59,8 @@ public function create(?User $user) : bool
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* Not ignored because the user can update itself
|
||||
*/
|
||||
public function update(User $user, User $model) : bool
|
||||
{
|
||||
@ -67,6 +75,9 @@ public function update(User $user, User $model) : bool
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* Ignored as long as the before() method restrict the access to admins only
|
||||
*/
|
||||
public function delete(User $user, User $model) : bool
|
||||
{
|
||||
@ -81,6 +92,9 @@ public function delete(User $user, User $model) : bool
|
||||
|
||||
/**
|
||||
* Determine whether the user can promote the model.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
* Ignored as long as the before() method restrict the access to admins only
|
||||
*/
|
||||
public function promote(User $user) : bool
|
||||
{
|
||||
|
@ -13,7 +13,7 @@
|
||||
use App\Listeners\Authentication\VisitedByProxyUserListener;
|
||||
use App\Listeners\CleanIconStorage;
|
||||
use App\Listeners\DissociateTwofaccountFromGroup;
|
||||
use App\Listeners\LogNotification;
|
||||
use App\Listeners\LogNotificationListener;
|
||||
use App\Listeners\RegisterOpenId;
|
||||
use App\Listeners\ReleaseRadar;
|
||||
use App\Listeners\ResetUsersPreference;
|
||||
@ -55,7 +55,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
RegisterOpenId::class,
|
||||
],
|
||||
NotificationSent::class => [
|
||||
LogNotification::class,
|
||||
LogNotificationListener::class,
|
||||
],
|
||||
Login::class => [
|
||||
LoginListener::class,
|
||||
|
10
docker/entrypoint.sh
vendored
10
docker/entrypoint.sh
vendored
@ -60,9 +60,11 @@ fi
|
||||
|
||||
echo "${COMMIT}" > /2fauth/installed
|
||||
php artisan storage:link --quiet
|
||||
php artisan optimize:clear
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
|
||||
# Clearing compiled, cache has already been cleared
|
||||
php artisan clear-compiled
|
||||
|
||||
# Clearing and Caching config, events, routes, views
|
||||
php artisan optimize
|
||||
|
||||
supervisord
|
||||
|
@ -23,7 +23,7 @@
|
||||
use Laravel\Passport\Http\Controllers\PersonalAccessTokenController;
|
||||
|
||||
// use App\Models\User;
|
||||
// use App\Notifications\SignedInWithNewDevice;
|
||||
// use App\Notifications\SignedInWithNewDeviceNotification;
|
||||
// use App\Models\AuthLog;
|
||||
|
||||
/*
|
||||
@ -109,7 +109,7 @@
|
||||
|
||||
// Route::get('/notification', function () {
|
||||
// $user = User::find(1);
|
||||
// return (new SignedInWithNewDevice(AuthLog::find(9)))
|
||||
// return (new SignedInWithNewDeviceNotification(AuthLog::find(9)))
|
||||
// ->toMail($user);
|
||||
// });
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Api\v1\Controllers\GroupController;
|
||||
use App\Api\v1\Resources\GroupResource;
|
||||
use App\Listeners\DissociateTwofaccountFromGroup;
|
||||
use App\Listeners\ResetUsersPreference;
|
||||
use App\Models\Group;
|
||||
use App\Models\TwoFAccount;
|
||||
@ -18,6 +19,7 @@
|
||||
#[CoversClass(ResetUsersPreference::class)]
|
||||
#[CoversClass(GroupPolicy::class)]
|
||||
#[CoversClass(Group::class)]
|
||||
#[CoversClass(DissociateTwofaccountFromGroup::class)]
|
||||
class GroupControllerTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
@ -103,6 +105,27 @@ public function test_index_returns_user_groups_only_with_pseudo_group()
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_orphan_groups_are_reassign_to_the_only_user()
|
||||
{
|
||||
config(['auth.defaults.guard' => 'reverse-proxy-guard']);
|
||||
|
||||
$this->anotherUser->delete();
|
||||
$this->userGroupA->user_id = null;
|
||||
$this->userGroupA->save();
|
||||
|
||||
$this->assertCount(1, User::all());
|
||||
$this->assertNull($this->userGroupA->user_id);
|
||||
|
||||
$this->actingAs($this->user, 'reverse-proxy-guard')
|
||||
->json('GET', '/api/v1/groups')
|
||||
->assertOk();
|
||||
|
||||
$this->userGroupA->refresh();
|
||||
|
||||
$this->assertNotNull($this->userGroupA->user_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_returns_created_group_resource()
|
||||
{
|
||||
|
@ -3,12 +3,14 @@
|
||||
namespace Tests\Api\v1\Controllers;
|
||||
|
||||
use App\Api\v1\Controllers\SettingController;
|
||||
use App\Api\v1\Requests\SettingUpdateRequest;
|
||||
use App\Facades\Settings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
@ -16,6 +18,7 @@
|
||||
* SettingController test class
|
||||
*/
|
||||
#[CoversClass(SettingController::class)]
|
||||
#[CoversMethod(SettingUpdateRequest::class, 'rules')]
|
||||
class SettingControllerTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
@ -230,6 +233,16 @@ public function test_update_missing_user_setting_returns_created_setting()
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_update_restrictList_setting_rejects_invalid_email_list()
|
||||
{
|
||||
$response = $this->actingAs($this->admin, 'api-guard')
|
||||
->json('PUT', '/api/v1/settings/restrictList', [
|
||||
'value' => 'johndoe@example.com|janedoeexamplecom',
|
||||
])
|
||||
->assertJsonValidationErrorFor('value');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_destroy_user_setting_returns_success()
|
||||
{
|
||||
|
@ -68,6 +68,8 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
||||
|
||||
protected $twofaccountD;
|
||||
|
||||
protected $twofaccountE;
|
||||
|
||||
private const VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET = [
|
||||
'id',
|
||||
'group_id',
|
||||
@ -102,12 +104,46 @@ class TwoFAccountControllerTest extends FeatureTestCase
|
||||
'period',
|
||||
];
|
||||
|
||||
private const VALID_EMBEDDED_OTP_RESOURCE_STRUCTURE_FOR_TOTP = [
|
||||
'generated_at',
|
||||
'password',
|
||||
];
|
||||
|
||||
private const VALID_OTP_RESOURCE_STRUCTURE_FOR_HOTP = [
|
||||
'otp_type',
|
||||
'password',
|
||||
'counter',
|
||||
];
|
||||
|
||||
private const VALID_RESOURCE_STRUCTURE_WITH_OTP = [
|
||||
'id',
|
||||
'group_id',
|
||||
'service',
|
||||
'account',
|
||||
'icon',
|
||||
'otp_type',
|
||||
'secret',
|
||||
'digits',
|
||||
'algorithm',
|
||||
'period',
|
||||
'counter',
|
||||
'otp' => self::VALID_EMBEDDED_OTP_RESOURCE_STRUCTURE_FOR_TOTP
|
||||
];
|
||||
|
||||
private const VALID_COLLECTION_RESOURCE_STRUCTURE_WITH_OTP = [
|
||||
'id',
|
||||
'group_id',
|
||||
'service',
|
||||
'account',
|
||||
'icon',
|
||||
'otp_type',
|
||||
'digits',
|
||||
'algorithm',
|
||||
'period',
|
||||
'counter',
|
||||
'otp' => self::VALID_EMBEDDED_OTP_RESOURCE_STRUCTURE_FOR_TOTP
|
||||
];
|
||||
|
||||
private const VALID_EXPORT_STRUTURE = [
|
||||
'app',
|
||||
'schema',
|
||||
@ -204,10 +240,13 @@ public function setUp() : void
|
||||
$this->twofaccountD = TwoFAccount::factory()->for($this->anotherUser)->create([
|
||||
'group_id' => $this->anotherUserGroupB->id,
|
||||
]);
|
||||
$this->twofaccountE = TwoFAccount::factory()->for($this->anotherUser)->create([
|
||||
'group_id' => $this->anotherUserGroupB->id,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('indexUrlParameterProvider')]
|
||||
#[DataProvider('validResourceStructureProvider')]
|
||||
public function test_index_returns_user_twofaccounts_only($urlParameter, $expected)
|
||||
{
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
@ -234,7 +273,7 @@ public function test_index_returns_user_twofaccounts_only($urlParameter, $expect
|
||||
/**
|
||||
* Provide data for index tests
|
||||
*/
|
||||
public static function indexUrlParameterProvider()
|
||||
public static function validResourceStructureProvider()
|
||||
{
|
||||
return [
|
||||
'VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET' => [
|
||||
@ -245,9 +284,71 @@ public static function indexUrlParameterProvider()
|
||||
'?withSecret=1',
|
||||
self::VALID_RESOURCE_STRUCTURE_WITH_SECRET,
|
||||
],
|
||||
'VALID_COLLECTION_RESOURCE_STRUCTURE_WITH_OTP' => [
|
||||
'?withOtp=1',
|
||||
self::VALID_COLLECTION_RESOURCE_STRUCTURE_WITH_OTP,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_index_returns_user_accounts_with_given_ids()
|
||||
{
|
||||
$response = $this->actingAs($this->anotherUser, 'api-guard')
|
||||
->json('GET', '/api/v1/twofaccounts?ids=' . $this->twofaccountC->id . ',' . $this->twofaccountE->id)
|
||||
->assertOk()
|
||||
->assertJsonCount(2, $key = null)
|
||||
->assertJsonStructure([
|
||||
'*' => self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET,
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'id' => $this->twofaccountC->id,
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'id' => $this->twofaccountE->id,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_index_returns_only_user_accounts_in_given_ids()
|
||||
{
|
||||
$response = $this->actingAs($this->anotherUser, 'api-guard')
|
||||
->json('GET', '/api/v1/twofaccounts?ids=' . $this->twofaccountA->id . ',' . $this->twofaccountE->id)
|
||||
->assertOk()
|
||||
->assertJsonCount(1, $key = null)
|
||||
->assertJsonStructure([
|
||||
'*' => self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET,
|
||||
])
|
||||
->assertJsonMissing([
|
||||
'id' => $this->twofaccountA->id,
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'id' => $this->twofaccountE->id,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_orphan_accounts_are_reassign_to_the_only_user()
|
||||
{
|
||||
config(['auth.defaults.guard' => 'reverse-proxy-guard']);
|
||||
|
||||
$this->anotherUser->delete();
|
||||
$this->twofaccountA->user_id = null;
|
||||
$this->twofaccountA->save();
|
||||
|
||||
$this->assertCount(1, User::all());
|
||||
$this->assertNull($this->twofaccountA->user_id);
|
||||
$this->assertCount(1, TwoFAccount::orphans()->get());
|
||||
|
||||
$this->actingAs($this->user, 'reverse-proxy-guard')
|
||||
->json('GET', '/api/v1/twofaccounts')
|
||||
->assertOk();
|
||||
|
||||
$this->twofaccountA->refresh();
|
||||
|
||||
$this->assertNotNull($this->twofaccountA->user_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_show_returns_twofaccount_resource_with_secret()
|
||||
{
|
||||
@ -289,6 +390,24 @@ public function test_show_returns_twofaccount_resource_without_secret()
|
||||
// ]);
|
||||
// }
|
||||
|
||||
#[Test]
|
||||
public function test_show_returns_twofaccount_resource_with_otp()
|
||||
{
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('GET', '/api/v1/twofaccounts/' . $this->twofaccountA->id . '?withOtp=1')
|
||||
->assertOk()
|
||||
->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITH_OTP);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_show_returns_twofaccount_resource_without_otp()
|
||||
{
|
||||
$response = $this->actingAs($this->user, 'api-guard')
|
||||
->json('GET', '/api/v1/twofaccounts/' . $this->twofaccountA->id . '?withOtp=0')
|
||||
->assertOk()
|
||||
->assertJsonStructure(self::VALID_RESOURCE_STRUCTURE_WITHOUT_SECRET);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_show_missing_twofaccount_returns_not_found()
|
||||
{
|
||||
|
@ -3,10 +3,12 @@
|
||||
namespace Tests\Api\v1\Controllers;
|
||||
|
||||
use App\Api\v1\Controllers\UserManagerController;
|
||||
use App\Api\v1\Resources\UserAuthenticationResource;
|
||||
use App\Api\v1\Resources\UserManagerResource;
|
||||
use App\Models\AuthLog;
|
||||
use App\Models\TwoFAccount;
|
||||
use App\Models\User;
|
||||
use App\Observers\UserObserver;
|
||||
use App\Policies\UserPolicy;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Auth\Notifications\ResetPassword;
|
||||
@ -29,8 +31,10 @@
|
||||
|
||||
#[CoversClass(UserManagerController::class)]
|
||||
#[CoversClass(UserManagerResource::class)]
|
||||
#[CoversClass(UserPolicy::class)]
|
||||
#[CoversClass(UserAuthenticationResource::class)]
|
||||
#[CoversClass(User::class)]
|
||||
#[CoversClass(UserObserver::class)]
|
||||
#[CoversClass(UserPolicy::class)]
|
||||
class UserManagerControllerTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
@ -147,6 +151,17 @@ public function test_show_returns_the_expected_UserManagerResource() : void
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_show_returns_forbidden_to_non_admin_user() : void
|
||||
{
|
||||
$this->actingAs($this->user, 'api-guard')
|
||||
->json('GET', '/api/v1/users/' . $this->anotherUser->id)
|
||||
->assertForbidden()
|
||||
->assertJsonStructure([
|
||||
'message',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_resetPassword_resets_password_and_sends_password_reset_to_user()
|
||||
{
|
||||
@ -299,6 +314,23 @@ public function test_store_returns_UserManagerResource_of_created_admin() : void
|
||||
$response->assertExactJson($resource->response($request)->getData(true));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_store_another_user_returns_forbidden() : void
|
||||
{
|
||||
$this->actingAs($this->user, 'api-guard')
|
||||
->json('POST', '/api/v1/users', [
|
||||
'name' => self::USERNAME,
|
||||
'email' => self::EMAIL,
|
||||
'password' => self::PASSWORD,
|
||||
'password_confirmation' => self::PASSWORD,
|
||||
'is_admin' => false,
|
||||
])
|
||||
->assertForbidden()
|
||||
->assertJsonStructure([
|
||||
'message',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_revokePATs_flushes_pats()
|
||||
{
|
||||
@ -437,6 +469,17 @@ public function test_destroy_the_only_admin_returns_forbidden()
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_destroy_another_user_returns_forbidden() : void
|
||||
{
|
||||
$this->actingAs($this->user, 'api-guard')
|
||||
->json('DELETE', '/api/v1/users/' . $this->anotherUser->id)
|
||||
->assertForbidden()
|
||||
->assertJsonStructure([
|
||||
'message',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_promote_changes_admin_status() : void
|
||||
{
|
||||
@ -468,6 +511,19 @@ public function test_promote_returns_UserManagerResource() : void
|
||||
$response->assertExactJson($resources->response($request)->getData(true));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_promote_another_user_returns_forbidden() : void
|
||||
{
|
||||
$this->actingAs($this->user, 'api-guard')
|
||||
->json('PATCH', '/api/v1/users/' . $this->anotherUser->id . '/promote', [
|
||||
'is_admin' => true,
|
||||
])
|
||||
->assertForbidden()
|
||||
->assertJsonStructure([
|
||||
'message',
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_demote_returns_UserManagerResource() : void
|
||||
{
|
||||
|
111
tests/Api/v1/Requests/DataProviders/TwoFAccountDataProvider.php
Normal file
111
tests/Api/v1/Requests/DataProviders/TwoFAccountDataProvider.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Api\v1\Requests\DataProviders;
|
||||
|
||||
final class TwoFAccountDataProvider
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function validIdsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'ids' => '1',
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,2,5',
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function invalidIdsProvider(): array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'ids' => '', // required
|
||||
]],
|
||||
[[
|
||||
'ids' => null, // required
|
||||
]],
|
||||
[[
|
||||
'ids' => true, // string
|
||||
]],
|
||||
[[
|
||||
'ids' => 10, // string
|
||||
]],
|
||||
[[
|
||||
'ids' => 'notaCommaSeparatedList', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => 'a,b', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => 'a,1', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => ',1,2', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,,2', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,2,', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => ',1,2,', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1;2', // regex
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function validIsAdminProvider(): array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => false,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 0,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 1,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function invalidIsAdminProvider(): array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'is_admin' => [],
|
||||
]],
|
||||
[[
|
||||
'is_admin' => null,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 'string',
|
||||
]],
|
||||
[[
|
||||
'is_admin' => '',
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 5,
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
93
tests/Api/v1/Requests/IconFetchRequestTest.php
Normal file
93
tests/Api/v1/Requests/IconFetchRequestTest.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\v1\Requests;
|
||||
|
||||
use App\Api\v1\Requests\GroupAssignRequest;
|
||||
use App\Api\v1\Requests\IconFetchRequest;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* IconFetchRequestTest test class
|
||||
*/
|
||||
#[CoversClass(IconFetchRequest::class)]
|
||||
class IconFetchRequestTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
#[Test]
|
||||
public function test_user_is_authorized()
|
||||
{
|
||||
Auth::shouldReceive('check')
|
||||
->once()
|
||||
->andReturn(true);
|
||||
|
||||
$request = new IconFetchRequest();
|
||||
|
||||
$this->assertTrue($request->authorize());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideValidData')]
|
||||
public function test_valid_data(array $data) : void
|
||||
{
|
||||
$request = new IconFetchRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertFalse($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide Valid data for validation test
|
||||
*/
|
||||
public static function provideValidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'service' => 'validWord',
|
||||
]],
|
||||
[[
|
||||
'service' => '0',
|
||||
]],
|
||||
[[
|
||||
'service' => '~string.with-sp3ci@l-ch4rs',
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideInvalidData')]
|
||||
public function test_invalid_data(array $data) : void
|
||||
{
|
||||
$request = new IconFetchRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide invalid data for validation test
|
||||
*/
|
||||
public static function provideInvalidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'service' => null,
|
||||
]],
|
||||
[[
|
||||
'service' => 0,
|
||||
]],
|
||||
[[
|
||||
'service' => true,
|
||||
]],
|
||||
[[
|
||||
'service' => [],
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Tests\Api\v1\Requests;
|
||||
|
||||
use App\Api\v1\Requests\SettingStoreRequest;
|
||||
use App\Api\v1\Requests\SettingUpdateRequest;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
@ -15,7 +14,7 @@
|
||||
/**
|
||||
* SettingUpdateRequestTest test class
|
||||
*/
|
||||
#[CoversClass(SettingStoreRequest::class)]
|
||||
#[CoversClass(SettingUpdateRequest::class)]
|
||||
class SettingUpdateRequestTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
@ -7,8 +7,9 @@
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\DataProviderExternal;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\Api\v1\Requests\DataProviders\TwoFAccountDataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@ -32,7 +33,7 @@ public function test_user_is_authorized()
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideValidData')]
|
||||
#[DataProviderExternal(TwoFAccountDataProvider::class, 'validIdsProvider')]
|
||||
public function test_valid_data(array $data) : void
|
||||
{
|
||||
$request = new TwoFAccountBatchRequest();
|
||||
@ -41,23 +42,8 @@ public function test_valid_data(array $data) : void
|
||||
$this->assertFalse($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide Valid data for validation test
|
||||
*/
|
||||
public static function provideValidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'ids' => '1',
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,2,5',
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideInvalidData')]
|
||||
#[DataProviderExternal(TwoFAccountDataProvider::class, 'invalidIdsProvider')]
|
||||
public function test_invalid_data(array $data) : void
|
||||
{
|
||||
$request = new TwoFAccountBatchRequest();
|
||||
@ -65,49 +51,4 @@ public function test_invalid_data(array $data) : void
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide invalid data for validation test
|
||||
*/
|
||||
public static function provideInvalidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'ids' => '', // required
|
||||
]],
|
||||
[[
|
||||
'ids' => null, // required
|
||||
]],
|
||||
[[
|
||||
'ids' => true, // string
|
||||
]],
|
||||
[[
|
||||
'ids' => 10, // string
|
||||
]],
|
||||
[[
|
||||
'ids' => 'notaCommaSeparatedList', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => 'a,b', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => 'a,1', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => ',1,2', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,,2', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1,2,', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => ',1,2,', // regex
|
||||
]],
|
||||
[[
|
||||
'ids' => '1;2', // regex
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
54
tests/Api/v1/Requests/TwoFAccountIndexRequestTest.php
Normal file
54
tests/Api/v1/Requests/TwoFAccountIndexRequestTest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\v1\Requests;
|
||||
|
||||
use App\Api\v1\Requests\TwoFAccountIndexRequest;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProviderExternal;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\Api\v1\Requests\DataProviders\TwoFAccountDataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* TwoFAccountIndexRequestTestTest test class
|
||||
*/
|
||||
#[CoversClass(TwoFAccountIndexRequest::class)]
|
||||
class TwoFAccountIndexRequestTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
#[Test]
|
||||
public function test_user_is_authorized()
|
||||
{
|
||||
Auth::shouldReceive('check')
|
||||
->once()
|
||||
->andReturn(true);
|
||||
|
||||
$request = new TwoFAccountIndexRequest();
|
||||
|
||||
$this->assertTrue($request->authorize());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProviderExternal(TwoFAccountDataProvider::class, 'validIdsProvider')]
|
||||
public function test_valid_data(array $data) : void
|
||||
{
|
||||
$request = new TwoFAccountIndexRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertFalse($validator->fails());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProviderExternal(TwoFAccountDataProvider::class, 'invalidIdsProvider')]
|
||||
public function test_invalid_data(array $data) : void
|
||||
{
|
||||
$request = new TwoFAccountIndexRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
}
|
||||
}
|
98
tests/Api/v1/Requests/UserManagerPromoteRequestTest.php
Normal file
98
tests/Api/v1/Requests/UserManagerPromoteRequestTest.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\v1\Requests;
|
||||
|
||||
use App\Api\v1\Requests\UserManagerPromoteRequest;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* UserManagerPromoteRequestTest test class
|
||||
*/
|
||||
#[CoversClass(UserManagerPromoteRequest::class)]
|
||||
class UserManagerPromoteRequestTest extends TestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
#[Test]
|
||||
public function test_user_is_authorized()
|
||||
{
|
||||
Auth::shouldReceive('user->isAdministrator')
|
||||
->once()
|
||||
->andReturn(true);
|
||||
|
||||
$request = new UserManagerPromoteRequest();
|
||||
|
||||
$this->assertTrue($request->authorize());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideValidData')]
|
||||
public function test_valid_data(array $data) : void
|
||||
{
|
||||
$request = new UserManagerPromoteRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertFalse($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide Valid data for validation test
|
||||
*/
|
||||
public static function provideValidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => false,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 0,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 1,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideInvalidData')]
|
||||
public function test_invalid_data(array $data) : void
|
||||
{
|
||||
$request = new UserManagerPromoteRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide invalid data for validation test
|
||||
*/
|
||||
public static function provideInvalidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'is_admin' => [],
|
||||
]],
|
||||
[[
|
||||
'is_admin' => null,
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 'string',
|
||||
]],
|
||||
[[
|
||||
'is_admin' => '',
|
||||
]],
|
||||
[[
|
||||
'is_admin' => 5,
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
181
tests/Api/v1/Requests/UserManagerStoreRequestTest.php
Normal file
181
tests/Api/v1/Requests/UserManagerStoreRequestTest.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Api\v1\Requests;
|
||||
|
||||
use App\Api\v1\Requests\UserManagerStoreRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\DataProviderExternal;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* UserManagerStoreRequestTest test class
|
||||
*/
|
||||
#[CoversClass(UserManagerStoreRequest::class)]
|
||||
class UserManagerStoreRequestTest extends FeatureTestCase
|
||||
{
|
||||
use WithoutMiddleware;
|
||||
|
||||
#[Test]
|
||||
public function test_admin_is_authorized()
|
||||
{
|
||||
Auth::shouldReceive('user->isAdministrator')
|
||||
->once()
|
||||
->andReturn(true);
|
||||
|
||||
$request = new UserManagerStoreRequest();
|
||||
|
||||
$this->assertTrue($request->authorize());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideValidData')]
|
||||
public function test_valid_data(array $data) : void
|
||||
{
|
||||
User::factory()->create([
|
||||
'name' => 'Jane',
|
||||
'email' => 'jane@example.com',
|
||||
]);
|
||||
|
||||
$request = new UserManagerStoreRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertFalse($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide Valid data for validation test
|
||||
*/
|
||||
public static function provideValidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'JOHN@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => 0,
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('provideInvalidData')]
|
||||
public function test_invalid_data(array $data) : void
|
||||
{
|
||||
User::factory()->create([
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
]);
|
||||
|
||||
$request = new UserManagerStoreRequest();
|
||||
$validator = Validator::make($data, $request->rules());
|
||||
|
||||
$this->assertTrue($validator->fails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide invalid data for validation test
|
||||
*/
|
||||
public static function provideInvalidData() : array
|
||||
{
|
||||
return [
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com', // unique
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => '', // required
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => '', // required
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', // max:255
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz@example.com', // max:255
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'johnexample.com', // email
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => '', // required
|
||||
'password_confirmation' => '', // required
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'anotherPassword', // confirmed
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'pwd', // min:8
|
||||
'password_confirmation' => 'pwd',
|
||||
'is_admin' => true,
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => null, // required
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => '', // required
|
||||
]],
|
||||
[[
|
||||
'name' => 'John',
|
||||
'email' => 'john@example.com',
|
||||
'password' => 'MyPassword',
|
||||
'password_confirmation' => 'MyPassword',
|
||||
'is_admin' => 'string', // boolean
|
||||
]],
|
||||
];
|
||||
}
|
||||
}
|
23
tests/Feature/AppTest.php
Normal file
23
tests/Feature/AppTest.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Providers\EventServiceProvider;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* AppTest class
|
||||
*/
|
||||
#[CoversClass(EventServiceProvider::class)]
|
||||
class AppTest extends FeatureTestCase
|
||||
{
|
||||
#[Test]
|
||||
public function test_events_should_be_explicitly_registered()
|
||||
{
|
||||
$eventServiceProvider = app()->getProvider(EventServiceProvider::class);
|
||||
|
||||
$this->assertFalse($eventServiceProvider->shouldDiscoverEvents());
|
||||
}
|
||||
}
|
@ -12,9 +12,12 @@
|
||||
use App\Http\Middleware\SkipIfAuthenticated;
|
||||
use App\Listeners\Authentication\FailedLoginListener;
|
||||
use App\Listeners\Authentication\LoginListener;
|
||||
use App\Listeners\Authentication\LogoutListener;
|
||||
use App\Listeners\LogNotificationListener;
|
||||
use App\Models\AuthLog;
|
||||
use App\Models\User;
|
||||
use App\Notifications\FailedLogin;
|
||||
use App\Notifications\SignedInWithNewDevice;
|
||||
use App\Notifications\FailedLoginNotification;
|
||||
use App\Notifications\SignedInWithNewDeviceNotification;
|
||||
use App\Rules\CaseInsensitiveEmailExists;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
@ -32,12 +35,16 @@
|
||||
#[CoversClass(RejectIfReverseProxy::class)]
|
||||
#[CoversClass(RejectIfDemoMode::class)]
|
||||
#[CoversClass(LoginListener::class)]
|
||||
#[CoversClass(LogoutListener::class)]
|
||||
#[CoversClass(FailedLoginListener::class)]
|
||||
#[CoversMethod(CaseInsensitiveEmailExists::class, 'handle')]
|
||||
#[CoversMethod(CaseInsensitiveEmailExists::class, 'validate')]
|
||||
#[CoversMethod(SkipIfAuthenticated::class, 'handle')]
|
||||
#[CoversMethod(Handler::class, 'register')]
|
||||
#[CoversMethod(KickOutInactiveUser::class, 'handle')]
|
||||
#[CoversMethod(LogUserLastSeen::class, 'handle')]
|
||||
#[CoversClass(LogNotificationListener::class)]
|
||||
#[CoversClass(SignedInWithNewDeviceNotification::class)]
|
||||
#[CoversClass(FailedLoginNotification::class)]
|
||||
class LoginTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
@ -50,6 +57,8 @@ class LoginTest extends FeatureTestCase
|
||||
*/
|
||||
protected $admin;
|
||||
|
||||
private const WEB_GUARD = 'web-guard';
|
||||
|
||||
private const PASSWORD = 'password';
|
||||
|
||||
private const WRONG_PASSWORD = 'wrong_password';
|
||||
@ -65,6 +74,8 @@ public function setUp() : void
|
||||
#[Test]
|
||||
public function test_user_login_returns_success()
|
||||
{
|
||||
Notification::fake();
|
||||
|
||||
$response = $this->json('POST', '/user/login', [
|
||||
'email' => $this->user->email,
|
||||
'password' => self::PASSWORD,
|
||||
@ -95,7 +106,7 @@ public function test_login_send_new_device_notification()
|
||||
'password' => self::PASSWORD,
|
||||
])->assertOk();
|
||||
|
||||
$this->actingAs($this->user, 'web-guard')
|
||||
$this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('GET', '/user/logout');
|
||||
|
||||
$this->travel(1)->minute();
|
||||
@ -107,7 +118,7 @@ public function test_login_send_new_device_notification()
|
||||
'HTTP_USER_AGENT' => 'NotSymfony',
|
||||
])->assertOk();
|
||||
|
||||
Notification::assertSentTo($this->user, SignedInWithNewDevice::class);
|
||||
Notification::assertSentTo($this->user, SignedInWithNewDeviceNotification::class);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@ -123,7 +134,7 @@ public function test_login_does_not_send_new_device_notification()
|
||||
'password' => self::PASSWORD,
|
||||
])->assertOk();
|
||||
|
||||
$this->actingAs($this->user, 'web-guard')
|
||||
$this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('GET', '/user/logout');
|
||||
|
||||
$this->travel(1)->minute();
|
||||
@ -170,6 +181,40 @@ public function test_user_login_with_uppercased_email_returns_success()
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_successful_web_login_with_password_is_logged()
|
||||
{
|
||||
$this->json('POST', '/user/login', [
|
||||
'email' => $this->user->email,
|
||||
'password' => self::PASSWORD,
|
||||
])->assertOk();
|
||||
|
||||
$this->assertDatabaseHas('auth_logs', [
|
||||
'authenticatable_id' => $this->user->id,
|
||||
'login_successful' => true,
|
||||
'guard' => self::WEB_GUARD,
|
||||
'login_method' => self::PASSWORD,
|
||||
'logout_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_failed_web_login_with_password_is_logged()
|
||||
{
|
||||
$this->json('POST', '/user/login', [
|
||||
'email' => $this->user->email,
|
||||
'password' => self::WRONG_PASSWORD,
|
||||
])->assertStatus(401);
|
||||
|
||||
$this->assertDatabaseHas('auth_logs', [
|
||||
'authenticatable_id' => $this->user->id,
|
||||
'login_successful' => false,
|
||||
'guard' => self::WEB_GUARD,
|
||||
'login_method' => self::PASSWORD,
|
||||
'logout_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_user_login_already_authenticated_is_rejected()
|
||||
{
|
||||
@ -178,7 +223,7 @@ public function test_user_login_already_authenticated_is_rejected()
|
||||
'password' => self::PASSWORD,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user, 'web-guard')
|
||||
$response = $this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('POST', '/user/login', [
|
||||
'email' => $this->user->email,
|
||||
'password' => self::PASSWORD,
|
||||
@ -229,7 +274,7 @@ public function test_login_with_invalid_credentials_send_failed_login_notificati
|
||||
'password' => self::WRONG_PASSWORD,
|
||||
])->assertStatus(401);
|
||||
|
||||
Notification::assertSentTo($this->user, FailedLogin::class);
|
||||
Notification::assertSentTo($this->user, FailedLoginNotification::class);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@ -278,7 +323,7 @@ public function test_user_logout_returns_validation_success()
|
||||
'password' => self::PASSWORD,
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($this->user, 'web-guard')
|
||||
$response = $this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('GET', '/user/logout')
|
||||
->assertOk()
|
||||
->assertExactJson([
|
||||
@ -308,4 +353,41 @@ public function test_user_logout_after_inactivity_returns_teapot()
|
||||
->json('GET', '/api/v1/twofaccounts')
|
||||
->assertStatus(418);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_successful_web_logout_is_logged()
|
||||
{
|
||||
$this->json('POST', '/user/login', [
|
||||
'email' => $this->user->email,
|
||||
'password' => self::PASSWORD,
|
||||
])->assertOk();
|
||||
|
||||
$this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('GET', '/user/logout')
|
||||
->assertOk();
|
||||
|
||||
$authlog = AuthLog::first();
|
||||
|
||||
$this->assertEquals($this->user->id, $authlog->authenticatable_id);
|
||||
$this->assertTrue($authlog->login_successful);
|
||||
$this->assertEquals(self::WEB_GUARD, $authlog->guard);
|
||||
$this->assertEquals(self::PASSWORD, $authlog->login_method);
|
||||
$this->assertNotNull($authlog->logout_at);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_orphan_web_logout_is_logged()
|
||||
{
|
||||
$this->actingAs($this->user, self::WEB_GUARD)
|
||||
->json('GET', '/user/logout')
|
||||
->assertOk();
|
||||
|
||||
$authlog = AuthLog::first();
|
||||
|
||||
$this->assertEquals($this->user->id, $authlog->authenticatable_id);
|
||||
$this->assertFalse($authlog->login_successful);
|
||||
$this->assertEquals(self::WEB_GUARD, $authlog->guard);
|
||||
$this->assertNull($authlog->login_method);
|
||||
$this->assertNotNull($authlog->logout_at);
|
||||
}
|
||||
}
|
||||
|
@ -60,42 +60,6 @@ public function test_sendRecoveryEmail_sends_notification_on_success()
|
||||
]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_WebauthnRecoveryNotification_renders_to_email()
|
||||
{
|
||||
$mail = (new WebauthnRecoveryNotification('test_token'))->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'http://localhost/webauthn/recover?token=test_token&email=' . urlencode($this->user->email),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get('Recover Account'),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get(
|
||||
'You are receiving this email because we received an account recovery request for your account.'
|
||||
),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get(
|
||||
'This recovery link will expire in :count minutes.',
|
||||
['count' => config('auth.passwords.webauthn.expire')]
|
||||
),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get('If you did not request an account recovery, no further action is required.'),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_sendRecoveryEmail_does_not_send_anything_to_unknown_email()
|
||||
{
|
||||
|
@ -9,6 +9,7 @@
|
||||
use Illuminate\Foundation\Testing\WithoutMiddleware;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\CoversTrait;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
@ -17,7 +18,7 @@
|
||||
*/
|
||||
#[CoversClass(WebAuthnManageController::class)]
|
||||
#[CoversClass(RejectIfReverseProxy::class)]
|
||||
#[CoversClass(WebAuthnManageCredentials::class)]
|
||||
#[CoversTrait(WebAuthnManageCredentials::class)]
|
||||
class WebAuthnManageControllerTest extends FeatureTestCase
|
||||
{
|
||||
// use WithoutMiddleware;
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Models\User;
|
||||
use App\Notifications\TestEmailSettingNotification;
|
||||
use App\Services\ReleaseRadarService;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
@ -122,17 +123,6 @@ public function test_testEmail_sends_a_notification()
|
||||
Notification::assertSentTo($this->admin, TestEmailSettingNotification::class);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_testEmail_renders_to_email()
|
||||
{
|
||||
$mail = (new TestEmailSettingNotification('test_token'))->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get('notifications.test_email_settings.reason'),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_testEmail_returns_unauthorized()
|
||||
{
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Models\Group;
|
||||
use App\Models\TwoFAccount;
|
||||
use App\Models\User;
|
||||
use App\Observers\UserObserver;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@ -21,6 +22,7 @@
|
||||
* UserModelTest test class
|
||||
*/
|
||||
#[CoversClass(User::class)]
|
||||
#[CoversClass(UserObserver::class)]
|
||||
class UserModelTest extends FeatureTestCase
|
||||
{
|
||||
#[Test]
|
||||
|
82
tests/Feature/Notifications/FailedLoginNotificationTest.php
Normal file
82
tests/Feature/Notifications/FailedLoginNotificationTest.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications;
|
||||
|
||||
use App\Models\AuthLog;
|
||||
use App\Models\User;
|
||||
use App\Notifications\FailedLoginNotification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* FailedLoginTest test class
|
||||
*/
|
||||
#[CoversClass(FailedLoginNotification::class)]
|
||||
class FailedLoginNotificationTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var \App\Models\AuthLog
|
||||
*/
|
||||
protected $authLog;
|
||||
|
||||
/**
|
||||
* @var \App\Notifications\FailedLoginNotification
|
||||
*/
|
||||
protected $failedLogin;
|
||||
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
|
||||
AuthLog::factory()->for($this->user, 'authenticatable')->failedLogin()->create();
|
||||
|
||||
$this->authLog = AuthLog::first();
|
||||
$this->failedLogin = new FailedLoginNotification($this->authLog);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_it_renders_to_email()
|
||||
{
|
||||
|
||||
$mail = $this->failedLogin->toMail($this->user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mail);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_rendered_email_contains_expected_data()
|
||||
{
|
||||
$mail = $this->failedLogin->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->authLog->login_at->toCookieString(),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->authLog->ip_address,
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->user->name,
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
__('admin.browser_on_platform', ['browser' => 'Firefox', 'platform' => 'Windows']),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications;
|
||||
|
||||
use App\Models\AuthLog;
|
||||
use App\Models\User;
|
||||
use App\Notifications\SignedInWithNewDeviceNotification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* SignedInWithNewDeviceNotificationTest test class
|
||||
*/
|
||||
#[CoversClass(SignedInWithNewDeviceNotification::class)]
|
||||
class SignedInWithNewDeviceNotificationTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var \App\Models\AuthLog
|
||||
*/
|
||||
protected $authLog;
|
||||
|
||||
/**
|
||||
* @var \App\Notifications\SignedInWithNewDeviceNotification
|
||||
*/
|
||||
protected $signedInWithNewDevice;
|
||||
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
|
||||
AuthLog::factory()->for($this->user, 'authenticatable')->failedLogin()->create();
|
||||
|
||||
$this->authLog = AuthLog::first();
|
||||
$this->signedInWithNewDevice = new SignedInWithNewDeviceNotification($this->authLog);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_it_renders_to_email()
|
||||
{
|
||||
$mail = $this->signedInWithNewDevice->toMail($this->user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mail);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_rendered_email_contains_expected_data()
|
||||
{
|
||||
$mail = $this->signedInWithNewDevice->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->authLog->login_at->toCookieString(),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->authLog->ip_address,
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
$this->user->name,
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
__('admin.browser_on_platform', ['browser' => 'Firefox', 'platform' => 'Windows']),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\TestEmailSettingNotification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* TestEmailSettingNotificationTest test class
|
||||
*/
|
||||
#[CoversClass(TestEmailSettingNotification::class)]
|
||||
class TestEmailSettingNotificationTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var \App\Notifications\TestEmailSettingNotification
|
||||
*/
|
||||
protected $testEmailSettingNotification;
|
||||
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->testEmailSettingNotification = new TestEmailSettingNotification('test_token');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_it_renders_to_email()
|
||||
{
|
||||
$mail = $this->testEmailSettingNotification->toMail($this->user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mail);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_rendered_email_contains_expected_data()
|
||||
{
|
||||
$mail = $this->testEmailSettingNotification->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
__('notifications.test_email_settings.success'),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Notifications;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Notifications\WebauthnRecoveryNotification;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
/**
|
||||
* WebauthnRecoveryNotificationTest test class
|
||||
*/
|
||||
#[CoversClass(WebauthnRecoveryNotification::class)]
|
||||
class WebauthnRecoveryNotificationTest extends FeatureTestCase
|
||||
{
|
||||
/**
|
||||
* @var \App\Models\User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var \App\Notifications\WebauthnRecoveryNotification
|
||||
*/
|
||||
protected $webauthnRecoveryNotification;
|
||||
|
||||
public function setUp() : void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->user = User::factory()->create();
|
||||
$this->webauthnRecoveryNotification = new WebauthnRecoveryNotification('test_token');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_it_renders_to_email()
|
||||
{
|
||||
$mail = $this->webauthnRecoveryNotification->toMail($this->user);
|
||||
|
||||
$this->assertInstanceOf(MailMessage::class, $mail);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_rendered_email_contains_expected_data()
|
||||
{
|
||||
$mail = $this->webauthnRecoveryNotification->toMail($this->user)->render();
|
||||
|
||||
$this->assertStringContainsString(
|
||||
'http://localhost/webauthn/recover?token=test_token&email=' . urlencode($this->user->email),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get('Recover Account'),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get(
|
||||
'You are receiving this email because we received an account recovery request for your account.'
|
||||
),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get(
|
||||
'This recovery link will expire in :count minutes.',
|
||||
['count' => config('auth.passwords.webauthn.expire')]
|
||||
),
|
||||
$mail
|
||||
);
|
||||
|
||||
$this->assertStringContainsString(
|
||||
Lang::get('If you did not request an account recovery, no further action is required.'),
|
||||
$mail
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -2,11 +2,22 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use PHPUnit\Framework\Attributes\CoversMethod;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\FeatureTestCase;
|
||||
|
||||
|
||||
#[CoversMethod(RouteServiceProvider::class, 'boot')]
|
||||
class RouteTest extends FeatureTestCase
|
||||
{
|
||||
const API_ROUTE_PREFIX = 'api/v1';
|
||||
const API_MIDDLEWARE = 'api.v1';
|
||||
|
||||
#[Test]
|
||||
public function test_landing_view_is_returned()
|
||||
{
|
||||
@ -23,4 +34,40 @@ public function test_exception_handler_with_web_route()
|
||||
|
||||
$response->assertStatus(405);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function test_all_api_routes_are_behind_apiv1_middleware()
|
||||
{
|
||||
$routes = Route::getRoutes();
|
||||
|
||||
foreach ($routes as $route) {
|
||||
$middlewares = Route::gatherRouteMiddleware($route);
|
||||
|
||||
if (Str::startsWith($route->uri(), self::API_ROUTE_PREFIX)) {
|
||||
$this->assertEquals(self::API_ROUTE_PREFIX, $route->getPrefix());
|
||||
$this->assertTrue(in_array(self::API_MIDDLEWARE, $middlewares));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
#[DataProvider('wherePatternProvider')]
|
||||
public function test_router_has_expected_global_where_patterns($pattern)
|
||||
{
|
||||
$patterns = Route::getPatterns();
|
||||
|
||||
$this->assertArrayHasKey($pattern, $patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide data for tests
|
||||
*/
|
||||
public static function wherePatternProvider()
|
||||
{
|
||||
return [
|
||||
'SETTING_NAME' => ['settingName']
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace Tests\Unit\Listeners;
|
||||
|
||||
use App\Listeners\LogNotification;
|
||||
use App\Listeners\LogNotificationListener;
|
||||
use Illuminate\Notifications\Events\NotificationSent;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
@ -10,9 +10,9 @@
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* ResetUsersPreferenceTest test class
|
||||
* LogNotificationTest test class
|
||||
*/
|
||||
#[CoversClass(LogNotification::class)]
|
||||
#[CoversClass(LogNotificationListener::class)]
|
||||
class LogNotificationTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
@ -22,7 +22,7 @@ public function test_LogNotificationTest_listen_to_NotificationSent_event()
|
||||
|
||||
Event::assertListening(
|
||||
NotificationSent::class,
|
||||
LogNotification::class
|
||||
LogNotificationListener::class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user