116 lines
5.5 KiB
PHP
116 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\BaselineProfile;
|
|
use App\Models\BaselineSnapshot;
|
|
use App\Models\Tenant;
|
|
use App\Models\Workspace;
|
|
use App\Support\Badges\BadgeCatalog;
|
|
use App\Support\Badges\BadgeDomain;
|
|
use App\Support\Baselines\BaselineReasonCodes;
|
|
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\Feature\Concerns\BuildsGovernanceArtifactTruthFixtures;
|
|
|
|
uses(RefreshDatabase::class, BuildsGovernanceArtifactTruthFixtures::class);
|
|
|
|
it('maps each curated governance-artifact truth badge to a known label', function (BadgeDomain $domain, string $state): void {
|
|
$spec = BadgeCatalog::spec($domain, $state);
|
|
|
|
expect($spec->label)->not->toBe('Unknown')
|
|
->and($spec->icon)->not->toBeNull();
|
|
})->with([
|
|
'healthy baseline artifact' => [BadgeDomain::GovernanceArtifactContent, 'trusted'],
|
|
'false-green baseline artifact' => [BadgeDomain::GovernanceArtifactExistence, 'created_but_not_usable'],
|
|
'historical baseline trace' => [BadgeDomain::GovernanceArtifactExistence, 'historical_only'],
|
|
'healthy evidence snapshot' => [BadgeDomain::GovernanceArtifactContent, 'trusted'],
|
|
'partial evidence snapshot' => [BadgeDomain::GovernanceArtifactContent, 'partial'],
|
|
'stale evidence snapshot' => [BadgeDomain::GovernanceArtifactFreshness, 'stale'],
|
|
'internal tenant review' => [BadgeDomain::GovernanceArtifactPublicationReadiness, 'internal_only'],
|
|
'blocked tenant review' => [BadgeDomain::GovernanceArtifactPublicationReadiness, 'blocked'],
|
|
'publishable tenant review' => [BadgeDomain::GovernanceArtifactPublicationReadiness, 'publishable'],
|
|
'historical review pack' => [BadgeDomain::GovernanceArtifactExistence, 'historical_only'],
|
|
'blocked review pack' => [BadgeDomain::GovernanceArtifactPublicationReadiness, 'blocked'],
|
|
'artifact requires action' => [BadgeDomain::GovernanceArtifactActionability, 'required'],
|
|
]);
|
|
|
|
it('separates review existence from publication readiness', function (): void {
|
|
$tenant = Tenant::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = $this->makeArtifactTruthEvidenceSnapshot($tenant);
|
|
$review = $this->makeArtifactTruthReview(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
snapshot: $snapshot,
|
|
reviewOverrides: [
|
|
'status' => 'draft',
|
|
'completeness_state' => 'complete',
|
|
],
|
|
summaryOverrides: [
|
|
'publish_blockers' => ['Review the missing approval note before publication.'],
|
|
],
|
|
);
|
|
|
|
$truth = app(ArtifactTruthPresenter::class)->forTenantReview($review);
|
|
|
|
expect($truth->artifactExistence)->toBe('created')
|
|
->and($truth->publicationReadiness)->toBe('blocked')
|
|
->and($truth->primaryLabel)->toBe('Blocked')
|
|
->and($truth->nextStepText())->toContain('Resolve');
|
|
});
|
|
|
|
it('marks ready review packs as publishable only when their source review stays publishable', function (): void {
|
|
$tenant = Tenant::factory()->create();
|
|
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
|
$snapshot = $this->makeArtifactTruthEvidenceSnapshot($tenant);
|
|
$review = $this->makeArtifactTruthReview($tenant, $user, $snapshot);
|
|
$pack = $this->makeArtifactTruthReviewPack($tenant, $user, $snapshot, $review);
|
|
|
|
$truth = app(ArtifactTruthPresenter::class)->forReviewPack($pack);
|
|
|
|
expect($truth->artifactExistence)->toBe('created')
|
|
->and($truth->publicationReadiness)->toBe('publishable')
|
|
->and($truth->primaryLabel)->toBe('Publishable')
|
|
->and($truth->nextStepText())->toBe('No action needed');
|
|
});
|
|
|
|
it('keeps baseline snapshot lifecycle truth separate from current-vs-historical artifact truth', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$profile = BaselineProfile::factory()->active()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
]);
|
|
|
|
$historicalSnapshot = BaselineSnapshot::factory()->complete()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
'captured_at' => now()->subDay(),
|
|
'completed_at' => now()->subDay(),
|
|
]);
|
|
|
|
$currentSnapshot = BaselineSnapshot::factory()->complete()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
'captured_at' => now(),
|
|
'completed_at' => now(),
|
|
]);
|
|
|
|
$incompleteSnapshot = BaselineSnapshot::factory()->incomplete(BaselineReasonCodes::SNAPSHOT_CAPTURE_FAILED)->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'baseline_profile_id' => (int) $profile->getKey(),
|
|
]);
|
|
|
|
$profile->update(['active_snapshot_id' => (int) $currentSnapshot->getKey()]);
|
|
|
|
$historicalTruth = app(ArtifactTruthPresenter::class)->forBaselineSnapshot($historicalSnapshot->fresh());
|
|
$incompleteTruth = app(ArtifactTruthPresenter::class)->forBaselineSnapshot($incompleteSnapshot->fresh());
|
|
|
|
expect($historicalTruth->artifactExistence)->toBe('historical_only')
|
|
->and($historicalTruth->diagnosticLabel)->toBe('Superseded')
|
|
->and(BadgeCatalog::spec(BadgeDomain::BaselineSnapshotLifecycle, $historicalSnapshot->lifecycleState()->value)->label)->toBe('Complete');
|
|
|
|
expect($incompleteTruth->artifactExistence)->toBe('created_but_not_usable')
|
|
->and($incompleteTruth->diagnosticLabel)->toBe('Incomplete')
|
|
->and($incompleteTruth->reason?->reasonCode)->toBe(BaselineReasonCodes::SNAPSHOT_CAPTURE_FAILED);
|
|
});
|