create(); $this->actingAs($user); return $user; } public function test_issue_confirm_consume_is_the_happy_path(): void { $this->login(); $token = ConfirmToken::issue('keyRemoved', ['fingerprint' => 'ab:cd'], 'ssh_key.remove', 'box · key', 7); // The modal confirms (pending → confirmed)… $this->assertSame('keyRemoved', ConfirmToken::confirm($token)['event']); // …then the handler consumes (confirmed → burned). $payload = ConfirmToken::consume($token, 'keyRemoved'); $this->assertSame(['fingerprint' => 'ab:cd'], $payload['params']); $this->assertSame('ssh_key.remove', $payload['auditAction']); $this->assertSame('box · key', $payload['auditTarget']); $this->assertSame(7, $payload['serverId']); } public function test_a_freshly_issued_token_cannot_be_consumed_without_confirmation(): void { // The modal-bypass guard: issuing a token (opener) does NOT authorize apply. $this->login(); $token = ConfirmToken::issue('twoFactorDisabled'); $this->expectException(InvalidConfirmToken::class); ConfirmToken::consume($token, 'twoFactorDisabled'); } public function test_issue_rejects_an_event_outside_the_allowlist(): void { $this->login(); $this->expectException(\InvalidArgumentException::class); ConfirmToken::issue('evilEvent', ['userId' => 1]); } public function test_a_token_can_be_confirmed_only_once(): void { $this->login(); $token = ConfirmToken::issue('twoFactorDisabled'); ConfirmToken::confirm($token); $this->expectException(InvalidConfirmToken::class); ConfirmToken::confirm($token); } public function test_a_confirmed_token_is_consumed_only_once(): void { $this->login(); $token = ConfirmToken::issue('twoFactorDisabled'); ConfirmToken::confirm($token); ConfirmToken::consume($token, 'twoFactorDisabled'); $this->expectException(InvalidConfirmToken::class); ConfirmToken::consume($token, 'twoFactorDisabled'); } public function test_consume_rejects_a_token_minted_for_a_different_action(): void { $this->login(); $token = ConfirmToken::issue('fileConfirmed', ['name' => 'x']); ConfirmToken::confirm($token); $this->expectException(InvalidConfirmToken::class); ConfirmToken::consume($token, 'keyRemoved'); } public function test_confirm_rejects_a_garbage_token(): void { $this->login(); $this->expectException(InvalidConfirmToken::class); ConfirmToken::confirm('not-a-real-token'); } public function test_consume_rejects_a_garbage_token(): void { $this->login(); $this->expectException(InvalidConfirmToken::class); ConfirmToken::consume('not-a-real-token', 'keyRemoved'); } public function test_peek_returns_null_on_a_garbage_token_and_never_throws(): void { $this->login(); $this->assertNull(ConfirmToken::peek('not-a-real-token')); } public function test_peek_exposes_the_audit_target_for_display(): void { $this->login(); $token = ConfirmToken::issue('keyRemoved', [], 'ssh_key.remove', 'box · key', 7); $this->assertSame('box · key', ConfirmToken::peek($token)['auditTarget']); } public function test_a_token_is_bound_to_the_issuing_user(): void { $this->login(); $token = ConfirmToken::issue('twoFactorDisabled'); // A different operator must not be able to confirm Alice's token. Auth::login(User::factory()->create()); $this->expectException(InvalidConfirmToken::class); ConfirmToken::confirm($token); } }