TenantAtlas/apps/platform/tests/Feature/ReviewPack/Spec347ReviewPackOutputContractTest.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

102 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\GenerateReviewPackJob;
use App\Models\ManagedEnvironment;
use App\Models\ReviewPack;
use App\Models\User;
use App\Services\ReviewPackService;
use Illuminate\Support\Facades\Storage;
beforeEach(function (): void {
Storage::fake('exports');
});
it('captures the spec347 required review pack files and metadata contract', function (): void {
[$user, $tenant] = createUserWithTenant(role: 'owner');
$review = composeEnvironmentReviewForTest($tenant, $user);
$pack = app(ReviewPackService::class)->generateFromReview($review, $user, [
'include_pii' => true,
'include_operations' => false,
]);
app()->call([new GenerateReviewPackJob(
reviewPackId: (int) $pack->getKey(),
operationRunId: (int) $pack->operation_run_id,
), 'handle']);
$pack->refresh();
[$zip, $tempFile, $filenames] = spec347OpenPackZip($pack);
$metadata = json_decode((string) $zip->getFromName('metadata.json'), true, 512, JSON_THROW_ON_ERROR);
$summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR);
$sections = json_decode((string) $zip->getFromName('sections.json'), true, 512, JSON_THROW_ON_ERROR);
expect($filenames)->toContain(
'metadata.json',
'summary.json',
'sections.json',
ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME,
)->and(data_get($metadata, 'delivery_bundle.contract'))->toBe(ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT)
->and(data_get($metadata, 'delivery_bundle.artifact_family'))->toBe('review_pack')
->and(data_get($metadata, 'delivery_bundle.review_pack_id'))->toBe((int) $pack->getKey())
->and(data_get($metadata, 'delivery_bundle.released_review.id'))->toBe((int) $review->getKey())
->and(data_get($metadata, 'delivery_bundle.released_review.status'))->toBe((string) $review->status)
->and(data_get($metadata, 'delivery_bundle.entrypoint.file'))->toBe(ReviewPackService::EXECUTIVE_ENTRYPOINT_FILENAME)
->and(data_get($metadata, 'delivery_bundle.entrypoint.role'))->toBe('executive_entrypoint')
->and(data_get($metadata, 'delivery_bundle.appendix.0.file'))->toBe('metadata.json')
->and(data_get($metadata, 'environment_review.id'))->toBe((int) $review->getKey())
->and(data_get($metadata, 'environment_review.status'))->toBe((string) $review->status)
->and(data_get($metadata, 'evidence_snapshot.id'))->toBe((int) $review->evidence_snapshot_id)
->and(data_get($metadata, 'evidence_snapshot.fingerprint'))->toBe((string) $review->evidenceSnapshot?->fingerprint)
->and(data_get($metadata, 'options.include_pii'))->toBeTrue()
->and(data_get($metadata, 'options.include_operations'))->toBeFalse()
->and(data_get($metadata, 'redaction_integrity.protected_values_hidden'))->toBeTrue()
->and(data_get($summary, 'has_ready_export'))->toBeTrue()
->and(data_get($summary, 'delivery_bundle.contract'))->toBe(ReviewPackService::REVIEW_DERIVED_DELIVERY_CONTRACT)
->and(data_get($summary, 'evidence_basis.completeness_state'))->toBe((string) $review->evidenceSnapshot?->completeness_state)
->and(data_get($summary, 'output_readiness.has_ready_export'))->toBeTrue()
->and(data_get($summary, 'output_readiness.contains_pii'))->toBeTrue()
->and($sections)->toBeArray()->not->toBeEmpty();
$firstSection = $sections[0];
$firstSectionFilename = sprintf(
'sections/%02d-%s.json',
(int) $firstSection['sort_order'],
(string) $firstSection['section_key'],
);
$firstSectionFile = json_decode((string) $zip->getFromName($firstSectionFilename), true, 512, JSON_THROW_ON_ERROR);
expect($filenames)->toContain($firstSectionFilename)
->and($firstSectionFile['section_key'] ?? null)->toBe($firstSection['section_key'])
->and($firstSectionFile['title'] ?? null)->toBe($firstSection['title'])
->and($firstSectionFile['sort_order'] ?? null)->toBe($firstSection['sort_order'])
->and($firstSectionFile['required'] ?? null)->toBe($firstSection['required'])
->and($firstSectionFile['completeness_state'] ?? null)->toBe($firstSection['completeness_state']);
$zip->close();
unlink($tempFile);
});
/**
* @return array{0: ZipArchive, 1: string, 2: list<string>}
*/
function spec347OpenPackZip(ReviewPack $pack): array
{
$zipContent = Storage::disk('exports')->get((string) $pack->file_path);
$tempFile = tempnam(sys_get_temp_dir(), 'spec347-review-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];
}