Files
ppeasy_vg12/tests/Feature/Web/DemoModeTest.php
T
2026-10-09 08:21:06 +02:00

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.');
});