browser()->timeout(20_000); function spec352BrowserScreenshotName(string $name): string { return 'spec352-environment-dashboard-'.$name; } /** * @return array{ * review:\App\Models\EnvironmentReview, * successor:\App\Models\EnvironmentReview|null, * reviewPack:ReviewPack, * } */ function spec352BrowserSeedBlockedReviewOutput(ManagedEnvironment $environment, User $user, bool $withSuccessorDraft = false): array { $snapshot = seedPartialEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0, operationRunCount: 0); $review = composeEnvironmentReviewForTest($environment, $user, $snapshot); $review->forceFill([ 'status' => EnvironmentReviewStatus::Published->value, 'published_at' => now()->subHour(), 'published_by_user_id' => (int) $user->getKey(), 'summary' => array_replace_recursive(is_array($review->summary) ? $review->summary : [], [ 'publish_blockers' => ['Operator approval note is still missing.'], ]), ])->save(); $reviewPack = 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(), 'file_path' => 'review-packs/spec352-browser.zip', 'file_disk' => 'exports', 'generated_at' => now()->subMinutes(10), 'options' => [ 'include_pii' => false, 'include_operations' => true, ], ]); $review->forceFill([ 'current_export_review_pack_id' => (int) $reviewPack->getKey(), ])->save(); $successor = null; if ($withSuccessorDraft) { $successor = app(EnvironmentReviewLifecycleService::class)->createNextReview($review->fresh(), $user, $snapshot); } return [ 'review' => $review->fresh(), 'successor' => $successor?->fresh(), 'reviewPack' => $reviewPack->fresh(), ]; } function spec352BrowserApplicationPermissionKey(): string { $permission = collect(spec283ConfiguredPermissionRows()) ->first(static fn (mixed $row): bool => is_array($row) && ($row['type'] ?? null) === 'application'); expect($permission)->not->toBeNull(); return (string) $permission['key']; } function spec352BrowserSeedPermissionRows( ManagedEnvironment $environment, array $missingKeys = [], array $errorKeys = [], ): void { foreach (spec283ConfiguredPermissionRows() as $permission) { if (! is_array($permission)) { continue; } $permissionKey = (string) ($permission['key'] ?? ''); if ($permissionKey === '') { continue; } ManagedEnvironmentPermission::query()->updateOrCreate( [ 'managed_environment_id' => (int) $environment->getKey(), 'permission_key' => $permissionKey, 'workspace_id' => (int) $environment->workspace_id, ], [ 'status' => in_array($permissionKey, $errorKeys, true) ? 'error' : (in_array($permissionKey, $missingKeys, true) ? 'missing' : 'granted'), 'details' => ['source' => 'spec352-browser-test'], 'last_checked_at' => now(), ], ); } } function spec352BrowserActAs(User $user, ManagedEnvironment $environment): void { test()->actingAs($user)->withSession([ WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id, WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [ (string) $environment->workspace_id => (int) $environment->getKey(), ], ]); } it('smokes provider-blocker guidance as the dominant dashboard case', function (): void { [$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); ProviderConnection::factory()->platform()->consentGranted()->create([ 'managed_environment_id' => (int) $environment->getKey(), 'workspace_id' => (int) $environment->workspace_id, 'is_default' => true, ]); spec352BrowserSeedBlockedReviewOutput($environment, $user, withSuccessorDraft: true); $missingPermissionKey = spec352BrowserApplicationPermissionKey(); spec352BrowserSeedPermissionRows($environment, missingKeys: [$missingPermissionKey]); spec352BrowserActAs($user, $environment); visit(EnvironmentDashboard::getUrl(panel: 'admin', tenant: $environment)) ->waitForText('Provider readiness blocks evidence refresh') ->assertSee('Recommended next action') ->assertSee('Review permissions') ->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-primary-next-action\"]')?.textContent?.includes('Review permissions') ?? false", true) ->assertScript("Array.from(document.querySelectorAll('a')).filter((node) => node.textContent?.trim().includes('Review permissions')).length === 1", true) ->assertScript("document.querySelectorAll('[data-testid=\"tenant-dashboard-operator-guidance-secondary-action\"]').length === 0", true) ->assertDontSee('No single repo-real follow-up is currently available.') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, spec352BrowserScreenshotName('provider-blocker')); }); it('smokes review-output guidance with subordinate secondary links when provider blockers are absent', function (): void { [$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); ProviderConnection::factory()->platform()->consentGranted()->create([ 'managed_environment_id' => (int) $environment->getKey(), 'workspace_id' => (int) $environment->workspace_id, 'is_default' => true, ]); spec352BrowserSeedBlockedReviewOutput($environment, $user, withSuccessorDraft: true); spec352BrowserSeedPermissionRows($environment); spec352BrowserActAs($user, $environment); visit(EnvironmentDashboard::getUrl(panel: 'admin', tenant: $environment)) ->waitForText('Draft review exists') ->assertSee('Open draft review') ->assertSee('Additional follow-ups') ->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-primary-next-action\"]')?.textContent?.includes('Open draft review') ?? false", true) ->assertScript("Array.from(document.querySelectorAll('a')).filter((node) => node.textContent?.trim().includes('Open draft review')).length === 1", true) ->assertScript("document.querySelectorAll('[data-testid=\"tenant-dashboard-operator-guidance-secondary-action\"]').length >= 1", true) ->assertScript("document.querySelector('[data-recommended-actions-style=\"compact\"]') !== null", true) ->assertDontSee('No single repo-real follow-up is currently available.') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, spec352BrowserScreenshotName('review-output')); }); it('smokes the no-urgent-action dashboard state with preserved secondary proof surfaces', function (): void { [$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); ProviderConnection::factory()->platform()->consentGranted()->create([ 'managed_environment_id' => (int) $environment->getKey(), 'workspace_id' => (int) $environment->workspace_id, 'is_default' => true, ]); spec352BrowserSeedPermissionRows($environment); workspaceOverviewSeedQuietTenantTruth($environment); $backupSet = workspaceOverviewSeedHealthyBackup($environment); workspaceOverviewSeedRestoreHistory($environment, $backupSet, 'completed'); spec352BrowserActAs($user, $environment); visit(EnvironmentDashboard::getUrl(panel: 'admin', tenant: $environment)) ->waitForText('No urgent operator action') ->assertSee('Review environment') ->assertSee('Readiness proof') ->assertScript("document.querySelector('[data-testid=\"tenant-dashboard-recommended-actions-empty\"]') !== null", true) ->assertScript("Array.from(document.querySelectorAll('a')).filter((node) => node.textContent?.trim().includes('Review environment')).length === 1", true) ->assertDontSee('No single repo-real follow-up is currently available.') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs() ->screenshot(true, spec352BrowserScreenshotName('no-urgent-action')); });