create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner'); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); 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' => 'Short-lived exception while remediation is scheduled', 'approval_reason' => 'Compensating controls accepted', 'requested_at' => now()->subDays(8), 'approved_at' => now()->subDays(7), 'effective_from' => now()->subDays(7), '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(ViewFinding::class, ['record' => $finding->getKey()]) ->assertSee('Risk governance') ->assertSee('Expiring'); }); it('shows a governance warning when a finding is marked as accepted risk without a valid exception', function (): void { [$viewer, $tenant] = createUserWithTenant(role: 'readonly'); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ViewFinding::class, ['record' => $finding->getKey()]) ->assertSee('Risk governance') ->assertSee('without a valid exception record'); expect(app(FindingRiskGovernanceResolver::class)->resolveFindingState($finding->fresh())) ->toBe('risk_accepted_without_valid_exception'); }); it('surfaces expired and revoked exceptions as governance warnings instead of valid accepted risk', function (string $mode, string $expectedWarning, string $expectedState): void { [$viewer, $tenant] = createUserWithTenant(role: 'readonly'); [$requester] = createUserWithTenant(tenant: $tenant, role: 'owner'); $approver = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager'); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); /** @var FindingExceptionService $service */ $service = app(FindingExceptionService::class); $requested = $service->request($finding, $tenant, $requester, [ 'owner_user_id' => (int) $requester->getKey(), 'request_reason' => 'Temporary exception', 'review_due_at' => now()->addDays(5)->toDateTimeString(), 'expires_at' => now()->addDays(14)->toDateTimeString(), ]); $exception = $service->approve($requested, $approver, [ 'effective_from' => now()->subDays(10)->toDateTimeString(), 'expires_at' => $mode === 'expired' ? now()->subDay()->toDateTimeString() : now()->addDays(14)->toDateTimeString(), 'approval_reason' => 'Approved with controls', ]); if ($mode === 'expired') { $exception = app(FindingRiskGovernanceResolver::class)->syncExceptionState($exception->fresh()); } else { $exception = $service->revoke($exception->fresh(), $requester, [ 'revocation_reason' => 'Compensating controls were removed.', ]); } $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ViewFinding::class, ['record' => $finding->getKey()]) ->assertSee($expectedWarning); Livewire::test(ListFindingExceptions::class) ->assertSee($expectedWarning); expect(app(FindingRiskGovernanceResolver::class)->resolveFindingState($finding->fresh('findingException'))) ->toBe($expectedState); })->with([ 'expired exception' => ['expired', 'expired and no longer governs accepted risk', 'expired_exception'], 'revoked exception' => ['revoked', 'was revoked and no longer governs accepted risk', 'revoked_exception'], ]); it('keeps historical exceptions visible while requiring a fresh decision after a finding reopens', function (): void { [$viewer, $tenant] = createUserWithTenant(role: 'readonly'); [$requester] = createUserWithTenant(tenant: $tenant, role: 'owner'); $approver = User::factory()->create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager'); $finding = Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); /** @var FindingExceptionService $service */ $service = app(FindingExceptionService::class); $requested = $service->request($finding, $tenant, $requester, [ 'owner_user_id' => (int) $requester->getKey(), 'request_reason' => 'Temporary exception while remediation is scheduled', 'review_due_at' => now()->addDays(7)->toDateTimeString(), 'expires_at' => now()->addDays(20)->toDateTimeString(), ]); $service->approve($requested, $approver, [ 'effective_from' => now()->subDay()->toDateTimeString(), 'expires_at' => now()->addDays(20)->toDateTimeString(), 'approval_reason' => 'Approved with controls', ]); $finding->forceFill([ 'status' => Finding::STATUS_REOPENED, 'reopened_at' => now(), 'closed_reason' => null, 'closed_at' => null, ])->save(); $this->actingAs($viewer); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::test(ViewFinding::class, ['record' => $finding->getKey()]) ->assertSee('fresh decision is required'); Livewire::test(ListFindingExceptions::class) ->assertSee('fresh decision is required'); expect(app(FindingRiskGovernanceResolver::class)->resolveFindingState($finding->fresh('findingException'))) ->toBe('ungoverned'); });