Automated PR provided by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #474
319 lines
12 KiB
PHP
319 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Monitoring\EvidenceOverview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Services\Evidence\EvidenceAnchorResolver;
|
|
use App\Services\Evidence\EvidenceAnchorResult;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\ReviewPackStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('Spec403 current evidence anchors require scoped active complete non-expired evidence', function (array $attributes): void {
|
|
$environment = ManagedEnvironment::factory()->create();
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner');
|
|
|
|
spec403EvidenceSnapshot($environment, $attributes);
|
|
|
|
setAdminPanelContext($environment);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
|
|
|
|
$result = app(EvidenceAnchorResolver::class)->currentForScope(
|
|
$environment->workspace,
|
|
$environment,
|
|
$user,
|
|
);
|
|
|
|
expect($result->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION)
|
|
->and($result->canLink)->toBeFalse()
|
|
->and($result->isCurrent)->toBeFalse()
|
|
->and($result->primaryReason)->toContain('No complete active evidence snapshot');
|
|
|
|
EvidenceSnapshot::query()
|
|
->where('workspace_id', (int) $environment->workspace_id)
|
|
->where('managed_environment_id', (int) $environment->getKey())
|
|
->delete();
|
|
|
|
$currentSnapshot = spec403EvidenceSnapshot($environment, [
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'generated_at' => now(),
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
$currentResult = app(EvidenceAnchorResolver::class)->currentForScope(
|
|
$environment->workspace,
|
|
$environment,
|
|
$user,
|
|
);
|
|
|
|
expect($currentResult->state)->toBe(EvidenceAnchorResult::STATE_READY)
|
|
->and($currentResult->evidenceSnapshotId)->toBe((int) $currentSnapshot->getKey())
|
|
->and($currentResult->canLink)->toBeTrue()
|
|
->and($currentResult->isCurrent)->toBeTrue();
|
|
})->with([
|
|
'queued evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Queued->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
]],
|
|
'generating evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Generating->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
]],
|
|
'failed evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Failed->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
]],
|
|
'partial evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Partial->value,
|
|
]],
|
|
'complete evidence with missing dimensions' => [[
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 2,
|
|
'missing_dimensions' => 1,
|
|
'stale_dimensions' => 0,
|
|
],
|
|
]],
|
|
'complete evidence with no usable captured dimensions' => [[
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 0,
|
|
'missing_dimensions' => 0,
|
|
'stale_dimensions' => 0,
|
|
],
|
|
]],
|
|
'complete evidence with stale dimensions' => [[
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 2,
|
|
'missing_dimensions' => 0,
|
|
'stale_dimensions' => 1,
|
|
],
|
|
]],
|
|
'expired evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'expires_at' => now()->subMinute(),
|
|
]],
|
|
'superseded evidence' => [[
|
|
'status' => EvidenceSnapshotStatus::Superseded->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
]],
|
|
]);
|
|
|
|
it('Spec403 evidence overview exposes canonical proof states without overclaiming operation proof', function (
|
|
string $status,
|
|
string $outcome,
|
|
string $expectedState,
|
|
): void {
|
|
$environment = ManagedEnvironment::factory()->create();
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'status' => $status,
|
|
'outcome' => $outcome,
|
|
'started_at' => now()->subMinutes(5),
|
|
'completed_at' => $status === OperationRunStatus::Completed->value ? now()->subMinute() : null,
|
|
'initiator_name' => 'Spec403 Operator',
|
|
]);
|
|
|
|
spec403EvidenceSnapshot($environment, [
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'generated_at' => now()->subMinutes(2),
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
|
$operationProof = collect($payload['proof_items'])->firstWhere('label', 'Operation proof');
|
|
$operationCard = collect($payload['cards'])->firstWhere('label', 'Operation proof');
|
|
$allowedStates = [
|
|
'Ready',
|
|
'Not configured',
|
|
'Running',
|
|
'Failed',
|
|
'Blocked',
|
|
'Expired',
|
|
'Needs attention',
|
|
'Historical',
|
|
];
|
|
|
|
expect($operationProof)->toBeArray()
|
|
->and($operationProof['state'])->toBe($expectedState)
|
|
->and($operationProof['url'])->toBeNull()
|
|
->and($operationProof['actionLabel'])->toBeNull()
|
|
->and($operationCard)->toBeArray()
|
|
->and($operationCard['value'])->toBe($expectedState)
|
|
->and($operationCard['url'])->toBeNull()
|
|
->and($operationCard['description'])->not->toContain('Operation #')
|
|
->and($payload['decision_card']['evidence'])->not->toContain('Operation #')
|
|
->and($operationProof['description'])->toContain('Spec403 Operator')
|
|
->and(array_values(array_diff(collect($payload['proof_items'])->pluck('state')->all(), $allowedStates)))->toBe([])
|
|
->and($operationProof['state'])->not->toBe('Available')
|
|
->and($operationProof['state'])->not->toBe('Unknown');
|
|
})->with([
|
|
'running operation' => [
|
|
OperationRunStatus::Running->value,
|
|
OperationRunOutcome::Pending->value,
|
|
'Running',
|
|
],
|
|
'succeeded operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::Succeeded->value,
|
|
'Historical',
|
|
],
|
|
'partially succeeded operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::PartiallySucceeded->value,
|
|
'Needs attention',
|
|
],
|
|
'blocked operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::Blocked->value,
|
|
'Blocked',
|
|
],
|
|
'failed operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::Failed->value,
|
|
'Failed',
|
|
],
|
|
'cancelled operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::Cancelled->value,
|
|
'Failed',
|
|
],
|
|
'completed pending operation' => [
|
|
OperationRunStatus::Completed->value,
|
|
OperationRunOutcome::Pending->value,
|
|
'Needs attention',
|
|
],
|
|
]);
|
|
|
|
it('Spec403 evidence overview uses canonical states for missing proof flow steps', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create();
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
|
$states = collect($payload['readiness_flow'])->pluck('state')->all();
|
|
$proofStates = collect($payload['proof_items'])->pluck('state')->all();
|
|
$allowedStates = [
|
|
'Ready',
|
|
'Not configured',
|
|
'Running',
|
|
'Failed',
|
|
'Blocked',
|
|
'Expired',
|
|
'Needs attention',
|
|
'Historical',
|
|
];
|
|
|
|
expect($states)->toBe([
|
|
'Ready',
|
|
'Not configured',
|
|
'Blocked',
|
|
'Blocked',
|
|
'Not configured',
|
|
'Not configured',
|
|
])
|
|
->and(array_values(array_diff($proofStates, $allowedStates)))->toBe([])
|
|
->and($proofStates)->not->toContain('Available')
|
|
->and($proofStates)->not->toContain('Unavailable')
|
|
->and($proofStates)->not->toContain('Not generated')
|
|
->and($proofStates)->not->toContain('Not applicable')
|
|
->and($proofStates)->not->toContain('Proof incomplete')
|
|
->and($proofStates)->not->toContain('Empty')
|
|
->and($proofStates)->not->toContain('Collapsed')
|
|
->and($proofStates)->not->toContain('Unknown');
|
|
});
|
|
|
|
it('Spec403 evidence overview maps review pack proof lifecycle states canonically', function (
|
|
string $status,
|
|
string $expectedState,
|
|
): void {
|
|
$environment = ManagedEnvironment::factory()->create();
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
|
|
$snapshot = spec403EvidenceSnapshot($environment, [
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'generated_at' => now()->subMinutes(2),
|
|
'expires_at' => now()->addDay(),
|
|
]);
|
|
|
|
ReviewPack::factory()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'status' => $status,
|
|
]);
|
|
|
|
$payload = spec403EvidenceOverviewPayload($user, $environment);
|
|
$reviewPackProof = collect($payload['proof_items'])->firstWhere('label', 'Review pack');
|
|
$reviewPackCard = collect($payload['cards'])->firstWhere('label', 'Review pack');
|
|
|
|
expect($reviewPackProof)->toBeArray()
|
|
->and($reviewPackProof['state'])->toBe($expectedState)
|
|
->and($reviewPackCard)->toBeArray()
|
|
->and($reviewPackCard['value'])->toBe($expectedState)
|
|
->and($reviewPackProof['state'])->not->toBe('Queued')
|
|
->and($reviewPackProof['state'])->not->toBe('Generating');
|
|
})->with([
|
|
'queued review pack' => [ReviewPackStatus::Queued->value, 'Running'],
|
|
'generating review pack' => [ReviewPackStatus::Generating->value, 'Running'],
|
|
'failed review pack' => [ReviewPackStatus::Failed->value, 'Failed'],
|
|
]);
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
function spec403EvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
|
|
{
|
|
return EvidenceSnapshot::query()->create(array_replace([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 2,
|
|
'missing_dimensions' => 0,
|
|
'stale_dimensions' => 0,
|
|
],
|
|
'generated_at' => now(),
|
|
'expires_at' => now()->addDay(),
|
|
], $attributes));
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
function spec403EvidenceOverviewPayload(App\Models\User $user, ManagedEnvironment $environment): array
|
|
{
|
|
setAdminPanelContext($environment);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $environment->workspace_id);
|
|
|
|
return Livewire::withQueryParams([
|
|
'environment_id' => (int) $environment->getKey(),
|
|
])
|
|
->actingAs($user)
|
|
->test(EvidenceOverview::class)
|
|
->instance()
|
|
->evidenceDisclosurePayload();
|
|
}
|