Added artifacts, screenshots, and documentation for the platform sellable smoke matrix. Fixed a bug in FindingRiskGovernanceResolver and updated related tests. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #426
245 lines
10 KiB
PHP
245 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.')
|
|
->assertDontSee('The linked exception is still valid, but it is nearing expiry and needs review.');
|
|
|
|
app()->setLocale($originalLocale);
|
|
});
|