Harden policy simulation direct-route defaults

This commit is contained in:
master
2026-03-10 09:09:29 +02:00
parent db7371de03
commit eae2dfc9d4
15 changed files with 739 additions and 18 deletions

View File

@@ -0,0 +1,221 @@
#!/usr/bin/env node
import { mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright';
import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const webRoot = path.resolve(__dirname, '..');
const outputDirectory = path.join(webRoot, 'output', 'playwright');
const DEFAULT_BASE_URL = process.env.STELLAOPS_FRONTDOOR_BASE_URL?.trim() || 'https://stella-ops.local';
const authStatePath = path.join(outputDirectory, 'live-policy-simulation-direct-routes.auth.json');
const authReportPath = path.join(outputDirectory, 'live-policy-simulation-direct-routes.auth-report.json');
const summaryPath = path.join(outputDirectory, 'live-policy-simulation-direct-routes.json');
function isStaticAsset(url) {
return /\.(?:css|js|map|png|jpg|jpeg|svg|woff2?|ico)(?:$|\?)/i.test(url);
}
function shouldIgnoreFailure(errorText) {
return errorText === 'net::ERR_ABORTED';
}
function createRuntime() {
return {
consoleErrors: [],
pageErrors: [],
requestFailures: [],
responseErrors: [],
};
}
function attachRuntimeWatchers(page, runtime) {
page.on('console', (message) => {
if (message.type() === 'error') {
runtime.consoleErrors.push({
page: page.url(),
text: message.text(),
});
}
});
page.on('pageerror', (error) => {
runtime.pageErrors.push({
page: page.url(),
message: error.message,
});
});
page.on('requestfailed', (request) => {
const url = request.url();
const errorText = request.failure()?.errorText ?? 'unknown';
if (isStaticAsset(url) || shouldIgnoreFailure(errorText)) {
return;
}
runtime.requestFailures.push({
page: page.url(),
method: request.method(),
url,
error: errorText,
});
});
page.on('response', (response) => {
const url = response.url();
if (isStaticAsset(url) || response.status() < 400) {
return;
}
runtime.responseErrors.push({
page: page.url(),
method: response.request().method(),
status: response.status(),
url,
});
});
}
async function waitForAnyVisible(page, selectors, timeout = 10_000) {
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
for (const selector of selectors) {
if (await page.locator(selector).first().isVisible().catch(() => false)) {
return selector;
}
}
await page.waitForTimeout(200);
}
throw new Error(`Timed out waiting for any selector: ${selectors.join(', ')}`);
}
async function checkRoute(page, baseUrl, route) {
const startedAt = new Date().toISOString();
let ok = false;
let error = null;
try {
await page.goto(`${baseUrl}${route.path}`, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
await waitForAnyVisible(page, route.expectVisible);
if (route.action) {
await route.action(page);
}
ok = true;
} catch (cause) {
error = cause instanceof Error ? cause.message : String(cause);
}
return {
route: route.path,
ok,
startedAtUtc: startedAt,
finishedAtUtc: new Date().toISOString(),
finalUrl: page.url(),
error,
};
}
async function main() {
mkdirSync(outputDirectory, { recursive: true });
const authReport = await authenticateFrontdoor({
baseUrl: DEFAULT_BASE_URL,
statePath: authStatePath,
reportPath: authReportPath,
headless: true,
});
const browser = await chromium.launch({
headless: true,
args: ['--disable-dev-shm-usage'],
});
const context = await createAuthenticatedContext(browser, authReport, {
statePath: authStatePath,
});
const page = await context.newPage();
const runtime = createRuntime();
attachRuntimeWatchers(page, runtime);
const routes = [
{
path: '/ops/policy/simulation/coverage',
expectVisible: ['h1:has-text("Test Coverage")', '.coverage__header'],
action: async (routePage) => {
await waitForAnyVisible(routePage, ['.coverage__summary', '.coverage__rules']);
},
},
{
path: '/ops/policy/simulation/lint',
expectVisible: ['h1:has-text("Policy Lint")', '.policy-lint__header'],
action: async (routePage) => {
await routePage.getByRole('button', { name: /run lint/i }).click();
await waitForAnyVisible(routePage, ['.lint-status', '.lint-summary']);
},
},
{
path: '/ops/policy/simulation/promotion',
expectVisible: ['h1:has-text("Promotion Gate")', '.promotion-gate__header'],
action: async (routePage) => {
await routePage.getByRole('button', { name: /check requirements/i }).click();
await waitForAnyVisible(routePage, ['.promotion-gate__info', '.promotion-gate__summary']);
},
},
{
path: '/ops/policy/simulation/diff/policy-pack-001',
expectVisible: ['h1:has-text("Policy Diff")', '.diff-viewer__header'],
action: async (routePage) => {
await waitForAnyVisible(routePage, ['.diff-viewer__stats', '.diff-viewer__empty', '.diff-viewer__files']);
},
},
];
const results = [];
try {
for (const route of routes) {
results.push(await checkRoute(page, DEFAULT_BASE_URL, route));
}
} finally {
await context.close().catch(() => {});
await browser.close().catch(() => {});
}
const summary = {
capturedAtUtc: new Date().toISOString(),
baseUrl: DEFAULT_BASE_URL,
results,
runtime,
failedRouteCount: results.filter((result) => result.ok === false).length,
runtimeIssueCount:
runtime.consoleErrors.length +
runtime.pageErrors.length +
runtime.requestFailures.length +
runtime.responseErrors.length,
};
writeFileSync(summaryPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
if (summary.failedRouteCount > 0 || summary.runtimeIssueCount > 0) {
process.exitCode = 1;
}
}
main().catch((error) => {
process.stderr.write(
`[live-policy-simulation-direct-routes] ${error instanceof Error ? error.message : String(error)}\n`,
);
process.exitCode = 1;
});