browser()->timeout(60_000); beforeEach(function (): void { Storage::fake('exports'); }); it('Spec343 smokes customer review acknowledgement and accepted risk lifecycle surfaces', function (): void { [$user, $ackRequiredEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager'); $ackRequiredEnvironment->forceFill(['name' => 'Spec343 Browser Ack Required'])->save(); $acknowledgedEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Acknowledged'); $acceptedRiskEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Accepted Risks'); $acceptedRiskExpiredEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser Accepted Risk Expired'); $noAcceptedRiskEnvironment = spec343BrowserEnvironmentFor($user, $ackRequiredEnvironment, 'Spec343 Browser No Accepted Risks'); [$ackRequiredReview] = spec343BrowserCreatePublishedReviewWithPack( $ackRequiredEnvironment, $user, seedEnvironmentReviewEvidence($ackRequiredEnvironment, findingCount: 0, driftCount: 0), [], 'review-packs/spec343-browser-ack-required.zip', ); [$acknowledgedReview, $acknowledgedPack] = spec343BrowserCreatePublishedReviewWithPack( $acknowledgedEnvironment, $user, seedEnvironmentReviewEvidence($acknowledgedEnvironment, findingCount: 0, driftCount: 0), [], 'review-packs/spec343-browser-acknowledged.zip', ); EnvironmentReviewAcknowledgement::query()->create([ 'managed_environment_id' => (int) $acknowledgedEnvironment->getKey(), 'workspace_id' => (int) $acknowledgedEnvironment->workspace_id, 'environment_review_id' => (int) $acknowledgedReview->getKey(), 'acknowledged_at' => now()->subHours(2), 'acknowledged_by_user_id' => (int) $user->getKey(), 'comment' => 'Review pack reviewed and understood.', 'evidence_snapshot_id' => (int) $acknowledgedReview->evidence_snapshot_id, 'review_pack_id' => (int) $acknowledgedPack->getKey(), ]); spec343BrowserCreatePublishedReviewWithPack( $acceptedRiskEnvironment, $user, seedEnvironmentReviewEvidence($acceptedRiskEnvironment, findingCount: 0, driftCount: 0), [ 'governance_package' => [ 'accepted_risks' => [ [ 'title' => 'Accepted risk renewal', 'governance_state' => 'expiring_exception', 'customer_summary' => 'Accepted risk requires customer awareness.', ], ], ], ], 'review-packs/spec343-browser-accepted-risk.zip', ); spec343BrowserCreateAcceptedRisk($acceptedRiskEnvironment, $user, state: FindingException::VALIDITY_EXPIRING); spec343BrowserCreatePublishedReviewWithPack( $acceptedRiskExpiredEnvironment, $user, seedEnvironmentReviewEvidence($acceptedRiskExpiredEnvironment, findingCount: 0, driftCount: 0), [], 'review-packs/spec343-browser-accepted-risk-expired.zip', ); spec343BrowserCreateAcceptedRisk($acceptedRiskExpiredEnvironment, $user, state: FindingException::VALIDITY_EXPIRED); spec343BrowserCreatePublishedReviewWithPack( $noAcceptedRiskEnvironment, $user, seedEnvironmentReviewEvidence($noAcceptedRiskEnvironment, findingCount: 0, driftCount: 0), [], 'review-packs/spec343-browser-no-accepted-risk.zip', ); spec343AuthenticateBrowser($this, $user, $ackRequiredEnvironment); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($ackRequiredEnvironment)) ->resize(1236, 900) ->waitForText(__('localization.review.review_acknowledgement')) ->assertSee(__('localization.review.acknowledgement_required')) ->assertSee(__('localization.review.acknowledge_review')) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('01-acknowledgement-required')); spec343CopyBrowserScreenshot('01-acknowledgement-required'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($acknowledgedEnvironment)) ->waitForText(__('localization.review.review_acknowledgement')) ->assertSee(__('localization.review.review_acknowledged')) ->assertSee('Review pack reviewed and understood.') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('02-review-acknowledged')); spec343CopyBrowserScreenshot('02-review-acknowledged'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment)) ->waitForText(__('localization.review.accepted_risk_summary')) ->assertSee(__('localization.review.accepted_risk_accountability')) ->assertSee(__('localization.review.accepted_risk_follow_up')) ->assertSee('Accepted risk requires customer awareness.') ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('03-accepted-risks-present')); spec343CopyBrowserScreenshot('03-accepted-risks-present'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment)) ->waitForText(__('localization.review.accepted_risk_accountability')) ->assertSee(__('localization.review.accepted_risks_expiring_soon')) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('04-accepted-risk-due-for-review')); spec343CopyBrowserScreenshot('04-accepted-risk-due-for-review'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskExpiredEnvironment)) ->waitForText(__('localization.review.accepted_risk_summary')) ->assertSee(__('localization.review.accepted_risks_expired')) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('05-accepted-risk-expired')); spec343CopyBrowserScreenshot('05-accepted-risk-expired'); $page = visit(CustomerReviewWorkspace::environmentFilterUrl($noAcceptedRiskEnvironment)) ->waitForText(__('localization.review.accepted_risk_summary')) ->assertSee(__('localization.review.no_accepted_risks_recorded')) ->assertNoJavaScriptErrors() ->assertNoConsoleLogs(); $page->screenshot(true, spec343BrowserScreenshotName('06-no-accepted-risks')); spec343CopyBrowserScreenshot('06-no-accepted-risks'); $page->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true); $page->screenshot(true, spec343BrowserScreenshotName('07-diagnostics-collapsed')); spec343CopyBrowserScreenshot('07-diagnostics-collapsed'); $page->script("document.documentElement.classList.add('dark');"); $page->script('window.scrollTo(0, 0);'); $page->assertScript('document.documentElement.classList.contains("dark")', true); $page->screenshot(true, spec343BrowserScreenshotName('08-dark-mode')); spec343CopyBrowserScreenshot('08-dark-mode'); expect(EnvironmentReviewAcknowledgement::query() ->where('environment_review_id', (int) $acknowledgedReview->getKey()) ->exists())->toBeTrue(); expect(EnvironmentReviewAcknowledgement::query() ->where('environment_review_id', (int) $ackRequiredReview->getKey()) ->exists())->toBeFalse(); }); function spec343BrowserScreenshotName(string $name): string { return 'spec343-customer-review-attestation-'.$name; } function spec343CopyBrowserScreenshot(string $name): void { $filename = spec343BrowserScreenshotName($name).'.png'; $source = base_path('tests/Browser/Screenshots/'.$filename); $targetDirectory = repo_path('specs/343-customer-review-attestation-accepted-risk-lifecycle/artifacts/screenshots'); if (! is_dir($targetDirectory)) { @mkdir($targetDirectory, 0755, true); } if (! is_file($source)) { $source = \Pest\Browser\Support\Screenshot::path($filename); } for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) { usleep(100_000); clearstatcache(true, $source); } if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) { @copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png'); } } function spec343AuthenticateBrowser(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 spec343BrowserEnvironmentFor(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 $summaryOverrides * @return array{0: EnvironmentReview, 1: ReviewPack} */ function spec343BrowserCreatePublishedReviewWithPack( ManagedEnvironment $environment, User $user, EvidenceSnapshot $snapshot, array $summaryOverrides = [], string $filePath = 'review-packs/spec343-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' => 'evidence_on_record', 'readiness_label' => 'Evidence on record', 'primary_reason' => 'Evidence path is complete.', 'recommended_next_action' => 'Open the current customer review pack.', ], ], ], 'governance_package' => [ 'decision_summary' => [ 'status' => 'none', 'evidence_state' => EnvironmentReviewCompletenessState::Complete->value, 'decision_data_state' => 'complete', 'total_count' => 0, 'summary' => 'No governance decisions require customer awareness.', 'next_action' => 'Open the current customer review pack.', 'entries' => [], ], ], ], $summaryOverrides, ); $run = OperationRun::factory()->forTenant($environment)->create([ 'type' => OperationRunType::ReviewPackGenerate->value, 'status' => OperationRunStatus::Completed->value, 'outcome' => OperationRunOutcome::Succeeded->value, 'started_at' => now()->subMinutes(6), 'completed_at' => now()->subMinutes(4), 'initiator_name' => 'Spec343 Browser Operator', ]); Storage::disk('exports')->put($filePath, 'PK-spec343-browser-test'); $review->forceFill([ 'status' => EnvironmentReviewStatus::Published->value, 'completeness_state' => EnvironmentReviewCompletenessState::Complete->value, 'summary' => $summary, 'operation_run_id' => (int) $run->getKey(), '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(), 'operation_run_id' => (int) $run->getKey(), 'initiated_by_user_id' => (int) $user->getKey(), 'status' => ReviewPackStatus::Ready->value, 'file_path' => $filePath, 'file_disk' => 'exports', 'generated_at' => now()->subMinutes(4), ]); $review->forceFill([ 'current_export_review_pack_id' => (int) $pack->getKey(), ])->save(); return [$review->refresh(), $pack->refresh()]; } function spec343BrowserCreateAcceptedRisk(ManagedEnvironment $environment, User $user, string $state): void { $owner = User::factory()->create(['name' => 'Spec343 Browser Risk Owner']); $finding = Finding::factory()->riskAccepted()->create([ 'managed_environment_id' => (int) $environment->getKey(), 'workspace_id' => (int) $environment->workspace_id, ]); FindingException::query()->create([ 'workspace_id' => (int) $environment->workspace_id, 'managed_environment_id' => (int) $environment->getKey(), 'finding_id' => (int) $finding->getKey(), 'status' => FindingException::STATUS_ACTIVE, 'current_validity_state' => $state, 'requested_by_user_id' => (int) $user->getKey(), 'request_reason' => 'Customer-approved maintenance window.', 'owner_user_id' => (int) $owner->getKey(), 'approved_by_user_id' => (int) $owner->getKey(), 'requested_at' => now()->subDays(3), 'approved_at' => now()->subDays(2), 'effective_from' => now()->subDays(2), 'review_due_at' => now()->addDays(30), 'expires_at' => $state === FindingException::VALIDITY_EXPIRED ? now()->subDay() : null, ]); }