TenantAtlas/apps/platform/tests/Browser/Spec328OperationsHubProductizationSmokeTest.php
ahmido 6ac0913ff8 feat: implement operations UI operator actions regression gate (#436)
Implemented operations UI operator actions regression gate.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #436
2026-06-08 01:21:14 +00:00

362 lines
15 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\User;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\Workspaces\WorkspaceContext;
pest()->browser()->timeout(60_000);
it('Spec328 smokes non-empty operations hub decision workbench entry', function (): void {
[$user, $environmentA, $environmentB] = spec328OperationsHubFixture();
spec328AuthenticateOperationsHubBrowser($this, $user, $environmentA);
$page = visit(OperationRunLinks::index(workspace: $environmentA->workspace))
->resize(1440, 1100)
->waitForText('Operations Hub')
->assertDontSee(__('localization.shell.no_environment_selected'))
->assertDontSee('Environment filter:')
->assertSee('Execution follow-up')
->assertSee('Which operation needs attention now?')
->assertSee('Decision workbench')
->assertSee('Needs attention')
->assertSee('Active operations')
->assertSee('Failed or blocked')
->assertSee('Completed recently')
->assertDontSee('Total Operations')
->assertDontSee('Avg Duration')
->assertDontSee('sparkline')
->assertDontSee('trend')
->assertSee('Inventory sync')
->assertSee('Outcome')
->assertSee('Blocked')
->assertSee('Reason')
->assertSee('Impact')
->assertSee($environmentA->name)
->assertSee('Proof')
->assertSee('Operation detail available')
->assertSee('Primary next action')
->assertSee('Next action')
->assertSee('Recent runs')
->assertSee('Policy sync')
->assertSee('View affected families')
->assertDontSee('tenant filter')
->assertDontSee('current tenant')
->assertDontSee('entitled tenant')
->assertDontSee('all tenants')
->assertDontSee('raw payload should stay hidden')
->assertDontSee('stack trace should stay hidden')
->assertDontSee('provider secret should stay hidden')
->assertDontSee('debug metadata should stay hidden')
->assertDontSee('internal exception should stay hidden')
->assertScript('document.querySelector("[data-testid=\"operations-hub-diagnostics\"]")?.open === false', true)
->assertScript('(() => {
const labels = Array.from(document.querySelectorAll("td.fi-ta-cell-next-action p"))
.filter((element) => element.textContent?.includes("View affected families"));
if (labels.length === 0) {
return false;
}
return labels.every((element) => {
const styles = getComputedStyle(element);
return element.scrollWidth <= element.clientWidth + 1
&& element.scrollHeight <= element.clientHeight + 1
&& styles.textOverflow !== "ellipsis"
&& styles.webkitLineClamp !== "1";
});
})()', true)
->assertScript('(() => {
const summaryCards = document.querySelector("[data-testid=\"operations-hub-summary-cards\"]");
const nativeStats = summaryCards?.querySelector(".fi-wi-stats-overview");
const needsAttention = document.querySelector("[data-testid=\"operations-workbench-stat-needs-attention\"]");
const activeOperations = document.querySelector("[data-testid=\"operations-workbench-stat-active-operations\"]");
const failedOrBlocked = document.querySelector("[data-testid=\"operations-workbench-stat-failed-or-blocked\"]");
const completedRecently = document.querySelector("[data-testid=\"operations-workbench-stat-completed-recently\"]");
if (! summaryCards || ! nativeStats || ! needsAttention || ! activeOperations || ! failedOrBlocked || ! completedRecently) {
return false;
}
if (
needsAttention.dataset.statColor !== "warning"
|| needsAttention.dataset.statValue !== "5"
|| activeOperations.dataset.statColor !== "info"
|| activeOperations.dataset.statValue !== "0"
|| failedOrBlocked.dataset.statColor !== "danger"
|| failedOrBlocked.dataset.statValue !== "5"
|| completedRecently.dataset.statColor !== "success"
|| completedRecently.dataset.statValue !== "0"
) {
return false;
}
if (summaryCards.textContent.includes("Total Operations") || summaryCards.textContent.includes("Avg Duration")) {
return false;
}
if (
summaryCards.querySelector("canvas")
|| summaryCards.querySelector("[data-card-style]")
|| summaryCards.querySelector("[data-accent-placement]")
|| summaryCards.outerHTML.includes("wire:poll")
) {
return false;
}
const decisionGrid = document.querySelector("[data-testid=\"operations-hub-decision-workbench\"]");
const workbench = document.querySelector("[data-testid=\"operations-hub-priority-card\"]");
const detail = document.querySelector("[data-testid=\"operations-hub-operation-proof-panel\"]");
if (! decisionGrid || ! workbench || ! detail) {
return false;
}
const children = Array.from(decisionGrid.children);
const summaryBox = summaryCards.getBoundingClientRect();
const decisionBox = decisionGrid.getBoundingClientRect();
const workbenchBox = workbench.getBoundingClientRect();
const detailBox = detail.getBoundingClientRect();
return window.innerWidth >= 1024
&& summaryBox.bottom <= decisionBox.top
&& decisionGrid.classList.contains("lg:grid-cols-[minmax(0,1fr)_22rem]")
&& detail.tagName === "ASIDE"
&& children.indexOf(workbench) !== -1
&& children.indexOf(detail) > children.indexOf(workbench)
&& detailBox.left > workbenchBox.right
&& Math.abs(detailBox.top - workbenchBox.top) <= 8;
})()', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page
->click('[data-testid="operations-hub-diagnostics"] summary')
->assertScript('document.querySelector("[data-testid=\"operations-hub-diagnostics\"]")?.open === true', true)
->assertSee('Raw context, provider payloads, stack traces, debug metadata, and support diagnostics stay on authorized operation detail surfaces.')
->click('[data-testid="operations-hub-diagnostics"] summary')
->assertScript('document.querySelector("[data-testid=\"operations-hub-diagnostics\"]")?.open === false', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs();
$page->script('window.scrollTo(0, 0);');
$page
->screenshot(false, spec328OperationsHubScreenshot('operations-hub-premium-summary-cards'))
->screenshot(true, spec328OperationsHubScreenshot('operations-hub--clean'));
spec328CopyBrowserScreenshot('operations-hub--clean');
spec328CopyBrowserScreenshot('operations-hub--clean', 'operations-hub-decision-workbench.png');
spec328CopyBrowserScreenshot('operations-hub-premium-summary-cards');
});
it('Spec328 smokes filtered operations hub clear and reload behavior', function (): void {
[$user, $environmentA, $environmentB] = spec328OperationsHubFixture();
$cleanPath = json_encode((string) parse_url(OperationRunLinks::index(workspace: $environmentA->workspace), PHP_URL_PATH), JSON_THROW_ON_ERROR);
spec328AuthenticateOperationsHubBrowser($this, $user, $environmentA);
$page = visit(OperationRunLinks::index($environmentA))
->waitForText('Environment filter:')
->assertSee('Environment filter: '.$environmentA->name)
->assertSee('Which operation needs attention now?')
->assertSee($environmentA->name)
->assertDontSee('Policy sync')
->assertScript('document.querySelector("[data-testid=\"operations-hub-diagnostics\"]")?.open === false', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec328OperationsHubScreenshot('operations-hub--filtered'));
spec328CopyBrowserScreenshot('operations-hub--filtered');
spec328ClearEnvironmentFilter($page)
->waitForText('Policy sync')
->assertDontSee('Environment filter:')
->assertSee('Policy sync')
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec328OperationsHubScreenshot('operations-hub--after-clear'));
spec328CopyBrowserScreenshot('operations-hub--after-clear');
$page->script('window.location.reload();');
$page
->waitForText('Policy sync')
->assertDontSee('Environment filter:')
->assertSee('Policy sync')
->assertScript("window.location.pathname === {$cleanPath}", true)
->assertScript('! window.location.search.includes("environment_id=")', true)
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec328OperationsHubScreenshot('operations-hub--after-reload'));
spec328CopyBrowserScreenshot('operations-hub--after-reload');
});
it('Spec328 smokes no-attention operations hub state', function (): void {
$environment = ManagedEnvironment::factory()->active()->create([
'name' => 'Spec328 Browser Empty Environment',
'external_id' => 'spec328-browser-empty-environment',
]);
[$user, $environment] = createUserWithTenant(
tenant: $environment,
role: 'owner',
workspaceRole: 'owner',
);
OperationRun::factory()->forTenant($environment)->create([
'type' => 'policy.sync',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'completed_at' => now()->subMinutes(5),
]);
spec328AuthenticateOperationsHubBrowser($this, $user, $environment);
visit(OperationRunLinks::index(workspace: $environment->workspace))
->waitForText('Operations Hub')
->assertSee('No operations need follow-up')
->assertSee('No failed, blocked, partial, or stale OperationRuns are visible in this scope.')
->assertSee('Operation detail available')
->assertDontSee('environment is healthy')
->assertDontSee('governance health is complete')
->assertNoJavaScriptErrors()
->assertNoConsoleLogs()
->screenshot(true, spec328OperationsHubScreenshot('operations-hub--empty'));
spec328CopyBrowserScreenshot('operations-hub--empty');
});
/**
* @return array{0: User, 1: ManagedEnvironment, 2: ManagedEnvironment}
*/
function spec328OperationsHubFixture(): array
{
$environmentA = ManagedEnvironment::factory()->active()->create([
'name' => 'Spec328 Browser Environment A',
'external_id' => 'spec328-browser-environment-a',
]);
[$user, $environmentA] = createUserWithTenant(
tenant: $environmentA,
role: 'owner',
workspaceRole: 'owner',
);
$environmentB = ManagedEnvironment::factory()->active()->create([
'workspace_id' => (int) $environmentA->workspace_id,
'name' => 'Spec328 Browser Environment B',
'external_id' => 'spec328-browser-environment-b',
]);
createUserWithTenant(
tenant: $environmentB,
user: $user,
role: 'owner',
workspaceRole: 'owner',
);
OperationRun::factory()->forTenant($environmentA)->create([
'type' => 'inventory_sync',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Blocked->value,
'context' => [
'reason_code' => 'write_gate_blocked',
'raw_payload' => 'raw payload should stay hidden',
'stack_trace' => 'stack trace should stay hidden',
'provider_secret' => 'provider secret should stay hidden',
'debug_metadata' => 'debug metadata should stay hidden',
'internal_exception' => 'internal exception should stay hidden',
],
'completed_at' => null,
]);
foreach (range(1, 4) as $index) {
OperationRun::factory()->forTenant($environmentA)->create([
'type' => 'backup.schedule.execute',
'status' => OperationRunStatus::Completed->value,
'outcome' => $index % 2 === 0
? OperationRunOutcome::Blocked->value
: OperationRunOutcome::Failed->value,
'completed_at' => null,
]);
}
OperationRun::factory()->forTenant($environmentB)->create([
'type' => 'policy.sync',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'completed_at' => now()->subDays(2),
]);
return [$user, $environmentA, $environmentB];
}
function spec328AuthenticateOperationsHubBrowser(
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 spec328ClearEnvironmentFilter(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 spec328OperationsHubScreenshot(string $name): string
{
return 'spec328-'.$name;
}
function spec328CopyBrowserScreenshot(string $name, ?string $targetFilename = null): void
{
$filename = spec328OperationsHubScreenshot($name).'.png';
$source = base_path('tests/Browser/Screenshots/'.$filename);
$targetDirectory = repo_path('specs/328-operations-hub-decision-first-workbench-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);
}
}