TenantAtlas/apps/platform/tests/Browser/Spec351ReviewOutputResolveActionsSmokeTest.php
ahmido d4e4d2d109 feat: review output resolve actions v1 (spec 351) (#422)
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.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #422
2026-06-04 00:55:02 +00:00

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];
}