create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner'); $createException = function (array $attributes) use ($tenant, $requester, $approver): FindingException { $finding = Finding::factory()->for($tenant)->create(); return FindingException::query()->create(array_merge([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $requester->getKey(), 'owner_user_id' => (int) $requester->getKey(), 'approved_by_user_id' => (int) $approver->getKey(), 'status' => FindingException::STATUS_ACTIVE, 'current_validity_state' => FindingException::VALIDITY_VALID, 'request_reason' => 'Temporary governance coverage', 'approval_reason' => 'Compensating controls accepted', 'requested_at' => now()->subDays(10), 'approved_at' => now()->subDays(9), 'effective_from' => now()->subDays(9), 'expires_at' => now()->addDays(21), 'review_due_at' => now()->addDays(14), 'evidence_summary' => ['reference_count' => 0], ], $attributes)); }; $pending = $createException([ 'approved_by_user_id' => null, 'status' => FindingException::STATUS_PENDING, 'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT, 'approval_reason' => null, 'approved_at' => null, 'effective_from' => null, 'expires_at' => now()->addDays(10), 'review_due_at' => now()->addDays(7), ]); $active = $createException([]); $expired = $createException([ 'expires_at' => now()->subDay(), 'review_due_at' => now()->subDays(3), ]); app(FindingRiskGovernanceResolver::class)->syncExceptionState($expired); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListFindingExceptions::class) ->assertCanSeeTableRecords([$pending, $active, $expired]) ->filterTable('status', FindingException::STATUS_EXPIRED) ->assertCanSeeTableRecords([$expired]) ->assertCanNotSeeTableRecords([$pending, $active]); }); it('renders exception detail with owner, approver, and validity context for tenant viewers', function (): void { [$viewer, $tenant] = createUserWithTenant(role: 'readonly'); [$requester] = createUserWithTenant(tenant: $tenant, role: 'owner'); $approver = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner'); $finding = Finding::factory()->for($tenant)->create(); $exception = FindingException::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $requester->getKey(), 'owner_user_id' => (int) $requester->getKey(), 'approved_by_user_id' => (int) $approver->getKey(), 'status' => FindingException::STATUS_EXPIRING, 'current_validity_state' => FindingException::VALIDITY_EXPIRING, 'request_reason' => 'Temporary exception request', 'approval_reason' => 'Valid until remediation window closes', 'requested_at' => now()->subDays(5), 'approved_at' => now()->subDays(4), 'effective_from' => now()->subDays(4), 'expires_at' => now()->addDays(2), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 0], ]); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ViewFindingException::class, ['record' => $exception->getKey()]) ->assertOk() ->assertSee('Validity') ->assertSee('Expiring') ->assertSee($requester->name) ->assertSee($approver->name); }); it('shows a single clear empty-state action when no tenant exceptions match', function (): void { [$viewer, $tenant] = createUserWithTenant(role: 'readonly'); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ListFindingExceptions::class) ->assertSee('No exceptions match this view') ->assertTableEmptyStateActionsExistInOrder(['open_findings']); });