first commit

This commit is contained in:
2026-10-09 08:21:06 +02:00
parent db7456df87
commit d259c62304
3761 changed files with 896046 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
<?php
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Testing\TestResponse;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('dashboard renders for authenticated admin', function () {
$this->actingAsAdmin()
->get('/')
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('Dashboard')
->has('widgets')
);
});
test('dashboard hides user management widgets from roles without the permission', function () {
$this->be(UserFactory::user()->create());
$components = widgetComponents($this->get('/')->assertOk());
$this->assertNotContains('StatWidget', $components);
$this->assertNotContains('RegistrationHistoryWidget', $components);
$this->assertNotContains('LatestRegistrationsWidget', $components);
$this->assertNotContains('ApprovalNoticeWidget', $components);
$this->assertContains('UserActionsWidget', $components);
});
test('dashboard shows user management widgets to roles holding the permission', function () {
$this->be(UserFactory::withPermissions('users.manage')->create());
$components = widgetComponents($this->get('/')->assertOk());
$this->assertContains('StatWidget', $components);
$this->assertContains('RegistrationHistoryWidget', $components);
$this->assertContains('LatestRegistrationsWidget', $components);
});
/**
* @return array<int, string>
*/
function widgetComponents(TestResponse $response): array
{
return array_column($response->viewData('page')['props']['widgets'], 'component');
}
test('dashboard redirects guest to login', function () {
$this->get('/')->assertRedirect('login');
});
test('dashboard flashes success when email verified session is set', function () {
$this->actingAsAdmin()
->withSession(['verified' => true])
->get('/')
->assertOk()
->assertSessionHas('success', __('E-Mail verified successfully.'));
});
+375
View File
@@ -0,0 +1,375 @@
<?php
use App\Models\Role;
use App\Models\User;
use App\Support\Demo;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
/**
* The account UserSeeder creates, which config/demo.php protects by default.
*/
function demoAccount(): User
{
return User::where('username', 'admin')->firstOrFail();
}
function enableDemoMode(): void
{
config(['demo.enabled' => true]);
}
/*
|--------------------------------------------------------------------------
| Off by default
|--------------------------------------------------------------------------
*/
test('demo mode is off unless the deployment turns it on', function () {
// phpunit.xml pins DEMO_MODE off, because the demo deployment's own .env
// turns it on and the suite must not change meaning there. That the
// shipped templates default to false is EnvironmentTemplateTest's job.
$this->assertFalse(Demo::enabled());
});
test('every write is reachable while demo mode is off', function () {
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'Renamed'])
->assertRedirect('/settings/general');
$this->assertEquals('Renamed', Setting::get('app_name'));
$this->from('/users')
->delete('users/'.demoAccount()->id)
->assertRedirect('users');
$this->assertDatabaseMissing('users', ['username' => 'admin']);
});
/*
|--------------------------------------------------------------------------
| Blocked routes
|--------------------------------------------------------------------------
*/
test('blocked routes are refused with a flashed error', function (string $url, array $data) {
enableDemoMode();
$this->actingAsAdmin()
->from('/')
->post($url, $data)
->assertRedirect('/');
$this->assertSessionHasFlashError('This action is disabled in the demo.');
})->with([
'general settings' => ['/settings/general', ['app_name' => 'Renamed']],
'auth settings' => ['/settings/auth', ['reg_enabled' => false]],
'two factor toggle' => ['/settings/auth/2fa/enable', []],
'permission matrix' => ['/permissions/save', ['roles' => []]],
]);
test('a blocked settings write leaves the setting alone', function () {
Setting::set('app_name', 'Vanguard');
Setting::save();
enableDemoMode();
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'Renamed']);
$this->assertEquals('Vanguard', Setting::get('app_name'));
});
test('a blocked permission matrix write leaves the role permissions alone', function () {
$role = Role::where('name', 'User')->firstOrFail();
$before = $role->permissions()->pluck('id')->sort()->values()->all();
enableDemoMode();
$this->actingAsAdmin()
->from('/permissions')
->post('/permissions/save', ['roles' => [$role->id => []]]);
$this->assertSessionHasFlashError('This action is disabled in the demo.');
$this->assertEquals($before, $role->fresh()->permissions()->pluck('id')->sort()->values()->all());
});
test('a refused Inertia delete answers 303 so the browser does not repeat the verb', function () {
enableDemoMode();
$this->actingAs(UserFactory::admin()->create())
->from('/users')
->withHeader('X-Inertia', 'true')
->delete('users/'.demoAccount()->id)
->assertStatus(303)
->assertRedirect('/users');
$this->assertSessionHasFlashError('The demo account cannot be modified.');
});
test('the refusal reaches the shared flash prop the layouts turn into a toast', function () {
enableDemoMode();
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'Renamed']);
// The flash is read by HandleInertiaRequests on the request the redirect
// lands on, which is where the layout's watcher fires.
$this->get('/settings')
->assertInertia(fn (Assert $page) => $page
->where('flash.error', 'This action is disabled in the demo.')
);
});
test('reading a page is never blocked', function () {
enableDemoMode();
$this->actingAsAdmin()->get('/settings')->assertOk();
$this->get('/users/'.demoAccount()->id)->assertOk();
$this->get('/permissions')->assertOk();
});
/*
|--------------------------------------------------------------------------
| The demo stays interactive
|--------------------------------------------------------------------------
*/
test('a visitor can still create and delete an ordinary user', function () {
enableDemoMode();
$this->actingAs(demoAccount())
->from('/users/create')
->post('users', [
'email' => 'jane@example.com',
'password' => 'secret123',
'password_confirmation' => 'secret123',
'role_id' => Role::where('name', 'User')->value('id'),
])
->assertRedirect('users');
$this->assertSessionHasSuccess('User created successfully.');
$created = User::where('email', 'jane@example.com')->firstOrFail();
$this->from('/users')
->delete('users/'.$created->id)
->assertRedirect('users');
$this->assertSessionHasSuccess('User deleted successfully.');
$this->assertDatabaseMissing('users', ['email' => 'jane@example.com']);
});
test('the demo account can still create a role', function () {
enableDemoMode();
$this->actingAs(demoAccount())
->from('/roles/create')
->post('roles', Role::factory()->raw(['name' => 'Editor']))
->assertRedirect('roles');
$this->assertSessionHasSuccess('Role created successfully.');
$this->assertDatabaseHas('roles', ['name' => 'Editor']);
});
test('a visitor can still impersonate another user and stop again', function () {
enableDemoMode();
$target = UserFactory::user()->create();
$this->actingAs(demoAccount())
->get(route('impersonate', $target->id))
->assertRedirect('/');
$this->assertTrue(auth()->user()->is($target));
$this->get(route('impersonate.leave'));
$this->assertTrue(auth()->user()->is(demoAccount()));
});
test('impersonating does not lift the demo restrictions', function () {
enableDemoMode();
$target = UserFactory::withPermissions('settings.general')->create();
$this->actingAs(demoAccount())->get(route('impersonate', $target->id));
$this->from('/settings/general')
->post('/settings/general', ['app_name' => 'Renamed']);
$this->assertSessionHasFlashError('This action is disabled in the demo.');
});
test('impersonating the demo account does not hand over its login details', function () {
enableDemoMode();
// Taking over a protected account is allowed; changing it is not, because
// the self service rules read the impersonated user rather than the one
// who signed in.
$this->actingAs(UserFactory::admin()->create())
->get(route('impersonate', demoAccount()->id));
$this->assertTrue(auth()->user()->is(demoAccount()));
$this->from('/profile')
->put('profile/login-details', [
'email' => 'hijacked@example.com',
'password' => 'hijacked123',
'password_confirmation' => 'hijacked123',
]);
$this->assertSessionHasFlashError('The demo account cannot be modified.');
$this->assertEquals('admin@example.com', demoAccount()->email);
});
test('logging in still works', function () {
enableDemoMode();
$user = UserFactory::withCredentials('jane', 'secret123')->user()->create();
$this->post('login', ['username' => 'jane', 'password' => 'secret123'])
->assertRedirect('/');
$this->assertAuthenticatedAs($user);
});
/*
|--------------------------------------------------------------------------
| The protected account
|--------------------------------------------------------------------------
*/
test('the demo account cannot be deleted', function () {
enableDemoMode();
$this->actingAs(UserFactory::admin()->create())
->from('/users')
->delete('users/'.demoAccount()->id)
->assertRedirect('/users');
$this->assertSessionHasFlashError('The demo account cannot be modified.');
$this->assertDatabaseHas('users', ['username' => 'admin']);
});
test('the demo account cannot have its login details changed by an administrator', function () {
enableDemoMode();
$this->actingAs(UserFactory::admin()->create())
->from('/users')
->put('users/'.demoAccount()->id.'/update/login-details', [
'email' => 'hijacked@example.com',
'password' => 'hijacked123',
'password_confirmation' => 'hijacked123',
]);
$this->assertSessionHasFlashError('The demo account cannot be modified.');
$this->assertEquals('admin@example.com', demoAccount()->email);
});
test('the demo account cannot change its own login details', function () {
enableDemoMode();
$this->actingAs(demoAccount())
->from('/profile')
->put('profile/login-details', [
'email' => 'hijacked@example.com',
'password' => 'hijacked123',
'password_confirmation' => 'hijacked123',
])
->assertRedirect('/profile');
$this->assertSessionHasFlashError('The demo account cannot be modified.');
$this->assertEquals('admin@example.com', demoAccount()->email);
});
test('an ordinary visitor can still change their own login details', function () {
enableDemoMode();
$user = UserFactory::user()->create();
$this->actingAs($user)
->from('/profile')
->put('profile/login-details', [
'email' => 'jane@example.com',
'password' => 'secret123',
'password_confirmation' => 'secret123',
])
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Login details updated successfully.');
$this->assertEquals('jane@example.com', $user->fresh()->email);
});
test('protected accounts can be named by email instead of username', function () {
enableDemoMode();
config(['demo.protected_users' => ['admin@example.com']]);
$this->actingAs(UserFactory::admin()->create())
->from('/users')
->delete('users/'.demoAccount()->id);
$this->assertSessionHasFlashError('The demo account cannot be modified.');
$this->assertDatabaseHas('users', ['username' => 'admin']);
});
/*
|--------------------------------------------------------------------------
| Seeded roles and permissions
|--------------------------------------------------------------------------
*/
test('a seeded role cannot be renamed', function () {
enableDemoMode();
$role = Role::where('name', 'Admin')->firstOrFail();
$this->actingAsAdmin()
->from('/roles')
->put('roles/'.$role->id, Role::factory()->raw(['name' => 'Renamed']))
->assertRedirect('/roles');
$this->assertSessionHasFlashError('Default roles and permissions cannot be modified in the demo.');
$this->assertEquals('Admin', $role->fresh()->name);
});
test('a role created during the demo can still be renamed', function () {
enableDemoMode();
$role = Role::factory()->create(['name' => 'Editor']);
$this->actingAsAdmin()
->from('/roles')
->put('roles/'.$role->id, Role::factory()->raw(['name' => 'Renamed']))
->assertRedirect('roles');
$this->assertSessionHasSuccess('Role updated successfully.');
$this->assertEquals('Renamed', $role->fresh()->name);
});
/*
|--------------------------------------------------------------------------
| Configuration
|--------------------------------------------------------------------------
*/
test('the configured message replaces every reason', function () {
enableDemoMode();
config(['demo.message' => 'Grab your own copy to try this.']);
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'Renamed']);
$this->assertSessionHasFlashError('Grab your own copy to try this.');
});
@@ -0,0 +1,73 @@
<?php
use App\Models\User;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed --class=RolesSeeder');
$this->artisan('db:seed --class=PermissionsSeeder');
});
test('unverified users cannot impersonate other users', function () {
$user = UserFactory::withPermissions('users.manage')->unverified()->create();
User::factory()->create();
$this->actingAs($user)->get('/users')->assertRedirect('/email/verify');
});
test('a user with appropriate permission can impersonate other users from a user list page', function () {
$user = UserFactory::withPermissions('users.manage')->create();
User::factory()->create();
$this->actingAs($user)->get('/users')->assertSee('Impersonate');
});
test('a user dont see impersonate button next to his name in the user list', function () {
$user = UserFactory::withPermissions('users.manage')->create();
$this->actingAs($user)->get('/user')->assertDontSee('Impersonate');
});
test('clicking on impersonate button will impersonate the user', function () {
$userA = UserFactory::withPermissions('users.manage')->create();
$userB = UserFactory::user()->create();
$this->actingAs($userA)
->get(route('impersonate', $userB))
->assertRedirect('/');
$this->assertTrue(auth()->user()->is($userB));
});
test('while impersonating user can stop impersonating by clicking on the header button', function () {
$userA = UserFactory::withPermissions('users.manage')->create();
$userB = UserFactory::user()->create();
$this->actingAs($userA)->get(route('impersonate', $userB));
$this->assertTrue(auth()->user()->is($userB));
$this->get('/')->assertInertia(fn (Assert $page) => $page
->where('auth.impersonating', true)
);
$this->get(route('impersonate.leave'));
$this->assertTrue(auth()->user()->is($userA));
});
test('while impersonating user cannot impersonate other user even if he has a permission', function () {
$userA = UserFactory::withPermissions('users.manage')->create();
$userB = UserFactory::withPermissions('users.manage')->create();
$this->actingAs($userA)->get(route('impersonate', $userB));
$this->get('/user')
->assertDontSee('Impersonate');
});
+58
View File
@@ -0,0 +1,58 @@
<?php
test('install start page is displayed', function () {
$this->get('install')
->assertOk()
->assertViewIs('install.start');
});
test('requirements page is displayed', function () {
$this->get('install/requirements')
->assertOk()
->assertViewIs('install.requirements')
->assertViewHas('requirements')
->assertViewHas('allLoaded');
});
test('permissions page redirects when requirements not met', function () {
// Hard to fake unmet requirements, so just verify the route is reachable.
$response = $this->get('install/permissions');
$this->assertContains(
$response->getStatusCode(),
[200, 302],
'install/permissions should return 200 or redirect to requirements.'
);
});
test('database page redirects when prerequisites not met', function () {
$response = $this->get('install/database');
$this->assertContains(
$response->getStatusCode(),
[200, 302],
'install/database should return 200 or redirect.'
);
});
test('installation redirects with bad db credentials', function () {
$this->post('install/start-installation', [
'host' => '127.0.0.1',
'username' => 'nope',
'password' => 'nope',
'database' => 'nope_no_database',
'prefix' => '',
])->assertRedirect();
});
test('complete page is displayed', function () {
$this->get('install/complete')
->assertOk()
->assertViewIs('install.complete');
});
test('error page is displayed', function () {
$this->get('install/error')
->assertOk()
->assertViewIs('install.error');
});
+286
View File
@@ -0,0 +1,286 @@
<?php
use App\Events\User\LoggedIn;
use App\Models\User;
use Carbon\Carbon;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Cache\RateLimiter;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Testing\TestResponse;
use Inertia\Testing\AssertableInertia as Assert;
use PragmaRX\Google2FA\Google2FA;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->setSettings([
'throttle_enabled' => false,
]);
});
test('successful login', function () {
$user = UserFactory::withCredentials('foo', 'bar')->create();
loginUser('foo', 'bar')
->assertRedirect('/');
$this->assertTrue(auth()->check());
$this->assertTrue($user->is(auth()->user()));
});
test('last login timestamp is updated after successful login', function () {
$testDate = Carbon::now();
Carbon::setTestNow($testDate);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$this->assertNull($user->last_login);
loginUser('foo', 'bar');
$this->assertEquals($testDate->timestamp, $user->fresh()->last_login->timestamp);
});
test('login with wrong credentials will fail', function () {
loginUser('foo', 'bar')
->assertRedirect('/login');
$this->assertFalse(auth()->check());
});
test('country id remains the same after login', function () {
$user = User::factory()->create([
'username' => 'foo',
'password' => 'bar',
'country_id' => 688,
]);
loginUser('foo', 'bar')
->assertRedirect('/');
$this->assertEquals(688, $user->fresh()->country_id);
});
test('throttling', function () {
$this->setSettings([
'throttle_enabled' => true,
'throttle_attempts' => 3,
'throttle_lockout_time' => 2, // 2 minutes
]);
for ($i = 0; $i < 3; $i++) {
loginUser('foo', 'bar');
}
loginUser('foo', 'bar')
->assertRedirect('login')
->assertSessionHasErrors('username');
$this->assertTrue(app(RateLimiter::class)->tooManyAttempts('foo|127.0.0.1', 3));
});
test('login with remember', function () {
$user = UserFactory::withCredentials('foo', 'bar')->create();
Setting::set('remember_me', false);
$this->get('login')
->assertInertia(fn (Assert $page) => $page
->component('auth/Login')
->where('settings.remember_me', false)
);
Setting::set('remember_me', true);
$this->get('login')
->assertInertia(fn (Assert $page) => $page
->component('auth/Login')
->where('settings.remember_me', true)
);
loginUser('foo', 'bar', true)
->assertRedirect('/');
$this->assertNotNull($user->fresh()->remember_token);
$this->assertNotNull($user->fresh()->last_login);
});
test('login max number of sessions reached', function () {
$user = UserFactory::withCredentials('foo', 'bar')->create();
Setting::set('max_active_sessions', 1);
DB::table('sessions')->insert([
'id' => Str::random(),
'user_id' => $user->id,
'ip_address' => fake()->ipv4,
'user_agent' => fake()->userAgent,
'payload' => 'test',
'last_activity' => Carbon::now()->timestamp,
]);
loginUser('foo', 'bar')
->assertRedirect('/login');
$this->assertSessionHasError(trans('auth.max_sessions_reached'));
$this->assertFalse(Auth::check());
});
test('banned user cannot log in', function () {
UserFactory::withCredentials('foo', 'bar')->banned()->create();
loginUser('foo', 'bar')
->assertRedirect('/login');
$this->assertSessionHasError('Your account is banned by administrator.');
});
test('login with 2fa enabled', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->withoutExceptionHandling();
$this->setSettings(['2fa.enabled' => true, 'throttle_enabled' => false]);
Event::fake([LoggedIn::class]);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
$validCode = $google2fa->getCurrentOtp(decrypt($user->two_factor_secret));
loginUser('foo', 'bar')
->assertRedirect('auth/two-factor-authentication')
->assertSessionHas('auth.2fa.id', $user->id);
$this->post('auth/two-factor-authentication', ['code' => $validCode])
->assertRedirect('/');
$this->assertTrue(auth()->check());
Event::assertDispatched(LoggedIn::class);
});
test('login with 2fa enabled redirect to custom page', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->withoutExceptionHandling();
$this->setSettings(['2fa.enabled' => true, 'throttle_enabled' => false]);
Event::fake([LoggedIn::class]);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
$validCode = $google2fa->getCurrentOtp(decrypt($user->two_factor_secret));
loginUser('foo', 'bar', redirectTo: 'https://google.com')
->assertRedirect('auth/two-factor-authentication')
->assertSessionHas('auth.2fa.id', $user->id);
$this->post('auth/two-factor-authentication', ['code' => $validCode])
->assertRedirect('https://google.com');
$this->assertTrue(auth()->check());
Event::assertDispatched(LoggedIn::class);
});
test('login with wrong 2fa token', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->setSettings(['2fa.enabled' => true, 'throttle_enabled' => false]);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
loginUser('foo', 'bar')
->assertRedirect('auth/two-factor-authentication')
->assertSessionHas('auth.2fa.id', $user->id);
$this->post('auth/two-factor-authentication', ['code' => '123123'])->assertRedirect('login');
$this->assertSessionHasError('Invalid 2FA token.');
});
test('login with wrong 2fa token when custom redirect page is provided', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->setSettings(['2fa.enabled' => true, 'throttle_enabled' => false]);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
loginUser('foo', 'bar', redirectTo: 'https://google.com')
->assertRedirect('auth/two-factor-authentication')
->assertSessionHas('auth.2fa.id', $user->id);
$this->post('auth/two-factor-authentication', ['code' => '123'])
->assertRedirect('login?to=https://google.com');
$this->assertSessionHasError('Invalid 2FA token.');
});
test('force password change on login', function () {
$this->setSettings(['password-change.enabled' => true]);
$user = User::factory()->create([
'username' => 'foo',
'password' => 'bar',
'force_password_change' => true,
]);
$this->assertDatabaseHas('users', [
'id' => $user->id,
'force_password_change' => true,
]);
loginUser('foo', 'bar')
->assertRedirect('/');
$this->get('/')->assertRedirect('/profile');
$this->from('/profile')
->put('/profile/login-details', [
'email' => 'john@doe.com',
'username' => 'milos',
'password' => '123123123',
'password_confirmation' => '123123123',
])
->assertRedirect('/profile');
$this->assertDatabaseHas('users', [
'id' => $user->id,
'force_password_change' => false,
]);
});
function loginUser($username, $password, $remember = false, $redirectTo = null): TestResponse
{
$url = 'login';
if ($redirectTo) {
$url .= '?to='.urlencode($redirectTo);
}
return test()->post($url, [
'username' => $username,
'password' => $password,
'remember' => $remember,
]);
}
+141
View File
@@ -0,0 +1,141 @@
<?php
use App\Mail\ResetPassword;
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->setSettings(['forgot_password' => true]);
});
test('password request form is displayed', function () {
$this->get('password/reset')
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('auth/ForgotPassword'));
});
test('reset link is sent for existing email', function () {
Mail::fake();
$user = User::factory()->create(['email' => 'foo@bar.com']);
$this->from('password/reset')
->post('password/email', ['email' => $user->email])
->assertRedirect('password/reset');
Mail::assertQueued(ResetPassword::class, fn ($mail) => $mail->hasTo($user->email));
});
test('reset link request with unknown email does not leak existence', function () {
Mail::fake();
$this->from('password/reset')
->post('password/email', ['email' => 'nobody@example.com'])
->assertRedirect('password/reset')
->assertSessionHasNoErrors()
->assertSessionHas('success', trans('passwords.sent'));
Mail::assertNothingQueued();
});
test('unknown email flashes a string the toast can render', function () {
Mail::fake();
$this->from('password/reset')
->post('password/email', ['email' => 'nobody@example.com'])
->assertSessionHas('success', fn ($success) => is_string($success));
$this->get('password/reset')
->assertInertia(fn (Assert $page) => $page->where('flash.success', trans('passwords.sent')));
});
test('a throttled reset link request reports the failure against the email field', function () {
Mail::fake();
$user = User::factory()->create(['email' => 'foo@bar.com']);
$this->post('password/email', ['email' => $user->email]);
$this->from('password/reset')
->post('password/email', ['email' => $user->email])
->assertRedirect('password/reset')
->assertSessionHasErrors(['email' => trans('passwords.throttled')]);
});
test('reset form is displayed with token', function () {
$this->get('password/reset/sometoken?email=foo@bar.com')
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('auth/ResetPassword')
->where('token', 'sometoken')
->where('email', 'foo@bar.com')
);
});
test('password can be reset with valid token', function () {
Event::fake([PasswordReset::class]);
$user = User::factory()->create(['email' => 'foo@bar.com']);
$token = Password::broker()->createToken($user);
$this->post('password/reset', [
'email' => $user->email,
'token' => $token,
'password' => 'new-password',
'password_confirmation' => 'new-password',
])->assertRedirect('/');
$this->assertTrue(Hash::check('new-password', $user->fresh()->password));
$this->assertTrue(auth()->check());
$this->assertTrue($user->is(auth()->user()));
Event::assertDispatched(PasswordReset::class);
});
test('password cannot be reset with invalid token', function () {
$user = User::factory()->create(['email' => 'foo@bar.com']);
$originalHash = $user->password;
$this->from('password/reset/invalid-token')
->post('password/reset', [
'email' => $user->email,
'token' => 'invalid-token',
'password' => 'new-password',
'password_confirmation' => 'new-password',
])
->assertRedirect('password/reset/invalid-token');
$this->assertSame($originalHash, $user->fresh()->password);
$this->assertFalse(auth()->check());
});
test('password reset requires password confirmation', function () {
$user = User::factory()->create();
$token = Password::broker()->createToken($user);
$this->from('password/reset/'.$token)
->post('password/reset', [
'email' => $user->email,
'token' => $token,
'password' => 'new-password',
'password_confirmation' => 'different',
])
->assertSessionHasErrors('password');
});
test('password reset routes unavailable when disabled', function () {
$this->setSettings(['forgot_password' => false]);
$this->get('password/reset')->assertStatus(404);
$this->post('password/email', ['email' => 'foo@bar.com'])->assertStatus(404);
});
+340
View File
@@ -0,0 +1,340 @@
<?php
use App\Events\Permission\Created;
use App\Events\Permission\Deleted;
use App\Events\Permission\Updated;
use App\Events\Role\PermissionsUpdated;
use App\Models\Permission;
use App\Models\Role;
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
$this->be(UserFactory::admin()->create());
});
test('permissions list', function () {
$permission = Permission::factory()->create();
$this->get('permissions')
->assertSee($permission->display_name);
});
test('only users with appropriate permissions can access the permissions list page', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('permissions.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)->get('/permissions')->assertStatus(403);
$this->actingAs($userB)->get('/permissions')->assertOk();
});
test('permission list with roles', function () {
$permission = Permission::factory()->create();
$role = Role::first();
$role->permissions()->attach($permission->id);
$this->get('permissions')
->assertInertia(function (Assert $page) use ($permission, $role) {
$page->component('permissions/Index');
$props = $page->toArray()['props'];
$this->assertContains(
$permission->display_name,
collect($props['permissions'])->pluck('display_name')->all()
);
// The granted matrix replaces the roles[{id}][] checkbox names.
$this->assertContains($permission->id, $props['granted'][$role->id]);
});
});
test('save role permissions', function () {
$this->withoutExceptionHandling();
Event::fake([
PermissionsUpdated::class,
]);
$permission = Permission::factory()->create();
$role = Role::factory()->create();
$this->post('permissions/save', [
'roles' => [
$role->id => [$permission->id],
],
]);
$this->assertSessionHasSuccess('Permissions saved successfully.');
$this->assertDatabaseHas('permission_role', [
'role_id' => $role->id,
'permission_id' => $permission->id,
]);
Event::assertDispatched(PermissionsUpdated::class);
});
test('only users with appropriate permissions can save role permissions', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('permissions.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$permission = Permission::factory()->create();
$role = Role::factory()->create();
$data = [$role->id => [$permission->id]];
$this->actingAs($userA)
->post('permissions/save', ['roles' => $data])
->assertStatus(403);
$this->assertDatabaseMissing('permission_role', [
'role_id' => $role->id,
'permission_id' => $permission->id,
]);
$this->actingAs($userB)
->post('permissions/save', ['roles' => $data]);
$this->assertDatabaseHas('permission_role', [
'role_id' => $role->id,
'permission_id' => $permission->id,
]);
});
test('save role permissions if no permission is selected for specific role', function () {
Event::fake([
PermissionsUpdated::class,
]);
$role1 = Role::factory()->create();
$permission1 = Permission::factory()->create();
$role1->permissions()->attach($permission1->id);
$role2 = Role::factory()->create();
$permission2 = Permission::factory()->create();
$role2->permissions()->attach($permission2->id);
$this->post('/permissions/save', [
'roles' => [
$role1->id => [$permission1->id],
],
]);
$this->assertSessionHasSuccess('Permissions saved successfully.');
$this->assertDatabaseHas('permission_role', [
'role_id' => $role1->id,
'permission_id' => $permission1->id,
]);
$this->assertDatabaseMissing('permission_role', [
'role_id' => $role2->id,
'permission_id' => $permission2->id,
]);
Event::assertDispatched(PermissionsUpdated::class);
});
test('create permission', function () {
$this->app->instance('middleware.disable', false);
Event::fake([
Created::class,
]);
$data = validPermissionParams();
$this->post('permissions', $data)
->assertRedirect('/permissions');
$this->assertSessionHasSuccess('Permission created successfully.');
$this->assertDatabaseHas('permissions', $data);
Event::assertDispatched(Created::class);
});
test('only users with appropriate permission can create new permissions', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('permissions.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = validPermissionParams();
$this->actingAs($userA)
->post('permissions', $data)
->assertStatus(403);
$this->assertDatabaseMissing('permissions', $data);
$this->actingAs($userB)
->post('permissions', $data)
->assertRedirect('/permissions');
$this->assertDatabaseHas('permissions', $data);
});
test('permission name must have valid format', function () {
$this->app->instance('middleware.disable', false);
$data = validPermissionParams();
$response = $this->post('permissions', validPermissionParams(['name' => 'invalid name']));
$response->assertSessionHasErrors('name');
$this->assertDatabaseMissing('permissions', $data);
$response = $this->post('permissions', validPermissionParams(['name' => 'invalid*name']));
$response->assertSessionHasErrors('name');
$this->assertDatabaseMissing('permissions', $data);
});
test('update permission', function () {
$this->withoutExceptionHandling();
Event::fake([
Updated::class,
]);
$permission = Permission::factory()->create();
$this->get("permissions/{$permission->id}/edit")
->assertOk()
->assertSee($permission->id)
->assertSee($permission->name)
->assertSee($permission->display_name);
$data = validPermissionParams();
$this->put("permissions/{$permission->id}", $data);
$this->assertSessionHasSuccess('Permission updated successfully.');
$this->assertDatabaseHas('permissions', $data + ['id' => $permission->id]);
Event::assertDispatched(Updated::class);
});
test('only users with appropriate permission can update existing permissions', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('permissions.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$permission = Permission::factory()->create();
$data = validPermissionParams();
$this->actingAs($userA)
->put("permissions/{$permission->id}", $data)
->assertStatus(403);
$this->assertEquals($permission->toArray(), $permission->fresh()->toArray());
$this->actingAs($userB)
->put("permissions/{$permission->id}", $data)
->assertRedirect('/permissions');
$permission->refresh();
$this->assertEquals($permission->name, $data['name']);
$this->assertEquals($permission->display_name, $data['display_name']);
$this->assertEquals($permission->description, $data['description']);
});
test('removable attribute cannot be changed on update', function () {
$permission = Permission::factory()->create(['removable' => false]);
$data = validPermissionParams(['removable' => true]);
$this->put("permissions/{$permission->id}", $data);
$permission->refresh();
$this->assertEquals($permission->name, $data['name']);
$this->assertEquals($permission->display_name, $data['display_name']);
$this->assertEquals($permission->description, $data['description']);
$this->assertFalse($permission->removable);
});
test('permission name must have valid format while updating the permission', function () {
$permission = Permission::factory()->create(['name' => 'foo']);
$response = $this->put('permissions/'.$permission->id, validPermissionParams(['name' => 'invalid name']));
$response->assertSessionHasErrors('name');
$this->assertEquals('foo', $permission->fresh()->name);
$response = $this->put('permissions/'.$permission->id, validPermissionParams(['name' => 'invalid*name']));
$response->assertSessionHasErrors('name');
$this->assertEquals('foo', $permission->fresh()->name);
});
test('delete permission', function () {
Event::fake([
Deleted::class,
]);
$permission = Permission::factory()->create();
$this->delete(route('permissions.destroy', $permission->id))
->assertRedirect('/permissions');
$this->assertSessionHasSuccess('Permission deleted successfully.');
$this->assertDatabaseMissing('permissions', ['id' => $permission->id]);
Event::assertDispatched(Deleted::class);
});
test('only users with appropriate permission can delete permissions', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('permissions.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$permission = Permission::factory()->create();
$this->actingAs($userA)
->delete("permissions/{$permission->id}")
->assertStatus(403);
$this->assertNotNull($permission->fresh());
$this->actingAs($userB)
->delete("permissions/{$permission->id}")
->assertRedirect('/permissions');
$this->assertNull($permission->fresh());
});
test('non removable permissions cannot be removed', function () {
$permission = Permission::factory()->create(['removable' => false]);
$this->delete(route('permissions.destroy', $permission->id))
->assertStatus(404);
$this->assertNotNull($permission->fresh());
});
function validPermissionParams(array $override = []): array
{
return array_merge([
'name' => 'foo_permission',
'display_name' => 'Foo Permission',
'description' => 'the description',
], $override);
}
@@ -0,0 +1,63 @@
<?php
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
config(['session.driver' => 'database']);
$this->artisan('db:seed');
});
test('user can see their active sessions', function () {
$user = User::factory()->create();
$sessionId = insertProfileSession($user);
$this->actingAs($user)
->get('profile/sessions')
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('users/Sessions')
->where('adminView', false)
->where('user.id', $user->id)
->has('sessions', 1)
->where('sessions.0.id', $sessionId)
);
});
test('user can invalidate their session', function () {
$user = User::factory()->create();
$sessionId = insertProfileSession($user);
$this->actingAs($user)
->delete("profile/sessions/{$sessionId}/invalidate")
->assertRedirect(route('profile.sessions'));
$this->assertDatabaseMissing('sessions', ['id' => $sessionId]);
});
test('guest cannot access profile sessions', function () {
$this->get('profile/sessions')->assertRedirect('login');
});
function insertProfileSession(User $user): string
{
$id = Str::random();
DB::table('sessions')->insert([
'id' => $id,
'user_id' => $user->id,
'ip_address' => fake()->ipv4,
'user_agent' => fake()->userAgent,
'payload' => 'foo',
'last_activity' => Carbon::now()->timestamp,
]);
return $id;
}
+214
View File
@@ -0,0 +1,214 @@
<?php
use App\Events\User\Approved;
use App\Mail\UserRegistered;
use App\Models\User;
use App\Support\Enum\UserStatus;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Notification;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('registration form is displayed when registration is enabled', function () {
$this->setSettings(['reg_enabled' => true]);
$this->get('register')
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('auth/Register'));
});
test('registration form exposes the captcha sitekey only when captcha is enabled', function () {
$this->setSettings(['reg_enabled' => true, 'registration.captcha.enabled' => false]);
$this->get('register')
->assertInertia(fn (Assert $page) => $page->where('captchaSitekey', null));
$this->setSettings(['registration.captcha.enabled' => true]);
config(['captcha.sitekey' => 'test-sitekey']);
$this->get('register')
->assertInertia(fn (Assert $page) => $page->where('captchaSitekey', 'test-sitekey'));
});
test('when registration is disabled a visitor cannot see the registration form', function () {
$this->setSettings(['reg_enabled' => false]);
$this->get('register')->assertStatus(404);
$this->get('login')->assertDontSee('You don\'t have an account?');
});
test('registration with approval enabled', function () {
$this->setSettings([
'reg_enabled' => true,
'approval.enabled' => true,
'reg_email_confirmation' => true,
'registration.captcha.enabled' => false,
'tos' => true,
]);
Notification::fake();
Event::fake([
Approved::class,
]);
$data = getRegistrationFormStubData();
$this->post('/register', $data)->assertRedirect('/login');
$expected = Arr::except($data, ['password', 'password_confirmation', 'tos']);
$expected += ['status' => UserStatus::UNCONFIRMED];
$this->assertDatabaseHas('users', $expected);
$user = User::where('email', $data['email'])->first();
$user->email_verified_at = now();
$user->status = UserStatus::WAITING_APPROVAL;
$user->save();
$this->actingAsAdmin()
->from("/users/{$user->id}/edit")
->put("/users/{$user->id}/update/approve")
->assertRedirect("users/{$user->id}/edit");
$expected = array_merge($expected, ['status' => UserStatus::ACTIVE]);
$this->assertDatabaseHas('users', $expected);
Event::assertDispatched(Approved::class);
Notification::assertSentTo($user, VerifyEmail::class);
});
test('registration with email confirmation', function () {
$this->setSettings([
'reg_enabled' => true,
'approval.enabled' => false,
'reg_email_confirmation' => true,
'registration.captcha.enabled' => false,
'tos' => true,
]);
Notification::fake();
$data = getRegistrationFormStubData();
$this->post('/register', $data)->assertRedirect('/');
$expected = Arr::except($data, ['password', 'password_confirmation', 'tos']);
$expected += ['status' => UserStatus::UNCONFIRMED];
$this->assertDatabaseHas('users', $expected);
$user = User::where('email', $data['email'])->first();
Notification::assertSentTo($user, VerifyEmail::class);
});
test('registration without email confirmation', function () {
$this->setSettings([
'reg_enabled' => true,
'reg_email_confirmation' => false,
'notifications_signup_email' => false,
'registration.captcha.enabled' => false,
'tos' => true,
]);
Notification::fake();
$data = getRegistrationFormStubData();
$this->post('/register', $data)->assertRedirect('/');
$expected = Arr::except($data, ['password', 'password_confirmation', 'tos']);
$expected += ['status' => UserStatus::ACTIVE];
$this->assertDatabaseHas('users', $expected);
Notification::assertNotSentTo(
User::where('email', $data['email'])->first(),
VerifyEmail::class
);
});
test('email notification is being sent when new user registers', function () {
$this->setSettings([
'app_name' => 'foo',
'reg_enabled' => true,
'reg_email_confirmation' => false,
'notifications_signup_email' => true,
'registration.captcha.enabled' => false,
'tos' => true,
]);
Mail::fake();
$admin = UserFactory::admin()->email('john.doe@test.com')->create();
$user1 = UserFactory::user()->email('jane.doe@test.com')->create();
$user2 = UserFactory::user()->email('josh.doe@test.com')->create();
$this->post('/register', getRegistrationFormStubData());
Mail::assertQueued(UserRegistered::class, 2);
});
test('redirect to custom page after login', function () {
UserFactory::withCredentials('foo', 'bar')->create();
$this->post('/login', [
'username' => 'foo',
'password' => 'bar',
'to' => 'http://www.google.com',
])->assertRedirect('http://www.google.com');
});
test('custom redirect page is available after failed login attempt', function () {
$to = 'http://www.google.com';
$this->post('/login', [
'username' => 'foo',
'password' => 'bar',
'to' => 'http://www.google.com',
])->assertRedirect('login?to='.$to);
});
test('access to auth pages is not allowed for authenticated users', function () {
$this->setSettings([
'reg_enabled' => true,
'2fa.enabled' => true,
'forgot_password' => true,
]);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$this->be($user);
$forbiddenGetRoutes = [
'login', 'register', 'password/reset', 'password/reset/123',
'auth/facebook/login', 'auth/facebook/callback',
];
foreach ($forbiddenGetRoutes as $route) {
$this->get($route)->assertRedirect('/');
}
$this->get('auth/two-factor-authentication')->assertRedirect('/login');
});
function getRegistrationFormStubData()
{
return [
'email' => 'test@test.com',
'username' => 'johndoe',
'password' => '123123123',
'password_confirmation' => '123123123',
'tos' => 1,
];
}
+207
View File
@@ -0,0 +1,207 @@
<?php
use App\Models\Role;
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('guests cannot view role list', function () {
$this->get('/roles')->assertRedirect('/login');
});
test('users without appropriate permission cannot view role list', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('roles.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)->get('/roles')->assertStatus(403);
$this->actingAs($userB)->get('/roles')->assertOk();
});
test('roles list is displayed properly', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::create();
$this->actingAsAdmin()
->get('/roles')
->assertInertia(function (Assert $page) use ($roleA, $roleB) {
// 2 default roles are created when db is seeded
$page->component('roles/Index')->has('roles', 4);
$names = collect($page->toArray()['props']['roles'])->pluck('name');
$this->assertTrue($names->contains($roleA->name));
$this->assertTrue($names->contains($roleB->name));
});
});
test('create role', function () {
$data = Role::factory()->raw();
$this->actingAsAdmin()
->post('/roles', $data)
->assertRedirect('/roles');
$this->assertSessionHasSuccess('Role created successfully.');
$this->assertDatabaseHas('roles', $data);
});
test('users without appropriate permission cannot create new roles', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('roles.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = Role::factory()->raw();
$this->actingAs($userA)
->post('/roles', $data)
->assertStatus(403);
$this->assertDatabaseMissing('roles', $data);
$this->actingAs($userB)
->post('/roles', $data)
->assertRedirect('roles');
$this->assertDatabaseHas('roles', $data);
});
test('update role', function () {
$role = Role::factory()->create(['name' => 'foo']);
$this->actingAsAdmin()
->get("roles/{$role->id}/edit")
->assertOk()
->assertSee($role->name)
->assertSee($role->display_name)
->assertSee($role->description);
$data = Role::factory()->raw();
$this->put("/roles/{$role->id}", $data);
$this->assertSessionHasSuccess('Role updated successfully.');
$this->assertDatabaseHas('roles', $data + ['id' => $role->id]);
});
test('users without appropriate permission cannot update role', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('roles.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$role = Role::factory()->create(['name' => 'foo']);
$data = Role::factory()->raw();
$this->actingAs($userA)
->put("/roles/{$role->id}", $data)
->assertStatus(403);
$this->assertEquals($role->toArray(), $role->fresh()->toArray());
$this->actingAs($userB)
->put("/roles/{$role->id}", $data)
->assertRedirect('roles');
$role->refresh();
$this->assertEquals($role->name, $data['name']);
$this->assertEquals($role->display_name, $data['display_name']);
$this->assertEquals($role->description, $data['description']);
});
test('removable attribute cannot be changed on update', function () {
$role = RoleFactory::unremovable()->create();
$data = Role::factory()->raw(['removable' => true]);
$this->actingAsAdmin()->put("/roles/{$role->id}", $data);
$role->refresh();
$this->assertEquals($role->name, $data['name']);
$this->assertEquals($role->display_name, $data['display_name']);
$this->assertEquals($role->description, $data['description']);
$this->assertFalse($role->removable);
});
test('delete role', function () {
$role = RoleFactory::removable()->create();
$this->actingAsAdmin()->delete(route('roles.destroy', $role));
$this->assertNull($role->fresh());
});
test('users without appropriate permission cannot delete role', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('roles.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$role = Role::factory()->create(['name' => 'foo']);
$this->actingAs($userA)
->delete(route('roles.destroy', $role->id))
->assertStatus(403);
$this->assertNotNull($role->fresh());
$this->actingAs($userB)
->delete(route('roles.destroy', $role->id))
->assertRedirect('roles');
$this->assertNull($role->fresh());
});
test('users receive default role after their role is deleted', function () {
$user = UserFactory::create();
$role = Role::factory()->create(['removable' => true]);
$userRole = Role::where('name', 'User')->first();
$user->setRole($role);
$this->assertTrue($user->fresh()->hasRole($role->name));
$this->actingAsAdmin()->delete(route('roles.destroy', $role));
$this->assertDatabaseHas('users', [
'role_id' => $userRole->id,
'id' => $user->id,
]);
$user = $user->fresh();
$this->assertFalse($user->hasRole($role->name));
$this->assertTrue($user->hasRole($userRole->name));
});
test('only removable roles can be deleted', function () {
$removableRole = Role::factory()->create(['removable' => true]);
$nonRemovableRole = Role::factory()->create(['removable' => false]);
$this->beAdmin();
$this->delete(route('roles.destroy', $removableRole->id));
$this->assertNull($removableRole->fresh());
$this->delete(route('roles.destroy', $nonRemovableRole->id))
->assertStatus(404);
$this->assertNotNull($nonRemovableRole->fresh());
});
@@ -0,0 +1,35 @@
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('enable two factor', function () {
Setting::set('approval.enabled', false);
$this->assertFalse(Setting::get('approval.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/approval/enable')
->assertRedirect('/settings/auth');
$this->assertTrue(Setting::get('approval.enabled'));
});
test('disable two factor', function () {
Setting::set('approval.enabled', true);
$this->assertTrue(Setting::get('approval.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/approval/disable')
->assertRedirect('/settings/auth');
$this->assertFalse(Setting::get('approval.enabled'));
});
@@ -0,0 +1,86 @@
<?php
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('auth settings page can be rendered', function () {
$this->actingAsAdmin()
->get('/settings/auth')
->assertOk();
});
test('update auth settings', function () {
Setting::set('app_name', 'bar');
$data = authSettingsData();
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth', $data)
->assertRedirect('/settings/auth');
assertAuthSettingsWereUpdated($data);
});
test('only users with appropriate permission can update auth settings', function () {
Setting::set('app_name', 'foo');
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('settings.auth')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = authSettingsData();
$data['app_name'] = 'bar';
$this->actingAs($userA)
->from('/settings/auth')
->post('/settings/auth', $data)
->assertStatus(403);
$this->assertNotEquals('bar', (string) Setting::get('app_name'));
$this->actingAs($userB)
->from('/settings/auth')
->post('/settings/auth', $data)
->assertRedirect('/settings/auth');
$this->assertEquals('bar', (string) Setting::get('app_name'));
});
function assertAuthSettingsWereUpdated(array $data)
{
test()->assertEquals((string) $data['remember_me'], (string) Setting::get('remember_me'));
test()->assertEquals((string) $data['forgot_password'], (string) Setting::get('forgot_password'));
test()->assertEquals((string) $data['login_reset_token_lifetime'], (string) Setting::get('login_reset_token_lifetime'));
test()->assertEquals((string) $data['throttle_enabled'], (string) Setting::get('throttle_enabled'));
test()->assertEquals((string) $data['throttle_attempts'], (string) Setting::get('throttle_attempts'));
test()->assertEquals((string) $data['throttle_lockout_time'], (string) Setting::get('throttle_lockout_time'));
test()->assertEquals((string) $data['reg_enabled'], (string) Setting::get('reg_enabled'));
test()->assertEquals((string) $data['tos'], (string) Setting::get('tos'));
test()->assertEquals((string) $data['reg_email_confirmation'], (string) Setting::get('reg_email_confirmation'));
}
function authSettingsData(): array
{
return [
'remember_me' => 1,
'forgot_password' => 1,
'login_reset_token_lifetime' => 123,
'throttle_enabled' => 1,
'throttle_attempts' => 10,
'throttle_lockout_time' => 2,
'reg_enabled' => 1,
'tos' => 1,
'reg_email_confirmation' => 1,
];
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('enable captcha', function () {
Setting::set('registration.captcha.enabled', false);
$this->assertFalse(Setting::get('registration.captcha.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/registration/captcha/enable')
->assertRedirect('/settings/auth');
$this->assertTrue(Setting::get('registration.captcha.enabled'));
});
test('disable two factor', function () {
Setting::set('registration.captcha.enabled', true);
$this->assertTrue(Setting::get('registration.captcha.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/registration/captcha/disable')
->assertRedirect('/settings/auth');
$this->assertFalse(Setting::get('registration.captcha.enabled'));
});
@@ -0,0 +1,48 @@
<?php
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('update general settings', function () {
Setting::set('app_name', 'bar');
$this->assertEquals('bar', Setting::get('app_name'));
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertRedirect('/settings/general');
$this->assertEquals('foo', Setting::get('app_name'));
});
test('only users with appropriate permission can update general settings', function () {
Setting::set('app_name', 'bar');
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('settings.general')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertStatus(403);
$this->assertEquals('bar', Setting::get('app_name'));
$this->actingAs($userB)
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertRedirect('/settings/general');
$this->assertEquals('foo', Setting::get('app_name'));
});
@@ -0,0 +1,86 @@
<?php
use App\Events\Settings\Updated as SettingsUpdated;
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('notifications settings page is displayed', function () {
Setting::set('notifications_signup_email', 1);
$this->actingAsAdmin()
->get('/settings/notifications')
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('settings/Notifications')
->where('values.notifications_signup_email', 1)
);
});
test('update notifications settings', function () {
Setting::set('notifications_signup_email', 0);
$this->actingAsAdmin()
->from('/settings/notifications')
->post('/settings/notifications', ['notifications_signup_email' => 1])
->assertRedirect('/settings/notifications');
$this->assertEquals(1, Setting::get('notifications_signup_email'));
});
test('updating notifications settings dispatches the settings updated event', function () {
Event::fake([SettingsUpdated::class]);
$this->actingAsAdmin()
->from('/settings/notifications')
->post('/settings/notifications', ['notifications_signup_email' => 1])
->assertRedirect('/settings/notifications');
Event::assertDispatched(SettingsUpdated::class);
});
test('only users with appropriate permission can view notifications settings', function () {
$userA = UserFactory::role(RoleFactory::create())->create();
$userB = UserFactory::role(RoleFactory::withPermissions('settings.notifications')->create())->create();
$this->actingAs($userA)->get('/settings/notifications')->assertStatus(403);
$this->actingAs($userB)->get('/settings/notifications')->assertOk();
});
test('only users with appropriate permission can update notifications settings', function () {
Setting::set('notifications_signup_email', 0);
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('settings.notifications')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)
->from('/settings/notifications')
->post('/settings/notifications', ['notifications_signup_email' => 1])
->assertStatus(403);
$this->assertEquals(0, Setting::get('notifications_signup_email'));
$this->actingAs($userB)
->from('/settings/notifications')
->post('/settings/notifications', ['notifications_signup_email' => 1])
->assertRedirect('/settings/notifications');
$this->assertEquals(1, Setting::get('notifications_signup_email'));
});
test('notifications settings are unavailable to guests', function () {
$this->get('/settings/notifications')->assertRedirect('login');
$this->post('/settings/notifications', ['notifications_signup_email' => 1])->assertRedirect('login');
});
@@ -0,0 +1,35 @@
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('enable two factor', function () {
Setting::set('2fa.enabled', false);
$this->assertFalse(Setting::get('2fa.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/2fa/enable')
->assertRedirect('/settings/auth');
$this->assertTrue(Setting::get('2fa.enabled'));
});
test('disable two factor', function () {
Setting::set('2fa.enabled', true);
$this->assertTrue(Setting::get('2fa.enabled'));
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth/2fa/disable')
->assertRedirect('/settings/auth');
$this->assertFalse(Setting::get('2fa.enabled'));
});
+109
View File
@@ -0,0 +1,109 @@
<?php
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('update general settings', function () {
Setting::set('app_name', 'bar');
$this->assertEquals('bar', Setting::get('app_name'));
$this->actingAsAdmin()
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertRedirect('/settings/general');
$this->assertEquals('foo', Setting::get('app_name'));
});
test('only users with appropriate permission can update general settings', function () {
Setting::set('app_name', 'bar');
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('settings.general')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertStatus(403);
$this->assertEquals('bar', Setting::get('app_name'));
$this->actingAs($userB)
->from('/settings/general')
->post('/settings/general', ['app_name' => 'foo'])
->assertRedirect('/settings/general');
$this->assertEquals('foo', Setting::get('app_name'));
});
test('update auth settings', function () {
Setting::set('app_name', 'bar');
$data = settingsPageAuthData();
$this->actingAsAdmin()
->from('/settings/auth')
->post('/settings/auth', $data)
->assertRedirect('/settings/auth');
assertSettingsPageAuthUpdated($data);
});
test('only users with appropriate permission can update auth settings', function () {
Setting::set('app_name', 'foo');
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('settings.auth')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = ['app_name' => 'bar'];
$this->actingAs($userA)
->from('/settings/auth')
->post('/settings/auth', $data)
->assertStatus(403);
$this->assertEquals('foo', Setting::get('app_name'));
$this->actingAs($userB)
->from('/settings/auth')
->post('/settings/auth', $data)
->assertRedirect('/settings/auth');
$this->assertEquals('bar', Setting::get('app_name'));
});
function assertSettingsPageAuthUpdated(array $data)
{
test()->assertEquals($data['remember_me'], Setting::get('remember_me'));
test()->assertEquals($data['forgot_password'], Setting::get('forgot_password'));
test()->assertEquals($data['login_reset_token_lifetime'], Setting::get('login_reset_token_lifetime'));
test()->assertEquals($data['throttle_enabled'], Setting::get('throttle_enabled'));
test()->assertEquals($data['throttle_attempts'], Setting::get('throttle_attempts'));
test()->assertEquals($data['throttle_lockout_time'], Setting::get('throttle_lockout_time'));
}
function settingsPageAuthData(): array
{
return [
'remember_me' => 1,
'forgot_password' => 1,
'login_reset_token_lifetime' => 123,
'throttle_enabled' => 1,
'throttle_attempts' => 10,
'throttle_lockout_time' => 2,
];
}
@@ -0,0 +1,206 @@
<?php
use App\Models\User;
use App\Support\Enum\UserStatus;
use Carbon\Carbon;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Socialite\Contracts\Provider as SocialiteProvider;
use Laravel\Socialite\Contracts\User as SocialUserContract;
use Mockery as m;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('social login for new user', function () {
$this->setSettings(['reg_enabled' => true]);
$socialUser = new StubSocialUser;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
$this->get('auth/foo/callback');
$this->assertDatabaseHas('users', [
'username' => null,
'email' => $socialUser->getEmail(),
'first_name' => 'John',
'last_name' => 'Doe',
'status' => UserStatus::ACTIVE,
'email_verified_at' => now(),
]);
$user = User::where('email', $socialUser->getEmail())->first();
$this->assertDatabaseHas('social_logins', [
'user_id' => $user->id,
'provider' => 'foo',
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]);
$this->assertEquals($user->id, Auth::id());
});
test('social login for new user if registration is disabled', function () {
$this->setSettings(['reg_enabled' => false]);
$socialUser = new StubSocialUser;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
$this->get('auth/foo/callback')
->assertRedirect('/login');
$this->assertSessionHasError('Only users who already created an account can log in.');
});
test('social login for banned user', function () {
$user = UserFactory::banned()->create();
$socialUser = new StubSocialUser;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
DB::table('social_logins')->insert([
'user_id' => $user->id,
'provider' => 'foo',
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
'created_at' => Carbon::now(),
]);
$this->get('auth/foo/callback')
->assertRedirect('/login');
$this->assertSessionHasError('Your account is banned by administrator.');
});
test('social login for existing user', function () {
$user = User::factory()->create();
$socialUser = new StubSocialUser;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
DB::table('social_logins')->insert([
'user_id' => $user->id,
'provider' => 'foo',
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
'created_at' => Carbon::now(),
]);
$this->get('auth/foo/callback')
->assertRedirect('/');
$this->assertEquals($user->id, Auth::id());
});
test('missing email login', function () {
$this->setSettings(['reg_enabled' => true]);
$socialUser = new StubSocialUserWithoutEmail;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
$this->get('auth/foo/callback')
->assertRedirect('login');
$this->assertSessionHasError('You have to provide your email address.');
});
test('social login for user with one word name', function () {
$this->setSettings(['reg_enabled' => true]);
$socialUser = new StubSocialUserWithOneWordName;
$driver = m::mock(SocialiteProvider::class);
$driver->shouldReceive('user')->andReturn($socialUser);
Socialite::shouldReceive('driver')->with('foo')->andReturn($driver);
$this->get('auth/foo/callback')
->assertRedirect('/');
$this->assertDatabaseHas('users', [
'username' => null,
'email' => $socialUser->getEmail(),
'first_name' => 'John',
'last_name' => '',
'status' => UserStatus::ACTIVE,
]);
$user = User::where('email', $socialUser->getEmail())->first();
$this->assertDatabaseHas('social_logins', [
'user_id' => $user->id,
'provider' => 'foo',
'provider_id' => $socialUser->getId(),
'avatar' => $socialUser->getAvatar(),
]);
$this->assertEquals($user->id, Auth::id());
});
class StubSocialUser implements SocialUserContract
{
public function getId()
{
return '123';
}
public function getNickname()
{
return 'johndoe';
}
public function getName()
{
return 'John Doe';
}
public function getEmail()
{
return 'john@doe.com';
}
public function getAvatar()
{
return 'http://www.gravatar.com/avatar';
}
}
class StubSocialUserWithoutEmail extends StubSocialUser
{
public $email = null;
public function getEmail()
{
return $this->email;
}
}
class StubSocialUserWithOneWordName extends StubSocialUser
{
public function getName()
{
return 'John';
}
}
+235
View File
@@ -0,0 +1,235 @@
<?php
use App\Events\User\TwoFactorDisabled;
use App\Events\User\TwoFactorEnabled;
use App\Models\User;
use Carbon\Carbon;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PragmaRX\Google2FA\Google2FA;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->artisan('db:seed');
});
test('the 2fa form is visible on profile page if 2fa is enabled', function () {
config(['services.authy.key' => 'test']);
$this->setSettings(['2fa.enabled' => false]);
// The tab is rendered client side from the shared setting, so that flag
// is what decides whether it appears rather than the markup.
$this->assertFalse(twoFactorAuthSetting('profile'));
$this->setSettings(['2fa.enabled' => true]);
$this->assertTrue(twoFactorAuthSetting('profile'));
});
test('the 2fa form is visible on edit user page if 2fa is enabled', function () {
config(['services.authy.key' => 'test']);
$this->setSettings(['2fa.enabled' => false]);
$user = UserFactory::create();
$this->assertFalse(twoFactorAuthSetting("/users/{$user->id}/edit"));
$this->setSettings(['2fa.enabled' => true]);
$this->assertTrue(twoFactorAuthSetting("/users/{$user->id}/edit"));
});
/**
* The `2fa.enabled` shared prop, which drives the Two-Factor tab.
*/
function twoFactorAuthSetting(string $url): bool
{
return (bool) test()->actingAsAdmin()
->get($url)
->viewData('page')['props']['settings']['2fa.enabled'];
}
test('enable 2fa from profile page', function () {
$this->setSettings(['2fa.enabled' => true]);
$user = UserFactory::user()->create();
$this->actingAs($user)
->post('/two-factor/enable')
->assertRedirect('/')
->assertSessionHas('tab', '2fa');
$this->assertTrue(
DB::table('users')
->where('id', $user->id)
->whereNotNull('two_factor_secret')
->exists()
);
});
test('enable 2fa from edit user page', function () {
$this->setSettings(['2fa.enabled' => true]);
$user = UserFactory::user()->create();
$formData = ['user' => $user->id];
$this->actingAsAdmin()
->post("users/{$user->id}/two-factor/enable", $formData)
->assertRedirect('/')
->assertSessionHas('tab', '2fa');
$this->assertTrue(
DB::table('users')
->where('id', $user->id)
->whereNotNull('two_factor_secret')
->exists()
);
});
test('users without appropriate permissions cannot enable 2fa for other users', function () {
$this->setSettings(['2fa.enabled' => true]);
$this->be(UserFactory::user()->create());
$user = UserFactory::user()->create();
$this->post('two-factor/enable', [
'user' => $user->id,
])->assertStatus(403);
$this->assertTrue(
DB::table('users')
->where('id', $user->id)
->whereNull('two_factor_secret')
->exists()
);
});
test('code field is required during 2fa verification', function () {
$this->setSettings(['2fa.enabled' => true]);
$user = UserFactory::user()->create();
$user->two_factor_secret = '123123';
$user->save();
$this->actingAs($user)
->post('two-factor/verify')
->assertSessionHasErrors('code');
});
test('the 2fa verification with wrong token will fail', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->withoutExceptionHandling();
$this->setSettings(['2fa.enabled' => true]);
$user = UserFactory::user()->create();
$user->two_factor_secret = $secret;
$user->save();
$this->actingAs($user)
->post('two-factor/verify', ['code' => '123123']);
$this->assertSessionHasError('Invalid 2FA token.');
});
test('successful 2fa code verification', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->setSettings(['2fa.enabled' => true]);
Event::fake([
TwoFactorEnabled::class,
]);
$user = UserFactory::user()->create();
$user->two_factor_secret = $secret;
$user->save();
$validCode = $google2fa->getCurrentOtp(decrypt($user->two_factor_secret));
$this->actingAs($user)
->post('two-factor/verify', ['code' => $validCode])
->assertRedirect('/profile');
$this->assertDatabaseHas('users', [
'id' => $user->id,
'two_factor_secret' => $secret,
]);
Event::assertDispatched(TwoFactorEnabled::class);
});
test('user can disable 2fa', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->setSettings(['2fa.enabled' => true]);
$user = UserFactory::user()->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
$this->be($user);
Event::fake([
TwoFactorDisabled::class,
]);
$this->from('/profile')
->post('two-factor/disable')
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Two-Factor Authentication disabled successfully.');
Event::assertDispatched(TwoFactorDisabled::class);
});
test('user can disable 2fa for another user', function () {
$google2fa = new Google2FA;
$secret = encrypt($google2fa->generateSecretKey());
$this->setSettings(['2fa.enabled' => true]);
Event::fake([
TwoFactorDisabled::class,
]);
$this->beAdmin();
$user = UserFactory::user()->create();
$user->two_factor_secret = $secret;
$user->two_factor_confirmed_at = Carbon::now();
$user->save();
$this->from("/users/{$user->id}/edit")
->post("/users/{$user->id}/two-factor/disable", ['user' => $user->id])
->assertRedirect("/users/{$user->id}/edit");
$this->assertDatabaseHas('users', [
'id' => $user->id,
'two_factor_secret' => null,
]);
$this->assertSessionHasSuccess('Two-Factor Authentication disabled successfully.');
Event::assertDispatched(TwoFactorDisabled::class);
});
test('user without appropriate permissions cannot disable 2fa for another user', function () {
$this->setSettings(['2fa.enabled' => true]);
$this->be(UserFactory::user()->create());
$user = User::factory()->create();
$this->post('two-factor/disable', ['user' => $user->id])->assertForbidden();
});
+226
View File
@@ -0,0 +1,226 @@
<?php
use App\Events\User\ChangedAvatar;
use App\Events\User\UpdatedProfileDetails;
use App\Models\Role;
use App\Support\Enum\UserStatus;
use Carbon\Carbon;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->be(UserFactory::create());
});
test('test user can access his profile page', function () {
$this->get('/profile')->assertOk();
});
test('test user can update his profile details', function () {
Event::fake([
UpdatedProfileDetails::class,
]);
$data = getStubDetailsData();
$this->from('/profile')
->put('profile/details', $data)
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Profile updated successfully.');
$this->assertDatabaseHas('users', $data + ['id' => auth()->id()]);
Event::assertDispatched(UpdatedProfileDetails::class);
});
test('test user cannot change his status while updating the profile', function () {
$data = getStubDetailsData();
$this->from('/profile')
->put('profile/details', $data + ['status' => UserStatus::BANNED->value])
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Profile updated successfully.');
$this->assertDatabaseHas('users', $data + [
'id' => auth()->id(),
'status' => UserStatus::ACTIVE,
]);
});
test('test user cannot change his role while updating the profile', function () {
$data = getStubDetailsData();
$role = Role::factory()->create();
$this->from('/profile')
->put('profile/details', $data + ['role_id' => $role])
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Profile updated successfully.');
$this->assertNotEquals($role->id, auth()->user()->role_id);
});
test('test update avatar', function () {
Event::fake([
ChangedAvatar::class,
]);
Storage::fake('public');
$data = [
'avatar' => UploadedFile::fake()->image('photo1.jpg', 300, 300),
'points' => [
'x1' => 0,
'y1' => 0,
'x2' => 200,
'y2' => 200,
],
];
$this->from('profile')
->post('/profile/avatar', $data)
->assertRedirect('profile');
$this->assertSessionHasSuccess('Avatar changed successfully.');
$user = auth()->user()->fresh();
$this->assertNotNull($user->avatar);
Storage::disk('public')->assertExists("upload/users/{$user->avatar}");
[$width, $height] = getimagesizefromstring(
Storage::disk('public')->get("upload/users/{$user->avatar}")
);
$this->assertEquals(160, $width);
$this->assertEquals(160, $height);
Event::assertDispatched(ChangedAvatar::class);
});
test('test update avatar with invalid image file', function () {
Storage::fake('public');
$data = [
'avatar' => UploadedFile::fake()->create('foo.txt', 123),
'points' => [
'x1' => 0,
'y1' => 0,
'x2' => 200,
'y2' => 200,
],
];
$this->from('profile')
->post('/profile/avatar', $data)
->assertRedirect('profile')
->assertSessionHasErrors('avatar');
$user = auth()->user()->fresh();
Storage::disk('public')->assertMissing("upload/users/{$user->avatar}");
$this->assertNull($user->avatar);
});
test('test update avatar external', function () {
Event::fake([
ChangedAvatar::class,
]);
$data = ['url' => '//www.gravatar.com/avatar'];
$this->post(route('profile.update.avatar-external', auth()->id()), $data)
->assertRedirect();
$this->assertSessionHasSuccess('Avatar changed successfully.');
$this->assertEquals($data['url'], auth()->user()->fresh()->avatar);
Event::assertDispatched(ChangedAvatar::class);
});
test('test update user login details', function () {
$data = [
'email' => 'john@doe.com',
'username' => 'milos',
'password' => 'milos123123',
'password_confirmation' => 'milos123123',
];
$this->from('/profile')
->put('profile/login-details', $data)
->assertRedirect('/profile');
$this->assertSessionHasSuccess('Login details updated successfully.');
$user = auth()->user()->fresh();
$this->assertEquals($data['email'], $user->email);
$this->assertEquals($data['username'], $user->username);
$this->assertTrue(Hash::check($data['password'], $user->password));
});
test('test password is not changed if omited on update', function () {
auth()->user()->update([
'email' => 'john@example.com',
'password' => '123123',
]);
$data = [
'email' => 'test@test.com',
'password' => '',
'password_confirmation' => '',
];
$this->from('/profile')
->put('profile/login-details', $data)
->assertRedirect('/profile');
$user = auth()->user()->fresh();
$this->assertEquals($data['email'], $user->email);
$this->assertTrue(Hash::check('123123', $user->password));
});
test('test user session invalidation', function () {
$this->withoutExceptionHandling();
config(['session.driver' => 'database']);
$user = UserFactory::withCredentials('foo', 'bar')->create();
$this->be($user);
$agent = $this->app['agent'];
$device = $agent->device() ?: 'Unknown';
$platform = $agent->platform() ?: 'Unknown';
// Log-in manually to actually create session record in DB
$this->post('/login', ['username' => 'foo', 'password' => 'bar']);
$this->get('/profile/sessions')
->assertSee('127.0.0.1')
->assertSee($device)
->assertSee($platform)
->assertSee($agent->browser());
$sessionId = DB::table('sessions')->where('user_id', $user->id)->first()->id;
$this->delete("profile/sessions/{$sessionId}/invalidate");
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
});
function getStubDetailsData(): array
{
return [
'first_name' => 'foo',
'last_name' => 'bar',
'birthday' => Carbon::now()->subYears(25)->format('Y-m-d'),
'phone' => '12345667',
'address' => 'the address',
'country_id' => 688, // Serbia,
];
}
@@ -0,0 +1,73 @@
<?php
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
uses(RefreshDatabase::class);
beforeEach(function () {
config(['session.driver' => 'database']);
$this->artisan('db:seed');
});
test('admin can view user sessions', function () {
$user = User::factory()->create();
$sessionId = insertUserSession($user);
$this->actingAsAdmin()
->get("users/{$user->id}/sessions")
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('users/Sessions')
->where('adminView', true)
->has('sessions', 1)
->where('sessions.0.id', $sessionId)
);
});
test('admin can invalidate user session', function () {
$user = User::factory()->create();
$sessionId = insertUserSession($user);
$this->actingAsAdmin()
->delete("users/{$user->id}/sessions/{$sessionId}/invalidate")
->assertRedirect(route('user.sessions', $user->id));
$this->assertDatabaseMissing('sessions', ['id' => $sessionId]);
});
test('non admin cannot view user sessions', function () {
$admin = User::factory()->create();
$user = User::factory()->create();
$this->actingAs($user)
->get("users/{$admin->id}/sessions")
->assertStatus(403);
});
test('guest cannot access user sessions', function () {
$user = User::factory()->create();
$this->get("users/{$user->id}/sessions")->assertRedirect('login');
});
function insertUserSession(User $user): string
{
$id = Str::random();
DB::table('sessions')->insert([
'id' => $id,
'user_id' => $user->id,
'ip_address' => fake()->ipv4,
'user_agent' => fake()->userAgent,
'payload' => 'foo',
'last_activity' => Carbon::now()->timestamp,
]);
return $id;
}
+644
View File
@@ -0,0 +1,644 @@
<?php
use App\Events\User\UpdatedByAdmin;
use App\Models\Role;
use App\Models\User;
use App\Support\Enum\UserStatus;
use Carbon\Carbon;
use Facades\Tests\Setup\RoleFactory;
use Facades\Tests\Setup\UserFactory;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
beforeEach(function () {
$this->artisan('db:seed', ['--class' => 'RolesSeeder']);
$this->artisan('db:seed', ['--class' => 'PermissionsSeeder']);
$this->artisan('db:seed', ['--class' => 'CountriesSeeder']);
});
test('guests cannot view the user list page', function () {
$this->get('/users')->assertRedirect('/login');
});
test('users without appropriate permission cannot view user list page', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)->get('/users')->assertForbidden();
$this->actingAs($userB)->get('/users')->assertOk();
});
test('user collection is being properly passed to the view', function () {
$admin = UserFactory::admin()->create();
$active = UserFactory::user()->create();
$banned = UserFactory::user()->banned()->create();
$unconfirmed = UserFactory::user()->unconfirmed()->create();
$ids = $this->actingAs($admin)->get('users')->viewData('page')['props']['users']['data'];
$ids = collect($ids)->pluck('id')->all();
$this->assertCount(4, $ids);
$this->assertContains($admin->id, $ids);
$this->assertContains($active->id, $ids);
$this->assertContains($banned->id, $ids);
$this->assertContains($unconfirmed->id, $ids);
});
test('user list is paginated', function () {
$this->beAdmin();
$this->get('users')->assertInertia(function (Assert $page) {
$page->component('users/Index')->has('users.data', 1);
$paginator = $page->toArray()['props']['users'];
$this->assertArrayHasKey('links', $paginator);
$this->assertSame(1, $paginator['current_page']);
});
});
test('users can be filtered out by search term', function () {
User::factory()->create(['first_name' => 'Milos', 'last_name' => 'Stojanovic']);
$user2 = User::factory()->create(['first_name' => 'John', 'last_name' => 'Doe']);
$user3 = User::factory()->create(['first_name' => 'Jane', 'last_name' => 'Doe']);
$ids = $this->actingAsAdmin()->get('users?search=doe')->viewData('page')['props']['users']['data'];
$ids = collect($ids)->pluck('id')->all();
$this->assertCount(2, $ids);
$this->assertContains($user3->id, $ids);
$this->assertContains($user2->id, $ids);
});
test('users can be filtered out by status', function () {
User::factory()->times(2)->create();
User::factory()->times(3)->create(['status' => UserStatus::UNCONFIRMED]);
$ids = $this->actingAsAdmin()
->get('users?status='.UserStatus::BANNED->value)
->viewData('page')['props']['users']['data'];
$ids = collect($ids)->pluck('id')->all();
$this->assertCount(0, $ids);
});
test('admin can successfully create new users', function () {
$data = validUserParams();
$this->actingAsAdmin()
->post('/users', $data)
->assertRedirect('/users');
$this->assertSessionHasSuccess('User created successfully.');
$user = User::where('email', $data['email'])->first();
$this->assertDatabaseHas('users', Arr::except($data, ['password', 'password_confirmation']));
$this->assertTrue(Hash::check('123123123', $user->password));
});
test('users without appropriate permissions cannot create new users', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = validUserParams();
$this->actingAs($userA)->post('/users', $data)->assertForbidden();
$this->assertNUll(User::where('email', $data['email'])->first());
$this->actingAs($userB)->post('/users', $data)->assertRedirect('/users');
$this->assertNotNull(User::where('email', $data['email'])->first());
});
test('email field is required while creating a user', function () {
$data = Arr::except(validUserParams(), ['email']);
$this->actingAsAdmin()
->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('email');
});
test('email field must be a valid email while creating a user', function () {
$data = validUserParams(['email' => 'foo']);
$this->actingAsAdmin()
->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('email');
});
test('email must be unique while creating a user', function () {
$this->beAdmin();
UserFactory::user()->email('john@example.com')->create();
$data = validUserParams(['email' => 'john@example.com']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('email');
});
test('username is not required', function () {
$this->beAdmin();
$data = validUserParams();
unset($data['username']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users');
$this->assertNotNull(User::where('email', $data['email'])->first());
});
test('username must be unique while creating a user', function () {
$this->beAdmin();
User::factory()->create(['username' => 'johndoe']);
$data = validUserParams(['username' => 'johndoe']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('username');
});
test('password is required while creating a user', function () {
$this->beAdmin();
$data = validUserParams();
unset($data['password']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('password');
});
test('password must be at least 6 characters long while creating a user', function () {
$this->beAdmin();
$data = validUserParams(['password' => '12345']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('password');
});
test('password must be confirmed while creating a user', function () {
$this->beAdmin();
$data = validUserParams();
unset($data['password_confirmation']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('password');
});
test('birthday must be a valid date while creating a user', function () {
$this->beAdmin();
$data = validUserParams(['birthday' => 'foo']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('birthday');
});
test('role field is required while creating a user', function () {
$this->beAdmin();
$data = validUserParams();
unset($data['role_id']);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('role_id');
});
test('selected role must exist inside the system while creating a user', function () {
$this->beAdmin();
$data = validUserParams(['role_id' => 123]);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('role_id');
});
test('country id field is not required while creating a user', function () {
$data = validUserParams(['country_id' => 0]);
$this->actingAsAdmin()
->post('/users', $data)
->assertRedirect('/users');
$this->assertSessionHasSuccess('User created successfully.');
$user = User::where('email', $data['email'])->first();
$expected = Arr::except($data, ['password', 'password_confirmation']);
$expected['country_id'] = null;
$this->assertDatabaseHas('users', $expected);
$this->assertTrue(Hash::check('123123123', $user->password));
});
test('country id must exist inside the system while creating a user', function () {
$this->beAdmin();
$data = validUserParams(['country_id' => 12345]);
$this->from('/users')
->post('/users', $data)
->assertRedirect('/users')
->assertSessionHasErrors('country_id');
});
test('admin can view users profile', function () {
$user = UserFactory::user()->create();
$this->actingAsAdmin()->get("users/{$user->id}")->assertOk();
});
test('users without appropriate permissions cannot view profile for another user', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)->get("users/{$userB->id}")->assertForbidden();
$this->actingAs($userB)->get("users/{$userA->id}")->assertOk();
});
test('update user details', function () {
Event::fake([
UpdatedByAdmin::class,
]);
$this->beAdmin();
$user = UserFactory::user()->create();
$data = validUserParams();
$this->from("users/{$user->id}/edit")
->put("/users/{$user->id}/update/details", $data)
->assertRedirect("users/{$user->id}/edit");
$expected = Arr::except($data, ['password', 'password_confirmation']);
$this->assertDatabaseHas('users', $expected + ['id' => $user->id]);
$this->assertSessionHasSuccess('User updated successfully.');
Event::assertDispatched(UpdatedByAdmin::class);
});
test('users without appropriate permissions cannot update other users', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = validUserParams();
$this->actingAs($userA)
->from("users/{$userB->id}/edit")
->put("/users/{$userB->id}/update/details", $data)
->assertForbidden();
$this->assertNotEquals($data['email'], $userB->fresh()->email);
$this->actingAs($userB)
->from("users/{$userA->id}/edit")
->put("/users/{$userA->id}/update/details", $data)
->assertRedirect("users/{$userA->id}/edit");
$this->assertEquals($data['email'], $userA->fresh()->email);
});
test('banning a user will invalidate all his sessions', function () {
config(['session.driver' => 'database']);
$this->beAdmin();
$user = UserFactory::user()->create(['remember_token' => Str::random(60)]);
DB::table('sessions')->insert([
'id' => Str::random(40),
'user_id' => $user->id,
'ip_address' => '127.0.0.1',
'user_agent' => 'Foo',
'payload' => Str::random(),
'last_activity' => Carbon::now()->subMinute()->timestamp,
]);
$data = validUserParams(['status' => UserStatus::BANNED->value]);
$this->put("/users/{$user->id}/update/details", $data);
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
$this->assertNull($user->fresh()->remember_token);
});
test('admin can update users login details', function () {
$this->beAdmin();
$user = UserFactory::user()->create();
$data = [
'email' => 'john@doe.com',
'username' => 'milos',
'password' => '123123123',
'password_confirmation' => '123123123',
];
$this->put("users/{$user->id}/update/login-details", $data)
->assertRedirect("users/{$user->id}/edit");
$this->assertSessionHasSuccess('Login details updated successfully.');
$user = $user->fresh();
$this->assertEquals($data['email'], $user->email);
$this->assertEquals($data['username'], $user->username);
$this->assertTrue(Hash::check($data['password'], $user->password));
});
test('users without appropriate permissions cannot update other users login details', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = [
'email' => 'john@doe.com',
'username' => 'milos',
'password' => '123123123',
'password_confirmation' => '123123123',
];
$this->actingAs($userA)
->from("users/{$userB->id}/edit")
->put("users/{$userB->id}/update/login-details", $data)
->assertForbidden();
$this->assertNotEquals($data['email'], $userB->fresh()->email);
$this->actingAs($userB)
->from("users/{$userA->id}/edit")
->put("users/{$userA->id}/update/login-details", $data)
->assertRedirect("users/{$userA->id}/edit");
$this->assertEquals($data['email'], $userA->fresh()->email);
});
test('two factor form visibility', function () {
config(['services.authy.key' => 'test']);
$this->setSettings(['2fa.enabled' => false]);
$user = UserFactory::user()->create();
// The tab is rendered client side from the shared setting, so that is
// what decides whether it appears rather than the markup.
$this->assertFalse(usersTwoFactorSetting("users/{$user->id}/edit"));
$this->setSettings(['2fa.enabled' => true]);
$this->assertTrue(usersTwoFactorSetting("users/{$user->id}/edit"));
});
function usersTwoFactorSetting(string $url): bool
{
return (bool) test()->actingAsAdmin()
->get($url)
->viewData('page')['props']['settings']['2fa.enabled'];
}
test('admin can update avatar on behalf of a user', function () {
Storage::fake('public');
$data = [
'avatar' => UploadedFile::fake()->image('photo1.jpg', 300, 300),
'points' => [
'x1' => 0,
'y1' => 0,
'x2' => 200,
'y2' => 200,
],
];
$user = UserFactory::user()->create();
$this->actingAsAdmin()
->from("users/{$user->id}/edit")
->post("users/{$user->id}/update/avatar", $data)
->assertRedirect("users/{$user->id}/edit");
$this->assertSessionHasSuccess('Avatar changed successfully.');
$user->refresh();
$this->assertNotNull($user->avatar);
Storage::disk('public')->assertExists("upload/users/{$user->avatar}");
[$width, $height] = getimagesizefromstring(
Storage::disk('public')->get("upload/users/{$user->avatar}")
);
$this->assertEquals(160, $width);
$this->assertEquals(160, $height);
});
test('admin can update avatar on behalf of a user only if valid file is selected', function () {
Storage::fake('public');
$data = [
'avatar' => UploadedFile::fake()->create('foo.txt', 123),
'points' => [
'x1' => 0,
'y1' => 0,
'x2' => 200,
'y2' => 200,
],
];
$user = UserFactory::user()->create();
$this->actingAsAdmin()
->from("users/{$user->id}/edit")
->post("users/{$user->id}/update/avatar", $data)
->assertRedirect("users/{$user->id}/edit");
$this->assertNull($user->fresh()->avatar);
});
test('users without appropriate permissions cannot update avatar for other users', function () {
Storage::fake('public');
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$data = [
'avatar' => UploadedFile::fake()->image('photo1.jpg', 300, 300),
'points' => [
'x1' => 0,
'y1' => 0,
'x2' => 200,
'y2' => 200,
],
];
$this->actingAs($userA)
->from("users/{$userB->id}/edit")
->post("users/{$userB->id}/update/avatar", $data)
->assertForbidden();
$this->assertNull($userB->fresh()->avatar);
$this->actingAs($userB)
->from("users/{$userA->id}/edit")
->post("users/{$userA->id}/update/avatar", $data)
->assertRedirect("users/{$userA->id}/edit");
$this->assertNotNull($userA->fresh()->avatar);
});
test('session page is not available for non database driver', function () {
config(['session.driver' => 'array']);
$user = UserFactory::admin()->create();
$this->actingAs($user)
->get('users')
->assertDontSee('User Sessions');
// this page should not be accessible if
// database session driver is not being used
$this->get("users/{$user->id}/sessions")->assertNotFound();
});
test('invalidate session', function () {
config(['session.driver' => 'database']);
Carbon::setTestNow(Carbon::now());
$user = UserFactory::admin()->withCredentials('foo', 'bar')->create();
$agent = $this->app['agent'];
$device = $agent->device() ?: 'Unknown';
$platform = $agent->platform() ?: 'Unknown';
$browser = $agent->browser() ?: 'Unknown';
// Log-in manually to actually create session record in DB
$this->post('/login', ['username' => 'foo', 'password' => 'bar']);
$sessions = $this->get("users/{$user->id}/sessions")
->viewData('page')['props']['sessions'];
$this->assertCount(1, $sessions);
$this->assertSame('127.0.0.1', $sessions[0]['ipAddress']);
$this->assertSame($device, $sessions[0]['device']);
$this->assertSame($platform, $sessions[0]['platform']);
$this->assertSame($browser, $sessions[0]['browser']);
$sessionId = DB::table('sessions')->where('user_id', $user->id)->first()->id;
$this->delete("users/{$user->id}/sessions/{$sessionId}/invalidate");
$this->assertDatabaseMissing('sessions', ['user_id' => $user->id]);
});
test('admins can delete other users', function () {
$user = UserFactory::user()->create();
$this->actingAsAdmin()
->delete(route('users.destroy', $user))
->assertRedirect('users');
$this->assertSessionHasSuccess('User deleted successfully.');
$this->assertNull($user->fresh());
});
test('users without appropriate permissions cannot delete other users', function () {
$roleA = RoleFactory::create();
$roleB = RoleFactory::withPermissions('users.manage')->create();
$userA = UserFactory::role($roleA)->create();
$userB = UserFactory::role($roleB)->create();
$this->actingAs($userA)
->delete(route('users.destroy', $userB))
->assertForbidden();
$this->assertNotNull($userB->fresh());
$this->actingAs($userB)
->delete(route('users.destroy', $userA))
->assertRedirect('/users');
$this->assertNull($userA->fresh());
});
test('user cannot delete himself', function () {
$this->beAdmin();
$this->delete(route('users.destroy', auth()->id()))
->assertRedirect('/users');
$this->assertNotNull(auth()->user()->fresh());
$this->assertSessionHasError('You cannot delete yourself.');
});
function validUserParams(array $overrides = []): array
{
return array_merge([
'role_id' => Role::whereName('User')->first()->id,
'status' => UserStatus::ACTIVE->value,
'first_name' => 'foo',
'last_name' => 'bar',
'birthday' => Carbon::now()->subYears(25)->format('Y-m-d'),
'phone' => '12345667',
'address' => 'the address',
'country_id' => 688, // Serbia,
'email' => 'john@doe.com',
'username' => 'johndoe',
'password' => '123123123',
'password_confirmation' => '123123123',
], $overrides);
}
+91
View File
@@ -0,0 +1,91 @@
<?php
use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\URL;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\UpdatesSettings;
uses(RefreshDatabase::class);
uses(UpdatesSettings::class);
test('unverified user sees verification notice', function () {
$user = User::factory()->create(['email_verified_at' => null]);
$this->actingAs($user)
->get('email/verify')
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('auth/VerifyEmail'));
});
test('already verified user is redirected from notice page', function () {
$user = User::factory()->create(['email_verified_at' => Carbon::now()]);
$this->actingAs($user)
->get('email/verify')
->assertRedirect('/');
});
test('signed verification link marks email as verified', function () {
Event::fake([Verified::class]);
$user = User::factory()->create(['email_verified_at' => null]);
$signedUrl = URL::temporarySignedRoute(
'verification.verify',
Carbon::now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1($user->email)]
);
$this->actingAs($user)
->get($signedUrl)
->assertRedirect('/');
$this->assertNotNull($user->fresh()->email_verified_at);
Event::assertDispatched(Verified::class);
});
test('verification link with invalid signature fails', function () {
$user = User::factory()->create(['email_verified_at' => null]);
$this->actingAs($user)
->get(route('verification.verify', ['id' => $user->id, 'hash' => sha1($user->email)]))
->assertStatus(403);
$this->assertNull($user->fresh()->email_verified_at);
});
test('resend verification email', function () {
Notification::fake();
$user = User::factory()->create(['email_verified_at' => null]);
$this->actingAs($user)
->post('email/resend')
->assertRedirect();
Notification::assertSentTo($user, VerifyEmail::class);
});
test('resend does nothing for already verified user', function () {
Notification::fake();
$user = User::factory()->create(['email_verified_at' => Carbon::now()]);
$this->actingAs($user)
->post('email/resend')
->assertRedirect('/');
Notification::assertNothingSent();
});
test('guest cannot access verification routes', function () {
$this->get('email/verify')->assertRedirect('login');
$this->post('email/resend')->assertRedirect('login');
});