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.
291 lines
11 KiB
PHP
291 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Filament\Resources\EnvironmentReviewResource;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
pest()->browser()->timeout(60_000);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
it('Spec351 smokes blocked workspace execution guidance and ready detail execution guidance', function (): void {
|
|
[$user, $publishedBlockedEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$publishedBlockedEnvironment->forceFill(['name' => 'Spec351 Browser Published Blocked'])->save();
|
|
$mutableBlockedEnvironment = spec351BrowserEnvironmentFor($user, $publishedBlockedEnvironment, 'Spec351 Browser Mutable Blocked');
|
|
$readyDraftEnvironment = spec351BrowserEnvironmentFor($user, $publishedBlockedEnvironment, 'Spec351 Browser Ready Draft');
|
|
$fallbackEnvironment = spec351BrowserEnvironmentFor($user, $publishedBlockedEnvironment, 'Spec351 Browser Fallback');
|
|
[$readonlyUser] = createUserWithTenant(
|
|
tenant: $fallbackEnvironment,
|
|
user: User::factory()->create(),
|
|
role: 'readonly',
|
|
workspaceRole: 'readonly',
|
|
);
|
|
|
|
[$publishedBlockedReview] = spec351BrowserCreatePublishedReviewWithPack(
|
|
$publishedBlockedEnvironment,
|
|
$user,
|
|
seedPartialEnvironmentReviewEvidence($publishedBlockedEnvironment, findingCount: 0, driftCount: 0),
|
|
[
|
|
'publish_blockers' => ['Operator approval note is still missing.'],
|
|
],
|
|
[
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'review-packs/spec351-browser-blocked.zip',
|
|
);
|
|
|
|
$mutableBlockedReview = composeEnvironmentReviewForTest(
|
|
$mutableBlockedEnvironment,
|
|
$user,
|
|
seedPartialEnvironmentReviewEvidence($mutableBlockedEnvironment, findingCount: 0, driftCount: 0),
|
|
);
|
|
$mutableBlockedReview->forceFill([
|
|
'status' => EnvironmentReviewStatus::Draft->value,
|
|
'published_at' => null,
|
|
'published_by_user_id' => null,
|
|
])->save();
|
|
|
|
$readyReview = markEnvironmentReviewCustomerSafeReady(composeEnvironmentReviewForTest(
|
|
$readyDraftEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($readyDraftEnvironment, findingCount: 0, driftCount: 0),
|
|
));
|
|
$readyReview->forceFill([
|
|
'status' => EnvironmentReviewStatus::Ready->value,
|
|
'published_at' => null,
|
|
'published_by_user_id' => null,
|
|
])->save();
|
|
|
|
spec351BrowserCreatePublishedReviewWithPack(
|
|
$fallbackEnvironment,
|
|
$user,
|
|
seedPartialEnvironmentReviewEvidence($fallbackEnvironment, findingCount: 0, driftCount: 0),
|
|
[
|
|
'publish_blockers' => ['Operator approval note is still missing.'],
|
|
],
|
|
[
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'review-packs/spec351-browser-fallback.zip',
|
|
);
|
|
|
|
spec351AuthenticateBrowser($this, $user, $publishedBlockedEnvironment);
|
|
|
|
$workspacePage = visit(CustomerReviewWorkspace::environmentFilterUrl($publishedBlockedEnvironment))
|
|
->resize(1236, 900)
|
|
->waitForText('Output not customer-ready')
|
|
->assertSee('Create next review')
|
|
->assertSee('Supporting actions')
|
|
->assertSee('Package exists')
|
|
->assertSee('Customer sharing')
|
|
->click('[data-testid="customer-review-primary-action"]')
|
|
->waitForText('Create next review?')
|
|
->assertSee('Create next review?')
|
|
->click('button[wire\\:target="callMountedAction"]')
|
|
->waitForText('Refresh review')
|
|
->assertSee('Refresh review')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$workspacePage->screenshot(true, spec351BrowserScreenshotName('01-published-blocked'));
|
|
spec351CopyBrowserScreenshot('01-published-blocked');
|
|
|
|
$returnWorkspacePage = visit(CustomerReviewWorkspace::environmentFilterUrl($publishedBlockedEnvironment))
|
|
->waitForText('Draft review exists')
|
|
->assertSee('Open draft review')
|
|
->assertDontSee('No released customer reviews match the active environment filter.')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$returnWorkspacePage->screenshot(true, spec351BrowserScreenshotName('01b-workspace-successor'));
|
|
spec351CopyBrowserScreenshot('01b-workspace-successor');
|
|
|
|
$mutableBlockedPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $mutableBlockedReview], $mutableBlockedEnvironment))
|
|
->waitForText('Refresh review')
|
|
->click('Refresh review')
|
|
->waitForText('Refresh review')
|
|
->assertSee('Refresh this environment review from the latest eligible evidence basis.')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$mutableBlockedPage->screenshot(true, spec351BrowserScreenshotName('02-mutable-blocked'));
|
|
spec351CopyBrowserScreenshot('02-mutable-blocked');
|
|
|
|
$detailPage = visit(EnvironmentReviewResource::environmentScopedUrl('view', ['record' => $readyReview], $readyDraftEnvironment))
|
|
->waitForText('Publish review')
|
|
->click('Publish review')
|
|
->waitForText('Publication reason')
|
|
->assertSee('Publication reason')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$detailPage->screenshot(true, spec351BrowserScreenshotName('03-ready-draft'));
|
|
spec351CopyBrowserScreenshot('03-ready-draft');
|
|
|
|
spec351AuthenticateBrowser($this, $readonlyUser, $fallbackEnvironment);
|
|
|
|
$fallbackPage = visit(CustomerReviewWorkspace::environmentFilterUrl($fallbackEnvironment))
|
|
->waitForText('Output not customer-ready')
|
|
->assertSee('Inspect review blockers')
|
|
->assertDontSee('Create next review')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$fallbackPage->screenshot(true, spec351BrowserScreenshotName('04-fallback'));
|
|
spec351CopyBrowserScreenshot('04-fallback');
|
|
|
|
expect($publishedBlockedReview)->toBeInstanceOf(EnvironmentReview::class)
|
|
->and($mutableBlockedReview)->toBeInstanceOf(EnvironmentReview::class)
|
|
->and($readyReview)->toBeInstanceOf(EnvironmentReview::class);
|
|
});
|
|
|
|
function spec351BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec351-review-output-resolve-actions-'.$name;
|
|
}
|
|
|
|
function spec351CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec351BrowserScreenshotName($name).'.png';
|
|
$primarySource = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$fallbackSource = \Pest\Browser\Support\Screenshot::path($filename);
|
|
$targetDirectory = repo_path('specs/351-review-output-resolve-actions-v1/artifacts/screenshots');
|
|
|
|
if (! is_dir($targetDirectory)) {
|
|
@mkdir($targetDirectory, 0755, true);
|
|
}
|
|
|
|
$source = null;
|
|
|
|
for ($attempt = 0; $attempt < 50 && $source === null; $attempt++) {
|
|
foreach ([$primarySource, $fallbackSource] as $candidate) {
|
|
if (is_file($candidate)) {
|
|
$source = $candidate;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($source !== null) {
|
|
break;
|
|
}
|
|
|
|
usleep(100_000);
|
|
clearstatcache(true, $primarySource);
|
|
clearstatcache(true, $fallbackSource);
|
|
}
|
|
|
|
if (is_string($source) && is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
|
|
}
|
|
}
|
|
|
|
function spec351AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
|
|
{
|
|
$workspaceId = (int) $environment->workspace_id;
|
|
|
|
$test->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => $workspaceId,
|
|
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
],
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
|
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
]);
|
|
|
|
setAdminPanelContext($environment);
|
|
}
|
|
|
|
function spec351BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnvironment, string $name): ManagedEnvironment
|
|
{
|
|
$environment = ManagedEnvironment::factory()->active()->create([
|
|
'workspace_id' => (int) $baseEnvironment->workspace_id,
|
|
'name' => $name,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager');
|
|
|
|
return $environment;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $summaryOverrides
|
|
* @param array<string, mixed> $packOptions
|
|
* @return array{0: EnvironmentReview, 1: ReviewPack}
|
|
*/
|
|
function spec351BrowserCreatePublishedReviewWithPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
array $summaryOverrides = [],
|
|
array $packOptions = [],
|
|
string $filePath = 'review-packs/spec351-browser-review-pack.zip',
|
|
): array {
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$summary = array_replace_recursive(
|
|
is_array($review->summary) ? $review->summary : [],
|
|
[
|
|
'control_interpretation' => [
|
|
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
|
|
'controls' => [
|
|
[
|
|
'control_key' => 'customer-output',
|
|
'title' => 'Customer output',
|
|
'readiness_bucket' => 'review_recommended',
|
|
'readiness_label' => 'Review recommended',
|
|
'primary_reason' => 'Evidence basis needs review.',
|
|
'recommended_next_action' => 'Create the next review cycle before customer sharing.',
|
|
],
|
|
],
|
|
],
|
|
],
|
|
$summaryOverrides,
|
|
);
|
|
|
|
Storage::disk('exports')->put($filePath, 'PK-spec351-browser-test');
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'completeness_state' => (string) $review->completeness_state,
|
|
'summary' => $summary,
|
|
'generated_at' => now()->subMinutes(5),
|
|
'published_at' => now()->subMinutes(3),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'options' => array_replace([
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
], $packOptions),
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
'generated_at' => now()->subMinutes(4),
|
|
]);
|
|
|
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
|
|
|
return [$review->fresh(['tenant', 'evidenceSnapshot', 'currentExportReviewPack.operationRun', 'operationRun']), $pack];
|
|
}
|