TenantAtlas/apps/platform/tests/Feature/ReviewPack/Spec347ReviewPackReadinessSemanticsTest.php
Ahmed Darrazi 549a9a0004
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
feat: review pack output contract and readiness semantics (spec 347)
Implemented the output contract and readiness semantics for review packs. Also added spec 348.
Includes changes to ChooseEnvironment, CustomerReviewWorkspace, GenerateReviewPackJob and related blade views.
Added comprehensive tests.
2026-06-03 01:14:29 +02:00

112 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\GenerateReviewPackJob;
use App\Models\ReviewPack;
use App\Models\User;
use App\Services\ReviewPackService;
use App\Support\ReviewPackStatus;
use Illuminate\Support\Facades\Storage;
beforeEach(function (): void {
Storage::fake('exports');
});
it('keeps section appendix files even when readiness stays limitation-aware', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$snapshot = seedPartialEnvironmentReviewEvidence($tenant, findingCount: 0, driftCount: 0);
$review = composeEnvironmentReviewForTest($tenant, $user, $snapshot);
$pack = app(ReviewPackService::class)->generateFromReview($review, $user, [
'include_pii' => false,
'include_operations' => true,
]);
app()->call([new GenerateReviewPackJob(
reviewPackId: (int) $pack->getKey(),
operationRunId: (int) $pack->operation_run_id,
), 'handle']);
$pack->refresh();
[$zip, $tempFile, $filenames] = spec347ReadinessZip($pack);
$summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR);
$sections = collect(json_decode((string) $zip->getFromName('sections.json'), true, 512, JSON_THROW_ON_ERROR));
$executive = (string) $zip->getFromName(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME);
$limitedSection = $sections->first(fn (array $section): bool => (string) $section['completeness_state'] !== 'complete');
expect($pack->status)->toBe(ReviewPackStatus::Ready->value)
->and(data_get($summary, 'has_ready_export'))->toBeTrue()
->and(data_get($summary, 'output_readiness.readiness_state'))->toBe('published_with_limitations')
->and(data_get($summary, 'output_readiness.evidence_completeness_state'))->toBe((string) $snapshot->completeness_state)
->and((int) data_get($summary, 'output_readiness.section_summary.required_limited'))->toBeGreaterThan(0)
->and($limitedSection)->not->toBeNull();
$limitedSectionFilename = sprintf(
'sections/%02d-%s.json',
(int) $limitedSection['sort_order'],
(string) $limitedSection['section_key'],
);
expect($filenames)->toContain($limitedSectionFilename)
->and($executive)->toContain('## Limitations')
->and($executive)->toContain('incomplete evidence basis')
->and($executive)->toContain('structured appendices but are marked missing');
$zip->close();
unlink($tempFile);
});
it('distinguishes ready export from customer-safe readiness when pii is included', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user);
$review = markEnvironmentReviewCustomerSafeReady($review);
$pack = app(ReviewPackService::class)->generateFromReview($review, $user, [
'include_pii' => true,
'include_operations' => true,
]);
app()->call([new GenerateReviewPackJob(
reviewPackId: (int) $pack->getKey(),
operationRunId: (int) $pack->operation_run_id,
), 'handle']);
$pack->refresh();
[$zip, $tempFile] = spec347ReadinessZip($pack);
$summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR);
$executive = (string) $zip->getFromName(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME);
expect(data_get($summary, 'has_ready_export'))->toBeTrue()
->and(data_get($summary, 'output_readiness.readiness_state'))->toBe('internal_review_package_available')
->and(data_get($summary, 'output_readiness.customer_safe_state'))->toBe('internal_only')
->and(data_get($summary, 'output_readiness.contains_pii'))->toBeTrue()
->and($executive)->toContain('PII is included in this package');
$zip->close();
unlink($tempFile);
});
/**
* @return array{0: ZipArchive, 1: string, 2: list<string>}
*/
function spec347ReadinessZip(ReviewPack $pack): array
{
$zipContent = Storage::disk('exports')->get((string) $pack->file_path);
$tempFile = tempnam(sys_get_temp_dir(), 'spec347-readiness-pack-');
file_put_contents($tempFile, $zipContent);
$zip = new ZipArchive;
$zip->open($tempFile);
$filenames = collect(range(0, $zip->numFiles - 1))
->map(fn (int $index): string => (string) $zip->getNameIndex($index))
->values()
->all();
return [$zip, $tempFile, $filenames];
}