clusev/tests/Feature/Fail2banBansModalTest.php

141 lines
5.6 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Modals\Fail2banBans;
use App\Models\AuditEvent;
use App\Models\Server;
use App\Models\User;
use App\Services\Fail2banService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Mockery;
use Tests\TestCase;
class Fail2banBansModalTest extends TestCase
{
use RefreshDatabase;
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
/** The modal opens instantly: mount stores only the id, with no SSH read yet. */
public function test_mount_does_not_read_status_so_the_modal_opens_instantly(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
// status() must NOT be called during mount — it is deferred to load().
$svc = Mockery::mock(Fail2banService::class);
$svc->shouldReceive('status')->never();
app()->instance(Fail2banService::class, $svc);
Livewire::test(Fail2banBans::class, ['serverId' => $server->id])
->assertSet('loaded', false)
->assertSet('jails', []);
}
/** load() (fired by wire:init) reads the live status and renders the jail/IP list. */
public function test_load_reads_status_and_renders_the_jail_list(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
$svc = Mockery::mock(Fail2banService::class);
$svc->shouldReceive('status')->once()->with(Mockery::on(fn ($s) => $s->id === $server->id))
->andReturn([
'jails' => [
['name' => 'sshd', 'currentlyBanned' => 1, 'currentlyFailed' => 3, 'bannedIps' => ['203.0.113.7']],
],
]);
app()->instance(Fail2banService::class, $svc);
Livewire::test(Fail2banBans::class, ['serverId' => $server->id])
->call('load')
->assertSet('loaded', true)
->assertSee('sshd')
->assertSee('203.0.113.7');
}
/** A read error from status() surfaces the translated error message. */
public function test_load_surfaces_a_read_error(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
$svc = Mockery::mock(Fail2banService::class);
$svc->shouldReceive('status')->once()->andReturn(['jails' => [], 'readError' => true]);
app()->instance(Fail2banService::class, $svc);
Livewire::test(Fail2banBans::class, ['serverId' => $server->id])
->call('load')
->assertSet('loaded', true)
->assertSet('error', __('servers.fail2ban_read_error'));
}
/** unban() calls the service, audits the success, refreshes the list, and signals the page. */
public function test_unban_calls_service_audits_refreshes_and_dispatches_change(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
$svc = Mockery::mock(Fail2banService::class);
// Initial load: one banned IP.
$svc->shouldReceive('status')->once()->andReturn([
'jails' => [
['name' => 'sshd', 'currentlyBanned' => 1, 'currentlyFailed' => 3, 'bannedIps' => ['203.0.113.7']],
],
]);
$svc->shouldReceive('unban')->once()
->with(Mockery::on(fn ($s) => $s->id === $server->id), 'sshd', '203.0.113.7')
->andReturn(['ok' => true, 'output' => 'unbanned']);
// Refresh after unban: the IP is gone.
$svc->shouldReceive('status')->once()->andReturn([
'jails' => [
['name' => 'sshd', 'currentlyBanned' => 0, 'currentlyFailed' => 3, 'bannedIps' => []],
],
]);
app()->instance(Fail2banService::class, $svc);
Livewire::test(Fail2banBans::class, ['serverId' => $server->id])
->call('load')
->assertSee('203.0.113.7')
->call('unbanIp', 'sshd', '203.0.113.7')
->assertDispatched('fail2banChanged')
->assertDispatched('notify')
->assertDontSee('203.0.113.7');
$this->assertDatabaseHas('audit_events', [
'server_id' => $server->id,
'action' => 'fail2ban.unban',
]);
}
/** A failed unban surfaces a notice and does NOT audit or signal the page. */
public function test_unban_failure_notifies_without_auditing(): void
{
$this->actingAs(User::factory()->create());
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
$svc = Mockery::mock(Fail2banService::class);
$svc->shouldReceive('status')->once()->andReturn([
'jails' => [
['name' => 'sshd', 'currentlyBanned' => 1, 'currentlyFailed' => 3, 'bannedIps' => ['203.0.113.7']],
],
]);
$svc->shouldReceive('unban')->once()->andReturn(['ok' => false, 'output' => 'no permission']);
app()->instance(Fail2banService::class, $svc);
Livewire::test(Fail2banBans::class, ['serverId' => $server->id])
->call('load')
->call('unbanIp', 'sshd', '203.0.113.7')
->assertDispatched('notify')
->assertNotDispatched('fail2banChanged');
$this->assertSame(0, AuditEvent::where('action', 'fail2ban.unban')->count());
}
}