141 lines
5.6 KiB
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());
|
|
}
|
|
}
|