TenantAtlas/apps/platform/tests/Browser/Spec329EvidenceAuditDisclosureSmokeTest.php
Ahmed Darrazi ebb68ca9bb
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m40s
feat: productize evidence and audit log disclosure
2026-05-19 23:24:00 +02:00

486 lines
20 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AuditLog;
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\Evidence\EvidenceCompletenessState;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\ReviewPackStatus;
use App\Support\Workspaces\WorkspaceContext;
pest()->browser()->timeout(60_000);
it('Spec329 smokes Evidence Overview proof-first disclosure and filter clearing', function (): void {
$fixture = spec329BrowserDisclosureFixture();
spec329AuthenticateDisclosureBrowser($this, $fixture['user'], $fixture['environmentA']);
$cleanPath = json_encode((string) parse_url(route('admin.evidence.overview'), PHP_URL_PATH), JSON_THROW_ON_ERROR);
$page = visit(route('admin.evidence.overview'))
->resize(1440, 1100)
->waitForText('What proof is available for this scope?')
->assertSee(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->assertSee('Evidence proof workbench')
->assertSee('Primary proof path')
->assertSee('Evidence path')
->assertSee('Evidence snapshot')
->assertSee('Review pack')
->assertSee('Stored report / export')
->assertSee('Operation proof')
->assertSee('Proof incomplete')
->assertSee('A proof record exists, but no usable captured evidence is available yet.')
->assertSee('Primary evidence snapshot is empty.')
->assertSee('Supporting proof exists through the review pack, stored report, and operation record.')
->assertSee('Empty')
->assertSee('Ready')
->assertSee('Available')
->assertSourceHas('Search evidence or next step')
->assertSee('Evidence inventory')
->assertSee($fixture['environmentA']->name)
->assertSee($fixture['environmentB']->name)
->assertDontSee('The artifact row exists, but it does not contain usable captured content.')
->assertDontSee('artifact row exists')
->assertSourceMissing('Search tenant or next')
->assertDontSee('Empty...')
->assertDontSee('Re...')
->assertDontSee('raw payload should stay hidden')
->assertDontSee('provider secret should stay hidden')
->assertDontSee('stack trace should stay hidden')
->assertDontSee('debug metadata should stay hidden')
->assertDontSee('internal exception should stay hidden')
->assertDontSee('current tenant')
->assertDontSee('tenant filter')
->assertDontSee('entitled tenant')
->assertDontSee('all tenants')
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
->assertScript('(() => {
const action = document.querySelector("[data-testid=\"evidence-primary-proof-action\"]");
if (! action) {
return false;
}
const box = action.getBoundingClientRect();
return action.textContent.trim() === "Open evidence snapshot"
&& box.height > 0
&& box.height <= 44
&& action.scrollWidth <= Math.ceil(action.clientWidth) + 1
&& getComputedStyle(action).whiteSpace === "nowrap";
})()', true)
->assertScript('(() => {
const badges = Array.from(document.querySelectorAll("[data-testid=\"evidence-path-state-badge\"]"));
return badges.length >= 4
&& badges.every((badge) => {
const label = badge.textContent.trim();
const box = badge.getBoundingClientRect();
return ! ["Empty...", "Re..."].includes(label)
&& box.width > 0
&& badge.scrollWidth <= Math.ceil(badge.clientWidth) + 1;
});
})()', true)
->assertScript('(() => {
const grid = document.querySelector("[data-testid=\"evidence-disclosure-workbench\"]");
const main = document.querySelector("[data-testid=\"evidence-proof-primary\"]");
const aside = document.querySelector("[data-testid=\"evidence-proof-aside\"]");
if (! grid || ! main || ! aside) {
return false;
}
const children = Array.from(grid.children);
const mainBox = main.getBoundingClientRect();
const asideBox = aside.getBoundingClientRect();
return window.innerWidth >= 1024
&& grid.classList.contains("lg:grid-cols-[minmax(0,1fr)_22rem]")
&& aside.tagName === "ASIDE"
&& children.indexOf(main) !== -1
&& children.indexOf(aside) > children.indexOf(main)
&& asideBox.left > mainBox.right
&& Math.abs(asideBox.top - mainBox.top) <= 8;
})()', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('evidence-overview--clean'));
spec329CopyDisclosureScreenshot('evidence-overview--clean');
$page = visit(route('admin.evidence.overview', [
'environment_id' => (int) $fixture['environmentA']->getKey(),
]))
->waitForText('Environment filter:')
->assertSee('Environment filter: '.$fixture['environmentA']->name)
->assertSee('What proof is available for this scope?')
->assertSee($fixture['environmentA']->name)
->assertSee('Proof incomplete')
->assertSee('Primary evidence snapshot is empty.')
->assertDontSee('The artifact row exists, but it does not contain usable captured content.')
->assertDontSee('Empty...')
->assertDontSee('Re...')
->assertDontSee($fixture['environmentB']->name)
->assertScript('document.querySelector("[data-testid=\"evidence-disclosure-diagnostics\"]")?.open === false', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('evidence-overview--filtered'));
spec329CopyDisclosureScreenshot('evidence-overview--filtered');
spec329ClearDisclosureEnvironmentFilter($page)
->waitForText(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->waitForText($fixture['environmentB']->name)
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('evidence-overview--after-clear'));
spec329CopyDisclosureScreenshot('evidence-overview--after-clear');
$page->script('window.location.reload();');
$page
->waitForText(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->assertSee($fixture['environmentB']->name)
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('evidence-overview--after-reload'));
spec329CopyDisclosureScreenshot('evidence-overview--after-reload');
});
it('Spec329 smokes Audit Log event-proof disclosure and filter clearing', function (): void {
$fixture = spec329BrowserDisclosureFixture();
spec329AuthenticateDisclosureBrowser($this, $fixture['user'], $fixture['environmentA']);
$cleanPath = json_encode((string) parse_url(route('admin.monitoring.audit-log'), PHP_URL_PATH), JSON_THROW_ON_ERROR);
$page = visit(route('admin.monitoring.audit-log', [
'event' => (int) $fixture['auditA']->getKey(),
]))
->resize(1440, 1100)
->waitForText('Which event proves what happened?')
->assertSee(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->assertSee('Audit proof workbench')
->assertSee('Selected event proof')
->assertSee('Event proof')
->assertSee('Actor')
->assertSee('Action')
->assertSee('Target')
->assertSee('Outcome')
->assertSee('Time')
->assertSee('Related proof')
->assertSee('Operation proof')
->assertSee('Readable context')
->assertSee('Audit event history')
->assertSee('Spec329 Browser Operator A')
->assertSee('Permission posture checked')
->assertSee('Permission posture report')
->assertDontSee('raw payload should stay hidden')
->assertDontSee('provider secret should stay hidden')
->assertDontSee('stack trace should stay hidden')
->assertDontSee('debug metadata should stay hidden')
->assertDontSee('internal exception should stay hidden')
->assertDontSee('provider response should stay hidden')
->assertDontSee('current tenant')
->assertDontSee('tenant filter')
->assertDontSee('entitled tenant')
->assertDontSee('all tenants')
->assertScript('document.querySelector("[data-testid=\"audit-disclosure-diagnostics\"]")?.open === false', true)
->assertScript('document.querySelector("[data-testid=\"audit-event-diagnostics\"]")?.open === false', true)
->assertScript('(() => {
const grid = document.querySelector("[data-testid=\"audit-disclosure-workbench\"]");
const main = document.querySelector("[data-testid=\"audit-proof-primary\"]");
const aside = document.querySelector("[data-testid=\"audit-proof-aside\"]");
if (! grid || ! main || ! aside) {
return false;
}
const children = Array.from(grid.children);
const mainBox = main.getBoundingClientRect();
const asideBox = aside.getBoundingClientRect();
return window.innerWidth >= 1024
&& grid.classList.contains("lg:grid-cols-[minmax(0,1fr)_22rem]")
&& aside.tagName === "ASIDE"
&& children.indexOf(main) !== -1
&& children.indexOf(aside) > children.indexOf(main)
&& asideBox.left > mainBox.right
&& Math.abs(asideBox.top - mainBox.top) <= 8;
})()', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('audit-log--clean'));
spec329CopyDisclosureScreenshot('audit-log--clean');
$page = visit(route('admin.monitoring.audit-log', [
'environment_id' => (int) $fixture['environmentA']->getKey(),
'event' => (int) $fixture['auditA']->getKey(),
]))
->waitForText('Environment filter:')
->assertSee('Environment filter: '.$fixture['environmentA']->name)
->assertSee('Which event proves what happened?')
->assertSee('Permission posture checked')
->assertDontSee('Workspace selected by browser proof B')
->assertScript('document.querySelector("[data-testid=\"audit-disclosure-diagnostics\"]")?.open === false', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('audit-log--filtered'));
spec329CopyDisclosureScreenshot('audit-log--filtered');
spec329ClearDisclosureEnvironmentFilter($page)
->waitForText(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->waitForText('Workspace selected by browser proof B')
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('audit-log--after-clear'));
spec329CopyDisclosureScreenshot('audit-log--after-clear');
$page->script('window.location.reload();');
$page
->waitForText(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->assertSee('Workspace selected by browser proof B')
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec329DisclosureScreenshot('audit-log--after-reload'));
spec329CopyDisclosureScreenshot('audit-log--after-reload');
});
/**
* @return array{
* user: User,
* environmentA: ManagedEnvironment,
* environmentB: ManagedEnvironment,
* auditA: AuditLog,
* auditB: AuditLog
* }
*/
function spec329BrowserDisclosureFixture(): array
{
$environmentA = ManagedEnvironment::factory()->active()->create([
'name' => 'Spec329 Browser Tenant Environment A',
'external_id' => 'spec329-browser-environment-a',
]);
[$user, $environmentA] = createUserWithTenant(
tenant: $environmentA,
role: 'owner',
workspaceRole: 'owner',
);
$environmentB = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'name' => 'Spec329 Browser Environment B',
'external_id' => 'spec329-browser-environment-b',
]);
createUserWithTenant(
tenant: $environmentB,
user: $user,
role: 'owner',
workspaceRole: 'owner',
);
$runA = OperationRun::factory()->forTenant($environmentA)->create([
'type' => 'tenant.evidence.snapshot.generate',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'context' => [
'raw_payload' => 'raw payload should stay hidden',
'provider_secret' => 'provider secret should stay hidden',
'stack_trace' => 'stack trace should stay hidden',
'debug_metadata' => 'debug metadata should stay hidden',
'internal_exception' => 'internal exception should stay hidden',
],
'completed_at' => now()->subMinutes(10),
]);
$runB = OperationRun::factory()->forTenant($environmentB)->create([
'type' => 'policy.sync',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'completed_at' => now()->subMinutes(5),
]);
$snapshotA = EvidenceSnapshot::query()->create([
'managed_environment_id' => (int) $environmentA->getKey(),
'workspace_id' => (int) $environmentA->workspace_id,
'operation_run_id' => (int) $runA->getKey(),
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Complete->value,
'summary' => [
'missing_dimensions' => 0,
'stale_dimensions' => 0,
'raw_payload' => 'raw payload should stay hidden',
],
'generated_at' => now()->subMinutes(4),
'expires_at' => now()->addDays(30),
]);
EvidenceSnapshot::query()->create([
'managed_environment_id' => (int) $environmentB->getKey(),
'workspace_id' => (int) $environmentB->workspace_id,
'operation_run_id' => (int) $runB->getKey(),
'status' => EvidenceSnapshotStatus::Active->value,
'completeness_state' => EvidenceCompletenessState::Partial->value,
'summary' => [
'missing_dimensions' => 1,
'stale_dimensions' => 0,
],
'generated_at' => now()->subMinutes(9),
'expires_at' => now()->addDays(30),
]);
ReviewPack::factory()->ready()->create([
'managed_environment_id' => (int) $environmentA->getKey(),
'workspace_id' => (int) $environmentA->workspace_id,
'evidence_snapshot_id' => (int) $snapshotA->getKey(),
'operation_run_id' => (int) $runA->getKey(),
'status' => ReviewPackStatus::Ready->value,
]);
$storedReport = StoredReport::factory()->permissionPosture([
'raw_payload' => 'raw payload should stay hidden',
])->create([
'managed_environment_id' => (int) $environmentA->getKey(),
'workspace_id' => (int) $environmentA->workspace_id,
'fingerprint' => 'spec329-browser-report',
]);
$auditA = AuditLog::query()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'managed_environment_id' => (int) $environmentA->getKey(),
'operation_run_id' => (int) $runA->getKey(),
'actor_email' => 'spec329-a@example.test',
'actor_name' => 'Spec329 Browser Operator A',
'actor_type' => 'human',
'action' => 'permission_posture.checked',
'status' => 'success',
'resource_type' => 'stored_report',
'resource_id' => (string) $storedReport->getKey(),
'target_label' => 'Permission posture report',
'summary' => 'Permission posture checked',
'metadata' => [
'reason' => 'Evidence review',
'raw_payload' => 'raw payload should stay hidden',
'provider_secret' => 'provider secret should stay hidden',
'stack_trace' => 'stack trace should stay hidden',
'debug_metadata' => 'debug metadata should stay hidden',
'internal_exception' => 'internal exception should stay hidden',
'provider_response' => 'provider response should stay hidden',
],
'recorded_at' => now()->subMinutes(2),
]);
$auditB = AuditLog::query()->create([
'workspace_id' => (int) $environmentB->workspace_id,
'managed_environment_id' => (int) $environmentB->getKey(),
'actor_email' => 'spec329-b@example.test',
'actor_name' => 'Spec329 Browser Operator B',
'actor_type' => 'human',
'action' => 'workspace.selected',
'status' => 'success',
'resource_type' => 'workspace',
'resource_id' => (string) $environmentB->workspace_id,
'target_label' => 'Workspace '.$environmentB->workspace_id,
'summary' => 'Workspace selected by browser proof B',
'recorded_at' => now()->subMinute(),
]);
return [
'user' => $user,
'environmentA' => $environmentA,
'environmentB' => $environmentB,
'auditA' => $auditA,
'auditB' => $auditB,
];
}
function spec329AuthenticateDisclosureBrowser(
mixed $test,
User $user,
ManagedEnvironment $rememberedEnvironment,
): void {
$workspaceId = (int) $rememberedEnvironment->workspace_id;
$session = [
WorkspaceContext::SESSION_KEY => $workspaceId,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $workspaceId => (int) $rememberedEnvironment->getKey(),
],
];
$test->actingAs($user)->withSession($session);
foreach ($session as $key => $value) {
session()->put($key, $value);
}
setAdminPanelContext($rememberedEnvironment);
}
function spec329ClearDisclosureEnvironmentFilter(mixed $page): mixed
{
$page->assertScript('document.querySelector(\'[data-testid="workspace-hub-environment-filter-clear"]\') instanceof HTMLAnchorElement', true);
$page->script('window.location.assign(document.querySelector(\'[data-testid="workspace-hub-environment-filter-clear"]\').href);');
return $page;
}
function spec329DisclosureScreenshot(string $name): string
{
return 'spec329-'.$name;
}
function spec329CopyDisclosureScreenshot(string $name, ?string $targetFilename = null): void
{
$filename = spec329DisclosureScreenshot($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/329-evidence-audit-log-disclosure-productization/artifacts/screenshots');
$targetFilename ??= $filename;
if (! is_dir($targetDirectory)) {
@mkdir($targetDirectory, 0755, true);
}
if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
return;
}
if (! is_file($source)) {
$source = \Pest\Browser\Support\Screenshot::path($filename);
}
if (is_file($source)) {
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$targetFilename);
}
}