create(['must_change_password' => false]); } private function operator(): User { return User::factory()->operator()->create(['must_change_password' => false]); } private function viewer(): User { return User::factory()->viewer()->create(['must_change_password' => false]); } private function server(string $name = 's', string $ip = '10.0.0.9'): Server { return Server::create(['name' => $name, 'ip' => $ip, 'ssh_port' => 22, 'status' => 'online']); } /** A modal-confirmed, server-scoped token (mirrors the real confirm step). */ private function confirmed(string $event, array $params, ?int $serverId = null): string { $token = ConfirmToken::issue($event, $params, '', null, $serverId); ConfirmToken::confirm($token); return $token; } // ── manage-fleet: CreateServer modal (mount + save) ───────────────────────── public function test_admin_can_open_and_save_create_server(): void { // SSH probe is mocked so the admin path performs no real login. $this->mock(FleetService::class, fn ($m) => $m->shouldReceive('testConnection')->andReturn(['ok' => true, 'error' => null])); Livewire::actingAs($this->admin()) ->test(CreateServer::class) ->set('name', 'web-1')->set('ip', '10.0.0.5')->set('sshPort', 22) ->set('username', 'root')->set('authType', 'password')->set('secret', 'pw') ->call('save') ->assertHasNoErrors(); // allowed through, SSH mocked away $this->assertSame(1, Server::count()); } public function test_operator_cannot_open_create_server(): void { // No FleetService probe should ever be reached — the mount guard fires first. $this->mock(FleetService::class, fn ($m) => $m->shouldReceive('testConnection')->never()); Livewire::actingAs($this->operator()) ->test(CreateServer::class) ->assertForbidden(); $this->assertSame(0, Server::count()); } public function test_viewer_cannot_open_create_server(): void { Livewire::actingAs($this->viewer()) ->test(CreateServer::class) ->assertForbidden(); } // ── manage-fleet: Servers\Show::deleteCredential ──────────────────────────── public function test_admin_can_delete_a_credential(): void { // ConfirmToken binds to the issuing uid — authenticate BEFORE minting it. $this->actingAs($this->admin()); $server = $this->server(); $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']); $token = $this->confirmed('credentialDeleted', [], $server->id); Livewire::test(Show::class, ['server' => $server]) ->call('deleteCredential', $token); $this->assertFalse($server->credential()->exists()); // allowed → credential gone } public function test_operator_cannot_delete_a_credential(): void { $server = $this->server(); $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']); // The method guard fires before the token is even consumed — no token needed. Livewire::actingAs($this->operator()) ->test(Show::class, ['server' => $server]) ->call('deleteCredential', 'irrelevant') ->assertForbidden(); $this->assertTrue($server->credential()->exists(), 'operator must not be able to delete the credential'); } public function test_viewer_cannot_delete_a_credential(): void { $server = $this->server(); $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']); Livewire::actingAs($this->viewer()) ->test(Show::class, ['server' => $server]) ->call('deleteCredential', 'irrelevant') ->assertForbidden(); $this->assertTrue($server->credential()->exists()); } // ── manage-fleet (host shell) vs operate (server shell): the split terminals ── public function test_admin_can_open_host_and_server_shells(): void { HostCredential::create([ 'host' => 'host.docker.internal', 'port' => 22, 'username' => 'root', 'auth_type' => 'password', 'secret' => 'r00t-pw', ]); $server = $this->server(); // Host shell = the admin-only sidebar Terminal page. Livewire::actingAs($this->admin())->test(TerminalIndex::class)->call('open'); // Server shell = the operate-gated Terminal tab on the server-details page. Livewire::actingAs($this->admin())->test(ServerTerminal::class, ['server' => $server])->call('open'); $this->assertSame(2, TerminalSession::count()); // both targets minted a session } public function test_operator_is_refused_the_host_terminal(): void { // The host terminal page (manage-fleet) is out of reach for an operator — route + component // mount both gate it, so they can never mint a host session or open the host-login setup. $this->actingAs($this->operator()); $this->get(route('terminal'))->assertForbidden(); $this->assertSame(0, TerminalSession::count()); } public function test_operator_can_open_a_server_shell(): void { $server = $this->server(); Livewire::actingAs($this->operator()) ->test(ServerTerminal::class, ['server' => $server]) ->call('open') ->assertOk(); // server shell = operate → allowed $this->assertSame(1, TerminalSession::count()); } public function test_viewer_cannot_open_host_or_server_shell(): void { $server = $this->server(); $this->actingAs($this->viewer()); $this->get(route('terminal'))->assertForbidden(); // host page = manage-fleet Livewire::actingAs($this->viewer()) ->test(ServerTerminal::class, ['server' => $server]) ->call('open') ->assertForbidden(); // server shell = operate $this->assertSame(0, TerminalSession::count()); } // ── manage-panel: System\Index domain/TLS mutations ───────────────────────── public function test_admin_can_apply_a_domain_change(): void { // ConfirmToken binds to the issuing uid — authenticate BEFORE minting it. $this->actingAs($this->admin()); $token = $this->confirmed('domainChanged', ['domain' => 'panel.example.test']); Livewire::test(SystemIndex::class) ->call('applyDomain', $token) ->assertSet('domainInput', 'panel.example.test'); // allowed → domain persisted $this->assertSame('panel.example.test', app(DeploymentService::class)->configuredDomain()); } public function test_operator_cannot_apply_a_domain_change(): void { Livewire::actingAs($this->operator()) ->test(SystemIndex::class) ->call('applyDomain', 'irrelevant') ->assertForbidden(); $this->assertNull(app(DeploymentService::class)->configuredDomain()); } public function test_operator_cannot_confirm_a_domain_change(): void { Livewire::actingAs($this->operator()) ->test(SystemIndex::class) ->set('domainInput', 'panel.example.test') ->call('confirmDomain') ->assertForbidden(); } public function test_viewer_cannot_apply_a_domain_change(): void { Livewire::actingAs($this->viewer()) ->test(SystemIndex::class) ->call('applyDomain', 'irrelevant') ->assertForbidden(); } }