TenantAtlas/apps/platform/tests/Feature/Monitoring/Spec354FindingExceptionsQueueGuidanceTest.php
Ahmed Darrazi 68ff50d460
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m45s
feat: finding exceptions accepted risk resolution guidance v1 (spec 354)
Implemented the accepted risk resolution guidance, including the AcceptedRiskResolutionAdapter, guidance cards, and updated related Filament views. Added unit, feature, and browser tests.
2026-06-05 04:18:59 +02:00

244 lines
10 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Governance\GovernanceInbox;
use App\Filament\Pages\Monitoring\FindingExceptionsQueue;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
uses(RefreshDatabase::class);
function spec354QueueEnvironment(
string $userRole = 'owner',
string $workspaceRole = 'manager',
): array {
[$user, $tenant] = createUserWithTenant(role: $userRole, workspaceRole: $workspaceRole);
test()->actingAs($user);
Filament::setCurrentPanel('admin');
Filament::setTenant(null, true);
Filament::bootCurrentPanel();
session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);
return [$user, $tenant];
}
function spec354QueueException(
ManagedEnvironment $tenant,
\App\Models\User $user,
array $findingAttributes = [],
array $exceptionAttributes = [],
): FindingException {
$decisionType = (string) ($exceptionAttributes['decision_type'] ?? \App\Models\FindingExceptionDecision::TYPE_APPROVED);
$decisionMetadata = is_array($exceptionAttributes['decision_metadata'] ?? null)
? $exceptionAttributes['decision_metadata']
: [];
unset($exceptionAttributes['decision_type']);
unset($exceptionAttributes['decision_metadata']);
$finding = Finding::factory()
->for($tenant)
->riskAccepted()
->create(array_merge([
'workspace_id' => (int) $tenant->workspace_id,
], $findingAttributes));
$exception = FindingException::query()->create(array_merge([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'finding_id' => (int) $finding->getKey(),
'requested_by_user_id' => (int) $user->getKey(),
'owner_user_id' => (int) $user->getKey(),
'approved_by_user_id' => (int) $user->getKey(),
'status' => FindingException::STATUS_ACTIVE,
'current_validity_state' => FindingException::VALIDITY_VALID,
'request_reason' => 'Spec354 queue guidance request',
'approval_reason' => 'Spec354 queue approval',
'requested_at' => now()->subDays(5),
'approved_at' => now()->subDays(4),
'effective_from' => now()->subDays(4),
'review_due_at' => now()->addDay(),
'expires_at' => now()->addDays(2),
'evidence_summary' => ['reference_count' => 0],
], $exceptionAttributes));
$decision = $exception->decisions()->create([
'workspace_id' => (int) $tenant->workspace_id,
'managed_environment_id' => (int) $tenant->getKey(),
'actor_user_id' => (int) $user->getKey(),
'decision_type' => $decisionType,
'reason' => 'Spec354 queue guidance decision',
'metadata' => $decisionMetadata,
'decided_at' => now()->subDays(4),
]);
$exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save();
return $exception->fresh(['finding', 'tenant', 'owner', 'requester', 'currentDecision', 'decisions.actor', 'evidenceReferences']);
}
it('shows one dominant expiring guidance case with repo-backed secondary actions only', function (): void {
[$user, $tenant] = spec354QueueEnvironment();
$expiring = spec354QueueException($tenant, $user);
Livewire::withQueryParams([
'exception' => (int) $expiring->getKey(),
])
->test(FindingExceptionsQueue::class)
->assertSet('selectedFindingExceptionId', (int) $expiring->getKey())
->assertSee('data-testid="accepted-risk-guidance-card"', false)
->assertSee(__('localization.accepted_risk_guidance.title_expiring'))
->assertSee(__('localization.accepted_risk_guidance.impact_expiring'))
->assertSee(__('localization.accepted_risk_guidance.action_open_exception'))
->assertSee(__('localization.accepted_risk_guidance.action_open_finding'))
->assertSee(__('localization.accepted_risk_guidance.detail_owner_label'))
->assertDontSee('Fix provider')
->assertDontSee('Grant permissions automatically');
});
it('preserves governance inbox continuity on queue guidance links', function (): void {
[$user, $tenant] = spec354QueueEnvironment();
$exception = spec354QueueException($tenant, $user);
$context = CanonicalNavigationContext::forGovernanceInbox(
canonicalRouteName: GovernanceInbox::getRouteName(Filament::getPanel('admin')),
tenantId: (int) $tenant->getKey(),
familyKey: 'finding_exceptions',
backLinkUrl: GovernanceInbox::getUrl(panel: 'admin', parameters: [
'environment_id' => (string) $tenant->getKey(),
'family' => 'finding_exceptions',
]),
);
$component = Livewire::withQueryParams(array_replace($context->toQuery(), [
'environment_id' => (int) $tenant->getKey(),
'exception' => (int) $exception->getKey(),
]))
->test(FindingExceptionsQueue::class)
->assertSee(__('localization.accepted_risk_guidance.action_open_exception'))
->assertSee(__('localization.accepted_risk_guidance.action_open_finding'));
$guidance = $component->instance()->selectedExceptionGuidance();
$secondaryActions = collect(is_array($guidance['secondary_actions'] ?? null) ? $guidance['secondary_actions'] : []);
$openExceptionAction = $secondaryActions->firstWhere('key', 'accepted_risk.expiring.open_exception');
expect($openExceptionAction)->toBeArray()
->and((string) $openExceptionAction['url'])
->toContain('nav%5Bsource_surface%5D=governance.inbox')
->toContain('nav%5Bfamily_key%5D=finding_exceptions');
});
it('keeps queue access workspace scoped and preserves explicit environment filter semantics', function (): void {
[$user, $tenant] = spec354QueueEnvironment();
$otherWorkspaceTenant = ManagedEnvironment::factory()->create();
spec354QueueException($tenant, $user);
$this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id,
])
->get(FindingExceptionsQueue::getUrl(panel: 'admin', parameters: [
'environment_id' => (int) $otherWorkspaceTenant->getKey(),
]))
->assertNotFound();
});
it('keeps approve and reject safety intact while rendering guidance without outbound http', function (): void {
bindFailHardGraphClient();
[$user, $tenant] = spec354QueueEnvironment();
$pending = spec354QueueException($tenant, $user, exceptionAttributes: [
'status' => FindingException::STATUS_PENDING,
'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
'approved_by_user_id' => null,
'approved_at' => null,
'effective_from' => null,
'approval_reason' => null,
'decision_type' => \App\Models\FindingExceptionDecision::TYPE_REQUESTED,
]);
assertNoOutboundHttp(function () use ($pending): void {
Livewire::withQueryParams([
'exception' => (int) $pending->getKey(),
])
->test(FindingExceptionsQueue::class)
->assertSee(__('localization.accepted_risk_guidance.title_pending'))
->assertActionVisible('approve_selected_exception')
->assertActionVisible('reject_selected_exception')
->mountAction('approve_selected_exception')
->callMountedAction()
->assertHasActionErrors(['approval_reason']);
Livewire::withQueryParams([
'exception' => (int) $pending->getKey(),
])
->test(FindingExceptionsQueue::class)
->mountAction('reject_selected_exception')
->callMountedAction()
->assertHasActionErrors(['rejection_reason']);
});
});
it('keeps expired and expiring carried-over governance dominant over pending renewal on the queue', function (): void {
[$user, $tenant] = spec354QueueEnvironment();
$expiredRenewal = spec354QueueException($tenant, $user, exceptionAttributes: [
'status' => FindingException::STATUS_PENDING,
'current_validity_state' => FindingException::VALIDITY_VALID,
'decision_type' => \App\Models\FindingExceptionDecision::TYPE_RENEWAL_REQUESTED,
'decision_metadata' => [
'previous_review_due_at' => now()->subDays(2)->toIso8601String(),
'previous_expires_at' => now()->subDay()->toIso8601String(),
],
]);
$expiringRenewal = spec354QueueException($tenant, $user, exceptionAttributes: [
'status' => FindingException::STATUS_PENDING,
'current_validity_state' => FindingException::VALIDITY_VALID,
'decision_type' => \App\Models\FindingExceptionDecision::TYPE_RENEWAL_REQUESTED,
'decision_metadata' => [
'previous_review_due_at' => now()->addDay()->toIso8601String(),
'previous_expires_at' => now()->addDays(2)->toIso8601String(),
],
]);
Livewire::withQueryParams([
'exception' => (int) $expiredRenewal->getKey(),
])
->test(FindingExceptionsQueue::class)
->assertSee(__('localization.accepted_risk_guidance.title_expired'));
Livewire::withQueryParams([
'exception' => (int) $expiringRenewal->getKey(),
])
->test(FindingExceptionsQueue::class)
->assertSee(__('localization.accepted_risk_guidance.title_expiring'))
->assertDontSee(__('localization.accepted_risk_guidance.title_pending_renewal'));
});
it('renders localized dominant queue guidance copy for german locale', function (): void {
$originalLocale = app()->getLocale();
[$user, $tenant] = spec354QueueEnvironment();
$expiring = spec354QueueException($tenant, $user);
app()->setLocale('de');
Livewire::withQueryParams([
'exception' => (int) $expiring->getKey(),
])
->test(FindingExceptionsQueue::class)
->assertSee(__('localization.accepted_risk_guidance.reason_expiring'))
->assertSee(__('localization.accepted_risk_guidance.impact_expiring'))
->assertDontSee('The current accepted-risk governance window is still active, but it is nearing expiry and needs review.');
app()->setLocale($originalLocale);
});