136 lines
5.7 KiB
PHP
136 lines
5.7 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\Settings;
|
|
use App\Support\Navigation;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* The settings page is tabbed, and the tab survives a reload.
|
|
*
|
|
* It had grown to three headed sections in one column with the operator's own
|
|
* password somewhere in the middle. Which part somebody wants is a property of
|
|
* what they came to do, not of how far they scroll — and the installation tab
|
|
* refreshes itself while an update runs, so a tab kept only in the component
|
|
* would drop them back to the first one mid-deployment.
|
|
*/
|
|
it('opens the installation tab by default and keeps it out of the address bar', function () {
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Settings::class)
|
|
->assertSet('tab', 'installation')
|
|
->assertSee(__('admin_settings.update_title'));
|
|
});
|
|
|
|
it('remembers the tab across a reload, because it is in the URL', function () {
|
|
// The whole point of #[Url]. Without it a refresh — or the browser's back
|
|
// button — lands on the first tab every time.
|
|
Livewire::withQueryParams(['tab' => 'account'])
|
|
->actingAs(operator('Owner'), 'operator')
|
|
->test(Settings::class)
|
|
->assertSet('tab', 'account')
|
|
->assertSee(__('admin_settings.account_title'))
|
|
->assertDontSee(__('admin_settings.update_title'));
|
|
});
|
|
|
|
it('falls back to the first tab when the URL names one that does not exist', function () {
|
|
// A query string is a string a stranger typed. Anything unknown must not
|
|
// render a settings page with no section on it at all.
|
|
Livewire::withQueryParams(['tab' => 'wat'])
|
|
->actingAs(operator('Owner'), 'operator')
|
|
->test(Settings::class)
|
|
->assertSet('tab', 'installation')
|
|
->assertSee(__('admin_settings.update_title'));
|
|
});
|
|
|
|
it('carries two-factor enrolment as a tab rather than a page of its own', function () {
|
|
Livewire::withQueryParams(['tab' => 'two-factor'])
|
|
->actingAs(operator('Owner'), 'operator')
|
|
->test(Settings::class)
|
|
->assertSet('tab', 'two-factor')
|
|
->assertSee(__('two_factor_setup.title'));
|
|
});
|
|
|
|
it('does not offer the team tab to somebody who cannot manage staff', function () {
|
|
// A tab that raises a 403 is worse than one that is not there — and an
|
|
// operator who reaches ?tab=team anyway lands on the first tab rather than
|
|
// on a page with nothing on it.
|
|
$support = operator('Support');
|
|
|
|
expect($support->can('staff.manage'))->toBeFalse();
|
|
|
|
Livewire::withQueryParams(['tab' => 'team'])
|
|
->actingAs($support, 'operator')
|
|
->test(Settings::class)
|
|
->assertSet('tab', 'installation')
|
|
->assertViewHas('tabs', fn (array $tabs) => ! in_array('team', $tabs, true));
|
|
});
|
|
|
|
it('lists every tab it can render, and renders every tab it lists', function () {
|
|
// The const IS the schema: it validates the query string, builds the bar
|
|
// and decides what renders. A tab in the list with no label is a button
|
|
// that says "admin_settings.tab.x".
|
|
foreach (Settings::TABS as $tab) {
|
|
$key = 'admin_settings.tab.'.str_replace('-', '_', $tab);
|
|
|
|
expect(__($key))->not->toBe($key);
|
|
}
|
|
});
|
|
|
|
it('moves the tunnel to System and drops the two-factor entry from the menu', function () {
|
|
// VPN is not a task of the day's operations — it is how the console reaches
|
|
// the estate at all, so it belongs beside the other things that have to
|
|
// work first. Two-factor left the menu entirely: it is a tab now.
|
|
$groups = collect(Navigation::console());
|
|
|
|
$routesIn = fn (string $label) => $groups
|
|
->firstWhere('label', __($label))['items'] ?? [];
|
|
|
|
$system = collect($routesIn('admin.nav_group.system'))->pluck(0);
|
|
$operations = collect($routesIn('admin.nav_group.operations'))->pluck(0);
|
|
|
|
expect($system)->toContain('admin.vpn')
|
|
->and($operations)->not->toContain('admin.vpn')
|
|
->and($groups->pluck('items')->flatten(1)->pluck(0))
|
|
->not->toContain('admin.two-factor-setup');
|
|
});
|
|
|
|
it('keeps the standalone enrolment route, because the gate sends people there', function () {
|
|
// RequireOperatorTwoFactor redirects an operator who has not enrolled yet,
|
|
// and that operator may not open anything else — the settings page
|
|
// included. Folding the page away entirely would have locked them out.
|
|
expect(route('admin.two-factor-setup'))->toBeString();
|
|
|
|
$this->actingAs(operator('Owner'), 'operator')
|
|
->get(route('admin.two-factor-setup'))
|
|
->assertOk()
|
|
->assertSee(__('two_factor_setup.title'));
|
|
});
|
|
|
|
it('tabs the integrations page the same way, and keeps the raw file to the Owner', function () {
|
|
// Same treatment, same reason: one narrow column of six cards had an
|
|
// operator scrolling past Stripe to reach DNS, with the raw .env editor at
|
|
// the bottom of everything.
|
|
$owner = operator('Owner');
|
|
|
|
Livewire::actingAs($owner, 'operator')
|
|
->test(App\Livewire\Admin\Integrations::class)
|
|
->assertSet('tab', 'services')
|
|
->assertViewHas('tabs', fn (array $tabs) => in_array('env', $tabs, true));
|
|
|
|
foreach (App\Livewire\Admin\Integrations::TABS as $tab) {
|
|
expect(__('integrations.tab.'.$tab))->not->toBe('integrations.tab.'.$tab);
|
|
}
|
|
|
|
// An Admin can reach the page for the DNS zone but not the file that holds
|
|
// every credential on the machine.
|
|
$admin = operator('Admin');
|
|
|
|
expect($admin->can('hosts.manage'))->toBeTrue()
|
|
->and($admin->can('secrets.manage'))->toBeFalse();
|
|
|
|
Livewire::withQueryParams(['tab' => 'env'])
|
|
->actingAs($admin, 'operator')
|
|
->test(App\Livewire\Admin\Integrations::class)
|
|
->assertSet('tab', 'services')
|
|
->assertViewHas('tabs', fn (array $tabs) => ! in_array('env', $tabs, true));
|
|
});
|