355 lines
17 KiB
PHP
355 lines
17 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\StoredReport;
|
|
use App\Models\User;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Evidence\EvidenceCompletenessState;
|
|
use App\Support\Evidence\EvidenceSnapshotStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\ReviewPackStatus;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
pest()->browser()->timeout(60_000);
|
|
|
|
function spec337BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec337-evidence-review-pack-'.$name;
|
|
}
|
|
|
|
function spec337CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec337BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/337-evidence-review-pack-product-process-flow-alignment/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 spec337AuthenticateBrowser(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 spec337BrowserEnvironmentFor(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;
|
|
}
|
|
|
|
it('Spec337 smokes Evidence Review Pack product process flow states', function (): void {
|
|
[$user, $missingEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$missingEnvironment->forceFill(['name' => 'Spec337 Evidence Snapshot Required'])->save();
|
|
|
|
$generatingEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Evidence Generating');
|
|
$generatingRun = OperationRun::factory()->forTenant($generatingEnvironment)->create([
|
|
'type' => OperationRunType::EvidenceSnapshotGenerate->value,
|
|
'status' => OperationRunStatus::Running->value,
|
|
'outcome' => OperationRunOutcome::Pending->value,
|
|
'started_at' => now()->subMinute(),
|
|
'initiator_name' => 'Spec337 Browser Operator',
|
|
]);
|
|
spec337BrowserCreateEvidenceSnapshot($generatingEnvironment, [
|
|
'operation_run_id' => (int) $generatingRun->getKey(),
|
|
'status' => EvidenceSnapshotStatus::Generating->value,
|
|
'generated_at' => null,
|
|
]);
|
|
|
|
$storedReportRequiredEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Stored Report Required');
|
|
spec337BrowserCreateEvidenceSnapshot($storedReportRequiredEnvironment);
|
|
|
|
$reviewPackRequiredEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Review Pack Required');
|
|
spec337BrowserCreateEvidenceSnapshot($reviewPackRequiredEnvironment);
|
|
spec337BrowserCreateStoredReport($reviewPackRequiredEnvironment);
|
|
|
|
$availableEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Review Pack Available');
|
|
$availableSnapshot = spec337BrowserCreateEvidenceSnapshot($availableEnvironment);
|
|
spec337BrowserCreateStoredReport($availableEnvironment);
|
|
spec337BrowserCreatePublishedReviewWithReadyPack($availableEnvironment, $user, $availableSnapshot);
|
|
|
|
$exportUnavailableEnvironment = spec337BrowserEnvironmentFor($user, $missingEnvironment, 'Spec337 Export Unavailable');
|
|
$exportUnavailableSnapshot = spec337BrowserCreateEvidenceSnapshot($exportUnavailableEnvironment);
|
|
spec337BrowserCreateStoredReport($exportUnavailableEnvironment);
|
|
ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $exportUnavailableEnvironment->getKey(),
|
|
'workspace_id' => (int) $exportUnavailableEnvironment->workspace_id,
|
|
'evidence_snapshot_id' => (int) $exportUnavailableSnapshot->getKey(),
|
|
'file_disk' => null,
|
|
'file_path' => null,
|
|
'file_size' => null,
|
|
'sha256' => null,
|
|
]);
|
|
|
|
spec337AuthenticateBrowser($this, $user, $missingEnvironment);
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $missingEnvironment->getKey(),
|
|
]))
|
|
->resize(1236, 862)
|
|
->waitForText('Evidence snapshot required')
|
|
->assertSee('Is this evidence package ready for customer or auditor consumption?')
|
|
->assertSee('Generate evidence snapshot')
|
|
->assertSee('Evidence readiness flow')
|
|
->assertScript('document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]").length === 6', true)
|
|
->assertScript('(() => { const list = document.querySelector("[data-testid=\"evidence-readiness-flow\"] ol"); return list !== null && getComputedStyle(list).flexDirection === "column"; })()', true)
|
|
->assertScript('(() => { const steps = Array.from(document.querySelectorAll("[data-testid=\"evidence-readiness-step\"]")); return steps.length === 6 && steps.every((step) => step.getBoundingClientRect().width >= 300); })()', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Missing"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
|
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
|
|
->assertDontSee('raw payload should stay hidden')
|
|
->assertDontSee('provider response should stay hidden')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('01-evidence-snapshot-required'));
|
|
spec337CopyBrowserScreenshot('01-evidence-snapshot-required');
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $generatingEnvironment->getKey(),
|
|
]))
|
|
->waitForText('Evidence generation in progress')
|
|
->assertSee('View operation progress')
|
|
->assertSee('Operation proof')
|
|
->assertSee('Spec337 Browser Operator')
|
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepState === "Generating"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Evidence snapshot\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('02-evidence-generating'));
|
|
spec337CopyBrowserScreenshot('02-evidence-generating');
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $storedReportRequiredEnvironment->getKey(),
|
|
]))
|
|
->waitForText('Stored report required')
|
|
->assertSee('Open evidence snapshot')
|
|
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepState === "Missing"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Stored report\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
|
->assertDontSee('Download export')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('03-stored-report-required'));
|
|
spec337CopyBrowserScreenshot('03-stored-report-required');
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $reviewPackRequiredEnvironment->getKey(),
|
|
]))
|
|
->waitForText('Review pack required')
|
|
->assertSee('Generate review pack')
|
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Required"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepCurrentBlocker === "true"', true)
|
|
->assertScript('document.querySelector("[data-testid=\"evidence-primary-proof-action\"]") instanceof HTMLAnchorElement', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('04-review-pack-required'));
|
|
spec337CopyBrowserScreenshot('04-review-pack-required');
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $availableEnvironment->getKey(),
|
|
]))
|
|
->waitForText('Review pack export available')
|
|
->assertSee('Download export')
|
|
->assertSee('Customer-safe output')
|
|
->assertSee('Review pack contents / coverage')
|
|
->assertScript('document.querySelector("[data-step-label=\"Review pack\"]")?.dataset.stepState === "Available"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Customer-safe output\"]")?.dataset.stepState === "Ready"', true)
|
|
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Available"', true)
|
|
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"evidence-review-pack-status-badge\"]")).every((badge) => !badge.innerText.includes("...") && getComputedStyle(badge).overflow !== "hidden" && getComputedStyle(badge).textOverflow !== "ellipsis")', true)
|
|
->assertDontSee('Auditor-ready')
|
|
->assertDontSee('environment is healthy')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('05-review-pack-available'));
|
|
spec337CopyBrowserScreenshot('05-review-pack-available');
|
|
|
|
$page->screenshot(true, spec337BrowserScreenshotName('06-customer-safe-output-state'));
|
|
spec337CopyBrowserScreenshot('06-customer-safe-output-state');
|
|
|
|
$page = visit(route('admin.evidence.overview', [
|
|
'environment_id' => (int) $exportUnavailableEnvironment->getKey(),
|
|
]))
|
|
->waitForText('Export unavailable')
|
|
->assertSee('Open review pack')
|
|
->assertScript('document.querySelector("[data-step-label=\"Export / delivery\"]")?.dataset.stepState === "Required"', true)
|
|
->assertDontSee('Download export')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec337BrowserScreenshotName('07-export-unavailable'));
|
|
spec337CopyBrowserScreenshot('07-export-unavailable');
|
|
|
|
$page->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true);
|
|
$page->screenshot(true, spec337BrowserScreenshotName('08-diagnostics-collapsed'));
|
|
spec337CopyBrowserScreenshot('08-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, spec337BrowserScreenshotName('09-dark-mode'));
|
|
spec337CopyBrowserScreenshot('09-dark-mode');
|
|
});
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
function spec337BrowserCreateEvidenceSnapshot(ManagedEnvironment $environment, array $attributes = []): EvidenceSnapshot
|
|
{
|
|
$snapshot = EvidenceSnapshot::query()->create(array_replace([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'status' => EvidenceSnapshotStatus::Active->value,
|
|
'fingerprint' => sha1('spec337-browser-snapshot-'.$environment->getKey().'-'.microtime(true)),
|
|
'completeness_state' => EvidenceCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'dimension_count' => 5,
|
|
'missing_dimensions' => 0,
|
|
'stale_dimensions' => 0,
|
|
'raw_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
],
|
|
'generated_at' => now(),
|
|
'expires_at' => now()->addDays(30),
|
|
], $attributes));
|
|
|
|
$snapshot->items()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'dimension_key' => 'permission_posture',
|
|
'state' => (string) ($snapshot->completeness_state ?: EvidenceCompletenessState::Complete->value),
|
|
'required' => true,
|
|
'source_kind' => 'stored_report',
|
|
'source_record_type' => StoredReport::class,
|
|
'source_record_id' => null,
|
|
'source_fingerprint' => null,
|
|
'measured_at' => now(),
|
|
'freshness_at' => now(),
|
|
'summary_payload' => [
|
|
'label' => 'Permission posture',
|
|
'state' => 'complete',
|
|
],
|
|
'sort_order' => 1,
|
|
]);
|
|
|
|
return $snapshot->refresh();
|
|
}
|
|
|
|
function spec337BrowserCreateStoredReport(ManagedEnvironment $environment): StoredReport
|
|
{
|
|
return StoredReport::factory()->permissionPosture([
|
|
'raw_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
])->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'fingerprint' => sha1('spec337-browser-report-'.$environment->getKey().'-'.microtime(true)),
|
|
]);
|
|
}
|
|
|
|
function spec337BrowserCreatePublishedReviewWithReadyPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
): ReviewPack {
|
|
$review = EnvironmentReview::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
'fingerprint' => sha1('spec337-browser-review-'.$environment->getKey().'-'.microtime(true)),
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'completeness_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'summary' => [
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'ready',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'decision_data_state' => 'complete',
|
|
],
|
|
],
|
|
],
|
|
'generated_at' => now()->subMinutes(5),
|
|
'published_at' => now()->subMinutes(3),
|
|
]);
|
|
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'outcome' => OperationRunOutcome::Succeeded->value,
|
|
'started_at' => now()->subMinutes(4),
|
|
'completed_at' => now()->subMinutes(2),
|
|
'initiator_name' => $user->name,
|
|
]);
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'status' => ReviewPackStatus::Ready->value,
|
|
'summary' => [
|
|
'finding_count' => 3,
|
|
'report_count' => 2,
|
|
'operation_count' => 1,
|
|
'section_count' => 4,
|
|
'evidence_resolution' => [
|
|
'required_dimensions' => [
|
|
'findings_summary',
|
|
'permission_posture',
|
|
'entra_admin_roles',
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return $pack->refresh();
|
|
}
|