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
112 lines
4.3 KiB
PHP
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];
|
|
}
|