107 lines
3.7 KiB
PHP
107 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Modals;
|
|
|
|
use App\Livewire\Modals\ConfirmAction;
|
|
use App\Models\AuditEvent;
|
|
use App\Models\Server;
|
|
use App\Models\User;
|
|
use App\Support\Confirm\ConfirmToken;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Features\SupportLockedProperties\CannotUpdateLockedPropertyException;
|
|
use Livewire\Livewire;
|
|
use Tests\TestCase;
|
|
|
|
class ConfirmActionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function login(): User
|
|
{
|
|
$user = User::factory()->create();
|
|
$this->actingAs($user);
|
|
|
|
return $user;
|
|
}
|
|
|
|
public function test_confirm_with_a_valid_token_audits_from_the_payload_and_dispatches_the_event(): void
|
|
{
|
|
$this->login();
|
|
$server = Server::create(['name' => 'box', 'ip' => '10.0.0.9', 'ssh_port' => 22, 'status' => 'online']);
|
|
$token = ConfirmToken::issue('keyRemoved', ['fingerprint' => 'ab:cd'], 'ssh_key.remove', 'box · key', $server->id);
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => $token])
|
|
->call('confirm')
|
|
->assertDispatched('keyRemoved', confirmToken: $token);
|
|
|
|
$this->assertSame(1, AuditEvent::where('action', 'ssh_key.remove')->count());
|
|
$this->assertDatabaseHas('audit_events', [
|
|
'action' => 'ssh_key.remove',
|
|
'target' => 'box · key',
|
|
'server_id' => $server->id,
|
|
]);
|
|
}
|
|
|
|
public function test_confirm_on_a_deferred_flow_dispatches_without_writing_audit(): void
|
|
{
|
|
$this->login();
|
|
// No auditAction → the handler audits after the remote op; confirm() must not.
|
|
$token = ConfirmToken::issue('firewallRuleDelete', ['spec' => '22/tcp', 'label' => 'SSH']);
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => $token])
|
|
->call('confirm')
|
|
->assertDispatched('firewallRuleDelete', confirmToken: $token);
|
|
|
|
$this->assertSame(0, AuditEvent::count());
|
|
}
|
|
|
|
public function test_confirm_with_a_forged_token_is_rejected_no_audit_no_dispatch(): void
|
|
{
|
|
$this->login();
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => 'forged-token'])
|
|
->call('confirm')
|
|
->assertDispatched('notify', message: __('modals.confirm_action.rejected'))
|
|
->assertNotDispatched('keyRemoved');
|
|
|
|
$this->assertSame(0, AuditEvent::count());
|
|
}
|
|
|
|
public function test_confirm_cannot_be_replayed_after_the_handler_burned_the_token(): void
|
|
{
|
|
$this->login();
|
|
$token = ConfirmToken::issue('twoFactorDisabled', [], 'two_factor.disable', 'a@b.c');
|
|
|
|
// Simulate the full lifecycle: modal confirmed it, handler burned it.
|
|
ConfirmToken::confirm($token);
|
|
ConfirmToken::consume($token, 'twoFactorDisabled');
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => $token])
|
|
->call('confirm')
|
|
->assertDispatched('notify', message: __('modals.confirm_action.rejected'))
|
|
->assertNotDispatched('twoFactorDisabled');
|
|
|
|
$this->assertSame(0, AuditEvent::count());
|
|
}
|
|
|
|
public function test_the_routing_token_is_locked_against_client_tampering(): void
|
|
{
|
|
$this->login();
|
|
$token = ConfirmToken::issue('twoFactorDisabled');
|
|
|
|
$this->expectException(CannotUpdateLockedPropertyException::class);
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => $token])
|
|
->set('token', ConfirmToken::issue('userRemoved', ['userId' => 1]));
|
|
}
|
|
|
|
public function test_the_display_target_comes_from_the_signed_token(): void
|
|
{
|
|
$this->login();
|
|
$token = ConfirmToken::issue('keyRemoved', [], 'ssh_key.remove', 'webserver · deploy@host', 7);
|
|
|
|
Livewire::test(ConfirmAction::class, ['token' => $token])
|
|
->assertSee('webserver · deploy@host');
|
|
}
|
|
}
|