TenantAtlas/apps/platform/tests/Unit/Evidence/Spec393EvidenceAnchorResolverTest.php
ahmido 77f499b60e feat: add evidence anchor reconciliation contracts and readiness fixes (#464)
Automated PR created by Codex via Gitea API.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #464
2026-06-21 09:39:14 +00:00

272 lines
12 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\ReviewPack;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Evidence\EvidenceAnchorResolver;
use App\Services\Evidence\EvidenceAnchorResult;
use App\Support\Auth\Capabilities;
use App\Support\EnvironmentReviewStatus;
use App\Support\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Gate;
uses(RefreshDatabase::class);
it('does not promote partial active evidence as the current scope anchor', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
setAdminPanelContext($tenant);
spec393EvidenceSnapshot($tenant, [
'completeness_state' => EvidenceCompletenessState::Partial->value,
'summary' => ['missing_dimensions' => 1, 'stale_dimensions' => 0],
]);
$anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION)
->and($anchor->evidenceSnapshotId)->toBeNull()
->and($anchor->targetRoute)->toBeNull()
->and($anchor->canLink)->toBeFalse();
});
it('does not fall back to evidence from another environment in the workspace', function (): void {
$tenantA = ManagedEnvironment::factory()->create();
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
$tenantB = ManagedEnvironment::factory()->create(['workspace_id' => (int) $tenantA->workspace_id]);
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
setAdminPanelContext($tenantA);
spec393EvidenceSnapshot($tenantB);
$anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenantA->workspace, $tenantA, $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NOT_CONFIGURED)
->and($anchor->targetRoute)->toBeNull();
});
it('excludes expired current evidence from current scope anchors', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
setAdminPanelContext($tenant);
spec393EvidenceSnapshot($tenant, [
'expires_at' => now()->subMinute(),
]);
$anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION)
->and($anchor->evidenceSnapshotId)->toBeNull()
->and($anchor->targetRoute)->toBeNull()
->and($anchor->canLink)->toBeFalse();
});
it('excludes superseded evidence from current scope anchors', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
setAdminPanelContext($tenant);
spec393EvidenceSnapshot($tenant, [
'status' => EvidenceSnapshotStatus::Superseded->value,
]);
$anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NEEDS_ATTENTION)
->and($anchor->evidenceSnapshotId)->toBeNull()
->and($anchor->targetRoute)->toBeNull();
});
it('excludes wrong workspace evidence even when the environment id matches', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$foreignWorkspace = Workspace::factory()->create();
setAdminPanelContext($tenant);
$snapshot = spec393EvidenceSnapshot($tenant);
EvidenceSnapshot::withoutEvents(function () use ($snapshot, $foreignWorkspace): void {
$snapshot->forceFill([
'workspace_id' => (int) $foreignWorkspace->getKey(),
])->save();
});
$anchor = app(EvidenceAnchorResolver::class)->currentForScope($tenant->workspace, $tenant, $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_NOT_CONFIGURED)
->and($anchor->evidenceSnapshotId)->toBeNull()
->and($anchor->targetRoute)->toBeNull();
});
it('keeps released review evidence pinned instead of falling forward to current evidence', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
setAdminPanelContext($tenant);
$releasedSnapshot = spec393EvidenceSnapshot($tenant, [
'fingerprint' => 'released-snapshot',
'generated_at' => now()->subDays(3),
]);
$review = EnvironmentReview::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'evidence_snapshot_id' => (int) $releasedSnapshot->getKey(),
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now()->subDays(2),
'published_by_user_id' => (int) $user->getKey(),
]);
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'environment_review_id' => (int) $review->getKey(),
'evidence_snapshot_id' => (int) $releasedSnapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
]);
$releasedSnapshot->forceFill([
'status' => EvidenceSnapshotStatus::Superseded->value,
])->save();
spec393EvidenceSnapshot($tenant, [
'fingerprint' => 'new-current-snapshot',
'generated_at' => now(),
]);
$releaseAnchor = app(EvidenceAnchorResolver::class)->forReviewPackRelease($pack->fresh(), $user);
$customerAnchor = app(EvidenceAnchorResolver::class)->forCustomerWorkspace($review->fresh(), $user);
expect($releaseAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_REVIEW_RELEASED_EVIDENCE)
->and($releaseAnchor->evidenceSnapshotId)->toBe((int) $releasedSnapshot->getKey())
->and($releaseAnchor->isReleaseBound)->toBeTrue()
->and($releaseAnchor->isCurrent)->toBeFalse()
->and($releaseAnchor->targetRoute)->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $releasedSnapshot], tenant: $tenant, panel: 'admin'))
->and($customerAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_CUSTOMER_SAFE_EVIDENCE_SUMMARY)
->and($customerAnchor->evidenceSnapshotId)->toBeNull()
->and($customerAnchor->targetRoute)->toBeNull()
->and($customerAnchor->isCustomerSafe)->toBeTrue();
});
it('fails closed when customer workspace review pack evidence does not match the pack scope', function (): void {
$tenantA = ManagedEnvironment::factory()->create();
[$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
$tenantB = ManagedEnvironment::factory()->create(['workspace_id' => (int) $tenantA->workspace_id]);
createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');
setAdminPanelContext($tenantA);
$wrongScopeSnapshot = spec393EvidenceSnapshot($tenantB);
$review = EnvironmentReview::factory()->create([
'managed_environment_id' => (int) $tenantA->getKey(),
'workspace_id' => (int) $tenantA->workspace_id,
'evidence_snapshot_id' => (int) $wrongScopeSnapshot->getKey(),
'status' => EnvironmentReviewStatus::Published->value,
'published_at' => now(),
'published_by_user_id' => (int) $user->getKey(),
]);
$pack = ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $tenantA->getKey(),
'workspace_id' => (int) $tenantA->workspace_id,
'environment_review_id' => (int) $review->getKey(),
'evidence_snapshot_id' => (int) $wrongScopeSnapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
]);
$anchor = app(EvidenceAnchorResolver::class)->forCustomerWorkspace($pack->fresh(), $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_NO_VALID_EVIDENCE)
->and($anchor->state)->toBe(EvidenceAnchorResult::STATE_BLOCKED)
->and($anchor->isCustomerSafe)->toBeFalse()
->and($anchor->evidenceSnapshotId)->toBeNull()
->and($anchor->targetRoute)->toBeNull()
->and($anchor->canLink)->toBeFalse()
->and($anchor->primaryReason)->toContain('scope does not match');
});
it('keeps draft review pack evidence internal and not customer safe', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
setAdminPanelContext($tenant);
$snapshot = spec393EvidenceSnapshot($tenant);
$pack = ReviewPack::factory()->create([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'evidence_snapshot_id' => (int) $snapshot->getKey(),
'initiated_by_user_id' => (int) $user->getKey(),
]);
$anchor = app(EvidenceAnchorResolver::class)->forReviewPackDraft($pack->fresh(), $user);
expect($anchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_REVIEW_DRAFT_EVIDENCE)
->and($anchor->isCustomerSafe)->toBeFalse()
->and($anchor->isTechnicalOnly)->toBeTrue()
->and($anchor->displayLabel)->toBe('Draft review evidence');
});
it('gates technical evidence detail links by evidence view authorization', function (): void {
$tenant = ManagedEnvironment::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$snapshot = spec393EvidenceSnapshot($tenant);
setAdminPanelContext($tenant);
$authorizedAnchor = app(EvidenceAnchorResolver::class)->technicalDetail($snapshot, $user);
expect($authorizedAnchor->anchorType)->toBe(EvidenceAnchorResult::TYPE_TECHNICAL_EVIDENCE_DETAIL)
->and($authorizedAnchor->displayLabel)->toBe('View internal evidence details')
->and($authorizedAnchor->canViewTechnicalDetail)->toBeTrue()
->and($authorizedAnchor->targetRoute)->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant, panel: 'admin'));
Gate::define(Capabilities::EVIDENCE_VIEW, fn (User $actor, ManagedEnvironment $environment): bool => false);
$deniedAnchor = app(EvidenceAnchorResolver::class)->technicalDetail($snapshot, $user);
expect($deniedAnchor->canViewTechnicalDetail)->toBeFalse()
->and($deniedAnchor->targetRoute)->toBeNull()
->and($deniedAnchor->canLink)->toBeFalse();
});
it('keeps current evidence selection order explicit and deterministic', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$resolver = app(EvidenceAnchorResolver::class);
$orders = $resolver->currentSnapshotQuery($tenant->workspace, $tenant)->getQuery()->orders;
expect($orders[0]['type'] ?? null)->toBe('Raw')
->and($orders[0]['sql'] ?? null)->toBe('generated_at IS NULL')
->and($orders[1]['column'] ?? null)->toBe('generated_at')
->and($orders[1]['direction'] ?? null)->toBe('desc')
->and($orders[2]['column'] ?? null)->toBe('id')
->and($orders[2]['direction'] ?? null)->toBe('desc');
});
/**
* @param array<string, mixed> $attributes
*/
function spec393EvidenceSnapshot(ManagedEnvironment $tenant, array $attributes = []): EvidenceSnapshot
{
return EvidenceSnapshot::query()->create(array_replace([
'managed_environment_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => ['missing_dimensions' => 0, 'stale_dimensions' => 0],
'fingerprint' => 'spec393-'.str()->uuid()->toString(),
'generated_at' => now(),
], $attributes));
}