CluPilotCloud/tests/Feature/Admin/SettingsTabsTest.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));
});