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. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #419
102 lines
4.8 KiB
PHP
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];
|
|
}
|