TenantAtlas/apps/platform/tests/Unit/Support/GovernanceArtifactTruth/GovernanceArtifactLifecycleContractTest.php
Ahmed Darrazi d091d84ddf
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m47s
chore: commit all workspace changes (automated)
2026-05-03 22:26:40 +02:00

150 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\FindingExceptionDecision;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\Tenant;
use App\Support\ReviewPackStatus;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('maps stored reports into immutable reference, lifecycle, and retention truth', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$historical = StoredReport::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
'fingerprint' => 'permission-posture-v1',
'created_at' => now()->subDay(),
'updated_at' => now()->subDay(),
]);
$current = StoredReport::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
'fingerprint' => 'permission-posture-v2',
'previous_fingerprint' => 'permission-posture-v1',
'created_at' => now(),
'updated_at' => now(),
]);
$historicalTruth = app(ArtifactTruthPresenter::class)->for($historical->fresh());
$currentTruth = app(ArtifactTruthPresenter::class)->for($current->fresh());
expect($historicalTruth)->not->toBeNull()
->and($currentTruth)->not->toBeNull();
$historicalState = $historicalTruth?->toArray();
$currentState = $currentTruth?->toArray();
expect($historicalState['displayReference'] ?? null)
->toContain('Stored report')
->toContain('#'.$historical->getKey())
->and($historicalState['integrityAnchor'] ?? null)->toBe('permission-posture-v1')
->and($historicalState['lifecycleState'] ?? null)->toBe('historical')
->and($historicalState['retentionState'] ?? null)->toBe('retained')
->and($currentState['displayReference'] ?? null)->toContain('#'.$current->getKey())
->and($currentState['integrityAnchor'] ?? null)->toBe('permission-posture-v2')
->and($currentState['lifecycleState'] ?? null)->toBe('current')
->and($currentState['retentionState'] ?? null)->toBe('retained');
});
it('maps accepted-risk decision history into current and superseded artifact truth', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$finding = Finding::factory()->for($tenant)->create();
$exception = FindingException::query()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'finding_id' => (int) $finding->getKey(),
'requested_by_user_id' => (int) $user->getKey(),
'owner_user_id' => (int) $user->getKey(),
'status' => FindingException::STATUS_ACTIVE,
'current_validity_state' => FindingException::VALIDITY_VALID,
'request_reason' => 'Approve temporary exception',
'requested_at' => now()->subDays(10),
'approved_at' => now()->subDays(9),
'effective_from' => now()->subDays(9),
'expires_at' => now()->addDays(14),
'review_due_at' => now()->addWeek(),
'evidence_summary' => ['reference_count' => 1],
]);
$requestedDecision = $exception->decisions()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'actor_user_id' => (int) $user->getKey(),
'decision_type' => FindingExceptionDecision::TYPE_REQUESTED,
'reason' => 'Approve temporary exception',
'metadata' => [],
'decided_at' => now()->subDays(10),
]);
$approvedDecision = $exception->decisions()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'actor_user_id' => (int) $user->getKey(),
'decision_type' => FindingExceptionDecision::TYPE_APPROVED,
'reason' => 'Approved for a bounded review period',
'metadata' => [],
'effective_from' => now()->subDays(9),
'expires_at' => now()->addDays(14),
'decided_at' => now()->subDays(9),
]);
$exception->forceFill(['current_decision_id' => (int) $approvedDecision->getKey()])->save();
$requestedTruth = app(ArtifactTruthPresenter::class)->for($requestedDecision->fresh('exception.currentDecision'));
$approvedTruth = app(ArtifactTruthPresenter::class)->for($approvedDecision->fresh('exception.currentDecision'));
expect($requestedTruth)->not->toBeNull()
->and($approvedTruth)->not->toBeNull();
$requestedState = $requestedTruth?->toArray();
$approvedState = $approvedTruth?->toArray();
expect($requestedState['displayReference'] ?? null)
->toContain('Accepted-risk decision')
->toContain('#'.$requestedDecision->getKey())
->and($requestedState['lifecycleState'] ?? null)->toBe('superseded')
->and($requestedState['retentionState'] ?? null)->toBe('retained')
->and($approvedState['displayReference'] ?? null)->toContain('#'.$approvedDecision->getKey())
->and($approvedState['lifecycleState'] ?? null)->toBe('current')
->and($approvedState['retentionState'] ?? null)->toBe('retained');
});
it('keeps expired direct access separate from historical lifecycle on review packs', function (): void {
$tenant = Tenant::factory()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
$pack = ReviewPack::factory()->create([
'tenant_id' => (int) $tenant->getKey(),
'workspace_id' => (int) $tenant->workspace_id,
'initiated_by_user_id' => (int) $user->getKey(),
'status' => ReviewPackStatus::Expired->value,
'fingerprint' => 'review-pack-expired',
'sha256' => 'sha-expired-pack',
'generated_at' => now()->subDays(3),
'expires_at' => now()->subDay(),
]);
$truth = app(ArtifactTruthPresenter::class)->forReviewPack($pack->fresh());
$state = $truth->toArray();
expect($state['displayReference'] ?? null)->not->toBeNull()
->and($state['displayReference'])->toContain('Review pack')
->and($state['displayReference'])->toContain('#'.$pack->getKey())
->and($state['integrityAnchor'] ?? null)->toBe('sha-expired-pack')
->and($state['lifecycleState'] ?? null)->toBe('historical')
->and($state['retentionState'] ?? null)->toBe('expired_direct_access');
});