TenantAtlas/apps/platform/tests/Feature/ReviewPack/Spec349ReviewPackResolutionGuidanceTest.php
ahmido 9b46c0e435 feat: customer review workspace output resolution guidance (spec 349) (#420)
Implemented the output resolution guidance for the customer review workspace and internal views. Added ReviewPackOutputResolutionGuidance, updated CustomerReviewWorkspace and EnvironmentReviewResource, and added related blade views and tests.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #420
2026-06-03 01:35:55 +00:00

144 lines
6.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Support\EnvironmentReviewCompletenessState;
use App\Support\ReviewPacks\ReviewPackOutputReadiness;
use App\Support\ReviewPacks\ReviewPackOutputResolutionGuidance;
it('maps publication blockers into one primary guidance state and groups the remaining limitations', function (): void {
$readiness = spec349Readiness(
evidenceState: EnvironmentReviewCompletenessState::Partial->value,
hasReadyExport: false,
includePii: true,
publishBlockers: ['Operator approval note is still missing.'],
requiredSectionStates: [
EnvironmentReviewCompletenessState::Complete->value => 3,
EnvironmentReviewCompletenessState::Missing->value => 2,
],
);
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
'download' => '/review-packs/1/download',
'review' => '/environment-reviews/1',
'evidence' => '/evidence/1',
'operation' => '/operations/1',
]);
expect($guidance['state'])->toBe('publication_blocked')
->and($guidance['label'])->toBe('Output not customer-ready')
->and($guidance['primary_action']['label'])->toBe('Inspect review blockers')
->and($guidance['action_help'])->toContain('opens the review detail with blockers, evidence status, and next steps')
->and($guidance['limitations'])->toHaveCount(5)
->and($guidance['limitations'][0]['key'])->toBe('publish_blockers_present')
->and(collect($guidance['secondary_actions'])->pluck('label')->all())->not->toContain('Open review')
->and(collect($guidance['limitations'])->pluck('key')->all())->toEqual([
'publish_blockers_present',
'export_not_ready',
'evidence_basis_incomplete',
'required_sections_incomplete',
'contains_pii',
]);
});
it('maps incomplete evidence to a published-with-limitations guidance item', function (): void {
$readiness = spec349Readiness(
evidenceState: EnvironmentReviewCompletenessState::Missing->value,
hasReadyExport: true,
);
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
'review' => '/environment-reviews/1',
'evidence' => '/evidence/1',
]);
expect($guidance['state'])->toBe('published_with_limitations')
->and($guidance['primary_reason'])->toBe('Evidence basis is incomplete.')
->and($guidance['primary_action']['label'])->toBe('Open evidence basis')
->and($guidance['limitations'])->toHaveCount(1)
->and($guidance['limitations'][0]['label'])->toBe('Evidence basis incomplete');
});
it('renders required section limitation reasons through pluralization instead of raw translation ranges', function (): void {
$readiness = spec349Readiness(
hasReadyExport: true,
requiredSectionStates: [
EnvironmentReviewCompletenessState::Complete->value => 1,
EnvironmentReviewCompletenessState::Partial->value => 4,
EnvironmentReviewCompletenessState::Missing->value => 2,
],
);
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
'review' => '/environment-reviews/1',
]);
expect($guidance['limitations'])->toHaveCount(1)
->and($guidance['limitations'][0]['key'])->toBe('required_sections_incomplete')
->and($guidance['limitations'][0]['reason'])->toBe('6 required sections are partial, missing, or stale.')
->and($guidance['limitations'][0]['reason'])->not->toContain('{1}')
->and($guidance['limitations'][0]['reason'])->not->toContain('[2,*]');
});
it('marks pii-bearing ready exports as internal-only and qualifies the download action', function (): void {
$readiness = spec349Readiness(
includePii: true,
hasReadyExport: true,
);
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
'download' => '/review-packs/1/download',
'review' => '/environment-reviews/1',
]);
expect($guidance['state'])->toBe('internal_only')
->and($guidance['label'])->toBe('Internal review package available')
->and($guidance['primary_action']['label'])->toBe('Review PII/redaction state')
->and(collect($guidance['secondary_actions'])->pluck('label')->all())->toContain('Download internal review pack');
});
it('marks complete non-pii exports as customer-safe ready', function (): void {
$readiness = spec349Readiness(
hasReadyExport: true,
);
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
'download' => '/review-packs/1/download',
'review' => '/environment-reviews/1',
]);
expect($guidance['state'])->toBe('customer_safe_ready')
->and($guidance['label'])->toBe('Customer-safe review pack ready')
->and($guidance['primary_action']['label'])->toBe('Download customer-safe review pack')
->and($guidance['limitations'])->toBeEmpty();
});
/**
* @param array<string, int> $requiredSectionStates
* @param list<string> $publishBlockers
* @return array<string, mixed>
*/
function spec349Readiness(
string $evidenceState = EnvironmentReviewCompletenessState::Complete->value,
bool $hasReadyExport = true,
bool $includePii = false,
array $publishBlockers = [],
array $requiredSectionStates = [
EnvironmentReviewCompletenessState::Complete->value => 5,
],
): array {
return ReviewPackOutputReadiness::derive(
reviewStatus: 'published',
reviewCompletenessState: EnvironmentReviewCompletenessState::Complete->value,
evidenceCompletenessState: $evidenceState,
sectionStateCounts: $requiredSectionStates,
requiredSectionCount: array_sum($requiredSectionStates),
requiredSectionStateCounts: $requiredSectionStates,
publishBlockers: $publishBlockers,
hasReadyExport: $hasReadyExport,
includePii: $includePii,
protectedValuesHidden: true,
disclosurePresent: true,
);
}