376 lines
12 KiB
PHP
376 lines
12 KiB
PHP
<?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.');
|
|
});
|