clusev/tests/Feature/RbacUiHidingTest.php

139 lines
4.7 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Servers\Index as Servers;
use App\Livewire\Services\Index as Services;
use App\Models\Server;
use App\Models\User;
use App\Services\FleetService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;
/**
* RBAC view-level HIDING: operators/viewers must not SEE the controls they would be
* 403'd from (the server-side abort_unless guards are proven in the Rbac*GateTest
* suites). This only asserts the `@can(...)` wrappers render/omit the button markup —
* a light UX check across a few representative surfaces, one per ability family:
* - Servers\Index add-server trigger → manage-fleet (admin sees, viewer does not);
* - Services\Index start/stop/restart → operate (operator sees, viewer does not);
* - the sidebar WireGuard nav item → manage-network(admin sees, viewer does not).
* Every SSH read is mocked — no real host is ever touched.
*/
class RbacUiHidingTest extends TestCase
{
use RefreshDatabase;
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
private function admin(): User
{
return User::factory()->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
}
// ── 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
}
}