Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
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.
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];
|
|
}
|