clusev/tests/Feature/WireguardPageTest.php

176 lines
6.7 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Wireguard\Index;
use App\Models\AuditEvent;
use App\Models\User;
use App\Models\WgTrafficSample;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
class WireguardPageTest extends TestCase
{
use RefreshDatabase;
private string $file;
protected function setUp(): void
{
parent::setUp();
$dir = storage_path('app/restart-signal');
@mkdir($dir, 0775, true);
$this->file = $dir.'/wg-status.json';
@unlink($this->file);
$this->actingAs(User::factory()->create(['must_change_password' => false]));
}
protected function tearDown(): void
{
@unlink($this->file);
parent::tearDown();
}
public function test_renders_unconfigured_state(): void
{
Livewire::test(Index::class)
->assertOk()
->assertViewHas('status', fn ($s) => $s['configured'] === false)
->assertSee(__('wireguard.setup_title'));
}
public function test_renders_peers_when_configured(): void
{
file_put_contents($this->file, json_encode([
'at' => time(), 'configured' => true,
'gate' => ['marker' => true, 'chain' => true], 'wg_quick' => 'active',
'server' => ['subnet' => '10.99.0.0/24', 'server_ip' => '10.99.0.1', 'port' => 51820, 'endpoint' => '1.2.3.4:51820', 'pubkey' => 'srvpub'],
'peers' => [['name' => 'laptop', 'pubkey' => 'p1', 'endpoint' => '5.6.7.8:41820', 'latest_handshake' => time(), 'rx' => 100, 'tx' => 200]],
]));
Livewire::test(Index::class)
->assertOk()
->assertViewHas('status', fn ($s) => $s['configured'] === true && count($s['peers']) === 1)
->assertSee('laptop')
->assertSee('10.99.0.0/24');
}
public function test_window_selector_switches_and_passes_traffic(): void
{
WgTrafficSample::create(['peer_pubkey' => 'p1', 'peer_name' => 'a', 'rx' => 0, 'tx' => 0, 'sampled_at' => now()->subMinutes(10)]);
WgTrafficSample::create(['peer_pubkey' => 'p1', 'peer_name' => 'a', 'rx' => 500, 'tx' => 100, 'sampled_at' => now()->subMinutes(1)]);
Livewire::test(Index::class)
->assertViewHas('traffic', fn ($t) => $t['total_down'] === 500)
->call('setWindow', 86400)
->assertSet('window', 86400)
->assertViewHas('traffic', fn ($t) => $t['window'] === 86400);
}
public function test_window_clamps_to_allowed_values(): void
{
Livewire::test(Index::class)
->call('setWindow', 999)
->assertSet('window', 3600);
}
public function test_add_peer_writes_a_request_and_audits(): void
{
Livewire::test(Index::class)
->set('newPeer', 'laptop')
->call('addPeer')
->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '');
$this->assertTrue(AuditEvent::where('action', 'wg.add-peer')->exists());
$this->assertFileExists(storage_path('app/restart-signal/wg-request.json'));
@unlink(storage_path('app/restart-signal/wg-request.json'));
}
public function test_add_peer_rejects_an_invalid_name(): void
{
Livewire::test(Index::class)
->set('newPeer', 'bad name')
->call('addPeer')
->assertHasErrors('newPeer');
$this->assertSame(0, AuditEvent::where('action', 'wg.add-peer')->count());
}
public function test_remove_peer_writes_a_request_and_audits(): void
{
Livewire::test(Index::class)
->call('removePeer', 'laptop')
->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '');
$this->assertTrue(AuditEvent::where('action', 'wg.remove-peer')->exists());
@unlink(storage_path('app/restart-signal/wg-request.json'));
}
public function test_set_endpoint_writes_a_request_and_audits(): void
{
Livewire::test(Index::class)
->set('newEndpoint', 'vpn.example.com:51820')
->call('setEndpoint')
->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '');
$this->assertTrue(AuditEvent::where('action', 'wg.set-endpoint')->exists());
@unlink(storage_path('app/restart-signal/wg-request.json'));
}
public function test_set_endpoint_rejects_a_bad_value(): void
{
Livewire::test(Index::class)
->set('newEndpoint', 'has spaces!!')
->call('setEndpoint')
->assertHasErrors('newEndpoint');
}
public function test_run_gate_writes_the_right_action_and_audits(): void
{
Livewire::test(Index::class)
->call('runGate', false)
->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '');
$this->assertTrue(AuditEvent::where('action', 'wg.gate-down')->exists());
@unlink(storage_path('app/restart-signal/wg-request.json'));
}
public function test_poll_result_surfaces_the_config_once(): void
{
$c = Livewire::test(Index::class)->set('newPeer', 'laptop')->call('addPeer');
$id = $c->get('pendingId');
file_put_contents(storage_path("app/restart-signal/wg-result-{$id}.json"), json_encode(['id' => $id, 'ok' => true, 'message' => 'ok', 'config' => "[Interface]\nPrivateKey = x", 'at' => time()]));
$c->call('pollResult')
->assertSet('pendingId', null)
->assertSet('resultConfig', fn ($v) => str_contains((string) $v, 'PrivateKey'));
@unlink(storage_path("app/restart-signal/wg-result-{$id}.json"));
}
public function test_setup_writes_a_request_and_audits(): void
{
Livewire::test(Index::class)
->set('setupSubnet', '10.99.0.0/24')->set('setupPort', '51820')->set('setupEndpoint', '1.2.3.4:51820')->set('setupPeer', 'client-1')
->call('setupWg')
->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '');
$this->assertTrue(AuditEvent::where('action', 'wg.setup')->exists());
@unlink(storage_path('app/restart-signal/wg-request.json'));
}
public function test_setup_rejects_a_bad_subnet(): void
{
Livewire::test(Index::class)
->set('setupSubnet', 'nope')->set('setupPort', '51820')->set('setupPeer', 'c')
->call('setupWg')
->assertHasErrors('setupSubnet');
}
public function test_poll_result_times_out_when_the_host_does_not_respond(): void
{
// A pending request with no host result for >30s must stop spinning, not poll forever.
Livewire::test(Index::class)
->set('pendingId', 'AAAAAAAAAAAAAAAAbbbbcccc')
->set('pendingSince', time() - 31)
->call('pollResult')
->assertSet('pendingId', null)
->assertSet('pendingSince', null);
}
}