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_shows_ssh_lock_active_state_when_the_host_reports_it(): void { file_put_contents($this->file, json_encode([ 'at' => time(), 'configured' => true, 'gate' => ['marker' => true, 'chain' => true, 'ssh' => 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' => [], ])); Livewire::test(Index::class) ->assertViewHas('status', fn ($s) => $s['gate']['ssh'] === true) ->assertSee(__('wireguard.ssh_lock')) ->assertSee(__('wireguard.ssh_lock_turn_off')); } 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_run_ssh_gate_lock_writes_the_right_action_and_audits(): void { Livewire::test(Index::class) ->call('runSshGate', true) ->assertSet('pendingId', fn ($v) => is_string($v) && $v !== '') ->assertSet('pendingAction', 'gate-ssh-on'); $this->assertTrue(AuditEvent::where('action', 'wg.ssh-lock')->exists()); @unlink(storage_path('app/restart-signal/wg-request.json')); } public function test_run_ssh_gate_unlock_writes_the_right_action_and_audits(): void { Livewire::test(Index::class) ->call('runSshGate', false) ->assertSet('pendingAction', 'gate-ssh-off'); $this->assertTrue(AuditEvent::where('action', 'wg.ssh-unlock')->exists()); @unlink(storage_path('app/restart-signal/wg-request.json')); } public function test_confirm_ssh_gate_opens_the_modal_without_auditing(): void { Livewire::test(Index::class) ->call('confirmSshGate', true) ->assertDispatched('openModal'); // The confirm step must NOT audit — only the apply path (runSshGate) does. $this->assertSame(0, AuditEvent::where('action', 'wg.ssh-lock')->count()); } 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_download_config_streams_a_conf_file(): void { Livewire::test(Index::class) ->set('resultConfig', "[Interface]\nPrivateKey = x") ->call('downloadConfig') ->assertFileDownloaded('clusev-wireguard.conf'); } 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); } }