Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m42s
Implemented the output resolution guidance for the customer review workspace and internal views. Added ReviewPackOutputResolutionGuidance, updated CustomerReviewWorkspace and EnvironmentReviewResource, and added related blade views and tests.
405 lines
19 KiB
PHP
405 lines
19 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\Finding;
|
|
use App\Models\FindingException;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
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\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\ReviewPackStatus;
|
|
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('Spec342 smokes final customer review consumption states', function (): void {
|
|
[$user, $notReadyEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$notReadyEnvironment->forceFill(['name' => 'Spec342 Browser Evidence Incomplete'])->save();
|
|
|
|
$readyEnvironment = spec342BrowserEnvironmentFor($user, $notReadyEnvironment, 'Spec342 Browser Ready');
|
|
$findingsEnvironment = spec342BrowserEnvironmentFor($user, $notReadyEnvironment, 'Spec342 Browser Findings');
|
|
$acceptedRiskEnvironment = spec342BrowserEnvironmentFor($user, $notReadyEnvironment, 'Spec342 Browser Accepted Risks');
|
|
|
|
spec342BrowserCreatePublishedReviewWithPack(
|
|
$notReadyEnvironment,
|
|
$user,
|
|
seedPartialEnvironmentReviewEvidence($notReadyEnvironment, findingCount: 0, driftCount: 0),
|
|
[
|
|
'debug_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
'stack_trace' => 'stack trace should stay hidden',
|
|
'source_fingerprint' => 'spec342-browser-hidden-fingerprint',
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'incomplete',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Partial->value,
|
|
'decision_data_state' => 'incomplete',
|
|
'total_count' => 1,
|
|
'summary' => 'Decision evidence is incomplete for this released review.',
|
|
'next_action' => 'Review the evidence basis before relying on the decision summary.',
|
|
'entries' => [],
|
|
],
|
|
],
|
|
],
|
|
'review-packs/spec342-browser-evidence-incomplete.zip',
|
|
normalizeOutputReadiness: false,
|
|
);
|
|
|
|
spec342BrowserCreatePublishedReviewWithPack(
|
|
$readyEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($readyEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec342-browser-ready.zip',
|
|
);
|
|
|
|
spec342BrowserCreatePublishedReviewWithPack(
|
|
$findingsEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($findingsEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
'review-packs/spec342-browser-findings.zip',
|
|
);
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $findingsEnvironment->getKey(),
|
|
'workspace_id' => (int) $findingsEnvironment->workspace_id,
|
|
'severity' => Finding::SEVERITY_CRITICAL,
|
|
'status' => Finding::STATUS_NEW,
|
|
]);
|
|
|
|
spec342BrowserCreatePublishedReviewWithPack(
|
|
$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/spec342-browser-accepted-risk.zip',
|
|
);
|
|
spec342BrowserCreateAcceptedRisk($acceptedRiskEnvironment, $user);
|
|
|
|
spec342AuthenticateBrowser($this, $user, $notReadyEnvironment);
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($notReadyEnvironment))
|
|
->resize(1236, 900)
|
|
->waitForText('Output not customer-ready')
|
|
->assertSee('Review blockers are still recorded for this output.')
|
|
->assertSee('Needs review')
|
|
->assertSee('Download review pack with limitations')
|
|
->assertSee('Review consumption flow')
|
|
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-readiness-step\"]").length === 6', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Available"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Needs review"', true)
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true)
|
|
->assertDontSee('raw payload should stay hidden')
|
|
->assertDontSee('provider response should stay hidden')
|
|
->assertDontSee('stack trace should stay hidden')
|
|
->assertDontSee('spec342-browser-hidden-fingerprint')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec342BrowserScreenshotName('01-evidence-incomplete-not-ready'));
|
|
spec342CopyBrowserScreenshot('01-evidence-incomplete-not-ready');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
|
->waitForText('Customer-safe review pack ready')
|
|
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
|
->assertSee('Download customer-safe review pack')
|
|
->assertSee('Review pack state')
|
|
->assertSee('Export ready')
|
|
->assertSee('Operation proof')
|
|
->assertSee('Spec342 Browser Operator')
|
|
->assertSee('No open findings require customer action.')
|
|
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-primary-action\"]").length === 1', true)
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-secondary-action\"]")?.innerText.includes("Download customer-safe review pack") === false', true)
|
|
->assertScript('document.querySelectorAll("[data-testid=\"customer-review-readiness-step\"]").length === 6', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Ready"', true)
|
|
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"customer-review-readiness-step\"] [class*=\"badge\"], [data-testid=\"customer-review-review-pack-panel\"] [class*=\"badge\"], [data-testid=\"customer-review-accepted-risk-panel\"] [class*=\"badge\"]")).every((badge) => ! badge.innerText.includes("..."))', true)
|
|
->assertScript('document.body.innerHTML.includes("source_surface=customer_review_workspace")', true)
|
|
->assertScript('! document.body.innerHTML.includes("/admin/t/") && ! window.location.search.includes("tenant_id=")', true)
|
|
->assertDontSee('Auditor-ready')
|
|
->assertDontSee('environment is healthy')
|
|
->assertDontSee('compliant')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec342BrowserScreenshotName('02-ready-with-evidence'));
|
|
spec342CopyBrowserScreenshot('02-ready-with-evidence');
|
|
|
|
$page->script('document.querySelector("[data-testid=\"customer-review-review-pack-panel\"]")?.scrollIntoView({ block: "center" });');
|
|
$page
|
|
->assertSee('Export availability')
|
|
->assertSee('Available');
|
|
$page->screenshot(true, spec342BrowserScreenshotName('03-review-pack-available'));
|
|
spec342CopyBrowserScreenshot('03-review-pack-available');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($findingsEnvironment))
|
|
->waitForText('Findings needing attention')
|
|
->assertSee('Published with limitations')
|
|
->assertSee('1 open finding needs attention; 1 is high impact.')
|
|
->assertSee('Keep open findings visible before customer handoff.')
|
|
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
|
->assertSee('High impact')
|
|
->assertSee('Open review')
|
|
->assertSee('Download review pack with limitations')
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download review pack with limitations") === true', true)
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-evidence-path-panel\"]")?.innerText.includes("Download review pack") === false', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepState === "Needs review"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Findings triaged\"]")?.dataset.stepCurrent === "true"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Needs review"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec342BrowserScreenshotName('04-findings-need-attention'));
|
|
spec342CopyBrowserScreenshot('04-findings-need-attention');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($acceptedRiskEnvironment))
|
|
->waitForText('Published with limitations')
|
|
->assertSee('Accepted-risk follow-up is recorded for this review. Review the owner, rationale, and review date before handoff.')
|
|
->assertSee('The pack can be shared only with the accepted-risk context included in the customer handoff.')
|
|
->assertSee('Open review')
|
|
->assertSee('Follow-up required')
|
|
->assertSee('Accepted-risk accountability')
|
|
->assertSee('Spec342 Browser Risk Owner')
|
|
->assertSee('Customer-approved maintenance window.')
|
|
->assertSee('Review date not recorded')
|
|
->assertSee('Accepted risk requires customer awareness.')
|
|
->assertSee('Download review pack with limitations')
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-decision-card\"]")?.innerText.includes("Download review pack with limitations") === true', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Accepted risks reviewed\"]")?.dataset.stepCurrent === "true"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec342BrowserScreenshotName('05-accepted-risks-present'));
|
|
spec342CopyBrowserScreenshot('05-accepted-risks-present');
|
|
|
|
$page->assertScript('document.querySelector("[data-testid=\"customer-review-diagnostics\"]")?.open === false', true);
|
|
$page->screenshot(true, spec342BrowserScreenshotName('06-diagnostics-collapsed'));
|
|
spec342CopyBrowserScreenshot('06-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, spec342BrowserScreenshotName('07-dark-mode'));
|
|
spec342CopyBrowserScreenshot('07-dark-mode');
|
|
});
|
|
|
|
function spec342BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec342-customer-review-workspace-'.$name;
|
|
}
|
|
|
|
function spec342CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec342BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/342-customer-review-workspace-final-consumption-productization/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 spec342AuthenticateBrowser(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 spec342BrowserEnvironmentFor(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
|
|
* @return array{0: EnvironmentReview, 1: ReviewPack}
|
|
*/
|
|
function spec342BrowserCreatePublishedReviewWithPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
array $summaryOverrides = [],
|
|
string $filePath = 'review-packs/spec342-browser-review-pack.zip',
|
|
bool $normalizeOutputReadiness = true,
|
|
): 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' => 'Spec342 Browser Operator',
|
|
]);
|
|
|
|
Storage::disk('exports')->put($filePath, 'PK-spec342-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();
|
|
if ($normalizeOutputReadiness) {
|
|
$review = markEnvironmentReviewCustomerSafeReady($review);
|
|
}
|
|
|
|
$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,
|
|
'options' => [
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'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 spec342BrowserCreateAcceptedRisk(ManagedEnvironment $environment, User $user): void
|
|
{
|
|
$owner = User::factory()->create(['name' => 'Spec342 Browser Risk Owner']);
|
|
$findingWithReviewDate = Finding::factory()->riskAccepted()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
]);
|
|
$findingWithoutReviewDate = 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) $findingWithReviewDate->getKey(),
|
|
'status' => FindingException::STATUS_ACTIVE,
|
|
'current_validity_state' => FindingException::VALIDITY_VALID,
|
|
'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),
|
|
]);
|
|
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'finding_id' => (int) $findingWithoutReviewDate->getKey(),
|
|
'status' => FindingException::STATUS_ACTIVE,
|
|
'current_validity_state' => FindingException::VALIDITY_EXPIRING,
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'request_reason' => 'Pending owner confirmation.',
|
|
'requested_at' => now()->subDay(),
|
|
'approved_at' => now()->subDay(),
|
|
'effective_from' => now()->subDay(),
|
|
'review_due_at' => null,
|
|
'expires_at' => null,
|
|
]);
|
|
}
|