TenantAtlas/tests/Unit/Badges/GovernanceArtifactTruthTest.php
ahmido 8426741068 feat: add baseline snapshot truth guards (#189)
## Summary
- add explicit BaselineSnapshot lifecycle truth with conservative backfill and a shared truth resolver
- block baseline compare from building, incomplete, or superseded snapshots and align workspace/tenant UI truth surfaces with effective snapshot state
- surface artifact truth separately from operation outcome across baseline profile, snapshot, compare, and operation run pages

## Testing
- integrated browser smoke test on the active feature surfaces
- `vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineSnapshotTruthSurfaceTest.php tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php`
- targeted baseline lifecycle and compare guard coverage added in Pest
- `vendor/bin/sail bin pint --dirty --format agent`

## Notes
- Livewire v4 compliance preserved
- no panel provider registration changes were needed; Laravel 12 providers remain in `bootstrap/providers.php`
- global search remains disabled for the affected baseline resources by design
- destructive actions remain confirmation-gated; capture and compare actions keep their existing authorization and confirmation behavior
- no new panel assets were added; existing deploy flow for `filament:assets` is unchanged

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #189
2026-03-23 11:32:00 +00:00

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);
});