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]); } /** An online server with a credential, selected as the active fleet server. */ private function activeServer(): Server { $server = Server::create(['name' => 'box', 'ip' => '10.0.0.2', 'ssh_port' => 22, 'status' => 'online']); $server->credential()->create(['username' => 'root', 'auth_type' => 'password', 'secret' => 'x']); session(['active_server_id' => $server->id]); return $server; } // ── manage-fleet: Servers\Index add-server trigger ────────────────────────── public function test_admin_sees_the_add_server_trigger(): void { $this->actingAs($this->admin()); Livewire::test(Servers::class) ->assertOk() ->assertSee(__('servers.add_server')); // "Server hinzufügen" } public function test_viewer_does_not_see_the_add_server_trigger(): void { $this->actingAs($this->viewer()); Livewire::test(Servers::class) ->assertOk() // the fleet list still renders ->assertDontSee(__('servers.add_server')); // but the create control is gone } // ── operate: Services\Index start/stop/restart action buttons ─────────────── /** Stubs the systemd read so the list renders with one service row. */ private function stubServices(): void { $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('systemd')->andReturn([ 'services' => [['name' => 'nginx.service', 'status' => 'offline', 'sub' => 'dead', 'enabled' => true, 'desc' => 'web']], 'journal' => [], 'cursor' => null, ]); app()->instance(FleetService::class, $fleet); } public function test_operator_sees_a_service_action_control(): void { $this->actingAs($this->operator()); $this->activeServer(); $this->stubServices(); Livewire::test(Services::class) ->call('load') ->assertOk() ->assertSee(__('services.restart')); // "Neustart" — a per-row action button } public function test_viewer_does_not_see_a_service_action_control(): void { $this->actingAs($this->viewer()); $this->activeServer(); $this->stubServices(); Livewire::test(Services::class) ->call('load') ->assertOk() ->assertSee('nginx.service') // the list itself is visible ->assertDontSee(__('services.restart')); // but the action buttons are gone } // ── operate: Files\Index content controls (download / edit / delete) ──────── // The listing (browse) is open to a viewer; the content-read + write controls are not. /** Stubs the SFTP directory read so the browser renders with one file row. */ private function stubFiles(): void { $fleet = Mockery::mock(FleetService::class); $fleet->shouldReceive('files')->andReturn([ ['name' => 'secrets.env', 'type' => 'file', 'size' => 12, 'perms' => '-rw-------', 'owner' => 'root', 'modified' => 'now'], ]); app()->instance(FleetService::class, $fleet); } public function test_operator_sees_the_file_content_controls(): void { $this->actingAs($this->operator()); $this->activeServer(); $this->stubFiles(); Livewire::test(Files::class) ->call('load') ->assertOk() ->assertSee(__('files.download')) // download button present ->assertSee(__('common.edit')); // edit trigger present } public function test_viewer_does_not_see_the_file_content_controls(): void { $this->actingAs($this->viewer()); $this->activeServer(); $this->stubFiles(); Livewire::test(Files::class) ->call('load') ->assertOk() ->assertSee('secrets.env') // the listing is visible (browse stays open) ->assertDontSee(__('files.download')) // but no download button ->assertDontSee(__('common.edit')) // no edit trigger ->assertDontSee(__('common.delete')); // no delete trigger } // ── manage-network: sidebar WireGuard nav item ────────────────────────────── public function test_admin_sees_the_wireguard_nav(): void { $this->actingAs($this->admin()); $this->get('/servers') ->assertOk() ->assertSee(__('shell.nav_wireguard')); // "WireGuard" nav item present } public function test_viewer_does_not_see_the_wireguard_nav(): void { $this->actingAs($this->viewer()); $this->get('/servers') ->assertOk() ->assertDontSee(__('shell.nav_wireguard')); // hidden for a viewer } }