Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m45s
Implemented the first version of review output resolve actions. Included a ReviewOutputResolveActionMapper, commands to seed browser fixtures, updated CustomerReviewWorkspace, EnvironmentReviewResource, UI enforcement, and related views. Also added extensive unit, feature, and browser tests, and updated the design coverage matrix.
246 lines
8.6 KiB
PHP
246 lines
8.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\EnvironmentReview;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\ResolutionGuidance\ResolutionAction;
|
|
use App\Support\ResolutionGuidance\ReviewOutputResolveActionMapper;
|
|
use App\Support\ReviewPacks\ReviewPackOutputReadiness;
|
|
use App\Support\ReviewPacks\ReviewPackOutputResolutionGuidance;
|
|
|
|
it('prefers a known successor review when one is concretely available', function (): void {
|
|
$review = spec351ReviewModel('published');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'published',
|
|
publishBlockers: ['Operator approval note is still missing.'],
|
|
hasReadyExport: true,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'environment_review_detail',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
'successor_review' => '/admin/reviews/42',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('open_successor_review')
|
|
->and($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_NAVIGATION)
|
|
->and($mapped['primary_action']['action_name'])->toBe('open_successor_review')
|
|
->and($mapped['primary_action']['url'])->toBe('/admin/reviews/42');
|
|
});
|
|
|
|
it('labels a known mutable successor as open draft review on the workspace', function (): void {
|
|
$review = spec351ReviewModel('published');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'published',
|
|
publishBlockers: ['Operator approval note is still missing.'],
|
|
hasReadyExport: true,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'customer_review_workspace',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
'successor_review' => '/admin/reviews/42',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
'successor_review_status' => 'draft',
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('open_successor_review')
|
|
->and($mapped['primary_action']['label'])->toBe('Open draft review')
|
|
->and($mapped['primary_action']['action_name'])->toBeNull()
|
|
->and($mapped['primary_action']['url'])->toBe('/admin/reviews/42');
|
|
});
|
|
|
|
it('prefers create next review for published blocked workspace output when execution is safe', function (): void {
|
|
$review = spec351ReviewModel('published');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'published',
|
|
publishBlockers: ['Operator approval note is still missing.'],
|
|
hasReadyExport: true,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'customer_review_workspace',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('create_next_review')
|
|
->and($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_OPERATION_ACTION)
|
|
->and($mapped['primary_action']['action_name'])->toBe('createNextReview')
|
|
->and($mapped['secondary_actions'][0]['key'])->toBe('resolve_review_blockers');
|
|
});
|
|
|
|
it('falls back honestly when published blocked output cannot execute on the current surface', function (): void {
|
|
$review = spec351ReviewModel('published');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'published',
|
|
publishBlockers: ['Operator approval note is still missing.'],
|
|
hasReadyExport: true,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'environment_review_detail.customer_workspace',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('resolve_review_blockers')
|
|
->and($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_NAVIGATION)
|
|
->and($mapped['primary_action']['action_name'])->toBeNull();
|
|
});
|
|
|
|
it('prefers refresh review for mutable blocked detail when execution is allowed', function (): void {
|
|
$review = spec351ReviewModel('draft');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'draft',
|
|
evidenceState: EnvironmentReviewCompletenessState::Partial->value,
|
|
hasReadyExport: false,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'environment_review_detail',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('refresh_review')
|
|
->and($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_OPERATION_ACTION)
|
|
->and($mapped['primary_action']['action_name'])->toBe('refresh_review');
|
|
});
|
|
|
|
it('prefers publish review for ready mutable detail when execution is allowed', function (): void {
|
|
$review = spec351ReviewModel('ready');
|
|
$guidance = spec351Guidance(
|
|
reviewStatus: 'ready',
|
|
hasReadyExport: false,
|
|
);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'environment_review_detail',
|
|
urls: [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
],
|
|
execution: [
|
|
'can_manage_review' => true,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['key'])->toBe('publish_review')
|
|
->and($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_DOMAIN_ACTION)
|
|
->and($mapped['primary_action']['action_name'])->toBe('publish_review');
|
|
});
|
|
|
|
it('keeps a none fallback for unsupported no-link states', function (): void {
|
|
$review = spec351ReviewModel('archived');
|
|
$guidance = ReviewPackOutputResolutionGuidance::fromReadiness([
|
|
'review_status' => 'archived',
|
|
'readiness_state' => 'unknown',
|
|
'primary_reason' => 'review_output_limitations',
|
|
'has_ready_export' => false,
|
|
'contains_pii' => false,
|
|
'limitations' => [],
|
|
'section_summary' => [],
|
|
'section_state_counts' => [],
|
|
'evidence_completeness_state' => EnvironmentReviewCompletenessState::Missing->value,
|
|
'required_section_state_counts' => [],
|
|
], []);
|
|
|
|
$mapped = ReviewOutputResolveActionMapper::map(
|
|
review: $review,
|
|
guidance: $guidance,
|
|
sourceSurface: 'environment_review_detail',
|
|
urls: [],
|
|
execution: [
|
|
'can_manage_review' => false,
|
|
],
|
|
);
|
|
|
|
expect($mapped['primary_action']['type'])->toBe(ResolutionAction::TYPE_NONE)
|
|
->and($mapped['secondary_actions'])->toBeEmpty();
|
|
});
|
|
|
|
function spec351ReviewModel(string $status): EnvironmentReview
|
|
{
|
|
$review = new EnvironmentReview;
|
|
$review->forceFill([
|
|
'id' => 41,
|
|
'workspace_id' => 1,
|
|
'managed_environment_id' => 7,
|
|
'status' => $status,
|
|
]);
|
|
|
|
return $review;
|
|
}
|
|
|
|
function spec351Guidance(
|
|
string $reviewStatus,
|
|
array $publishBlockers = [],
|
|
bool $hasReadyExport = false,
|
|
string $evidenceState = EnvironmentReviewCompletenessState::Complete->value,
|
|
): array {
|
|
$readiness = ReviewPackOutputReadiness::derive(
|
|
reviewStatus: $reviewStatus,
|
|
reviewCompletenessState: EnvironmentReviewCompletenessState::Complete->value,
|
|
evidenceCompletenessState: $evidenceState,
|
|
sectionStateCounts: [
|
|
EnvironmentReviewCompletenessState::Complete->value => 5,
|
|
],
|
|
requiredSectionCount: 5,
|
|
requiredSectionStateCounts: [
|
|
EnvironmentReviewCompletenessState::Complete->value => 5,
|
|
],
|
|
publishBlockers: $publishBlockers,
|
|
hasReadyExport: $hasReadyExport,
|
|
includePii: false,
|
|
protectedValuesHidden: true,
|
|
disclosurePresent: true,
|
|
);
|
|
|
|
return ReviewPackOutputResolutionGuidance::fromReadiness($readiness, [
|
|
'review' => '/admin/reviews/41',
|
|
'evidence' => '/admin/evidence/8',
|
|
'operation' => '/admin/operations/9',
|
|
'download' => $hasReadyExport ? '/admin/review-packs/8/download' : null,
|
|
]);
|
|
}
|