Files
pulse/tests/integration/tests/runtime-defaults.ts
T
rcourtman 36b42c1591 Make e2e auth deterministic and refuse implicit dev-runtime resets
Playwright storage states persist cookies and localStorage only, while the
primary-API-token flow authenticates through sessionStorage. Storage states
captured after token auth were silently unauthenticated, so whole spec files
landed on the login screen whenever the token path happened to win. Storage
states now always come from the cookie-backed password login, cached once
per run and probed before reuse.

Explicit bearer/X-API-Token requests now go through a cookie-less request
context: the backend intentionally CSRF-rejects mutations that carry a
session cookie without a CSRF token even when the Authorization header is
valid, so token-scope coverage could never pass from a logged-in page.
Session-semantics specs use the new ensureSessionAuthenticated instead of
racing the token path.

First-run resets now refuse the implicitly resolved hot-dev runtime (the pid
fallback with no explicit base URL and no harness-written runtime state);
an ad-hoc spec run wiped the live dev backend's auth twice through that
path. Resets can also recover an instance stuck unauthenticated mid-reset by
re-reading the rotated bootstrap token through docker exec, and login()
retries through the backend's 10-per-minute login limiter instead of failing
on burst runs.
2026-07-08 11:09:35 +01:00

104 lines
3.4 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export type RuntimeState = {
baseURL?: string;
primaryAPIToken?: string;
};
const trim = (value: unknown): string => String(value ?? '').trim();
const firstNonEmpty = (...values: readonly unknown[]): string =>
values.map(trim).find((value) => value !== '') || '';
const browserHostForBind = (host: unknown): string => {
const normalized = trim(host).toLowerCase().replace(/^\[/, '').replace(/\]$/, '');
if (normalized === '' || normalized === '0.0.0.0' || normalized === '::') {
return '127.0.0.1';
}
return trim(host);
};
export const repoRootFromEnv = (env: NodeJS.ProcessEnv = process.env): string =>
trim(env.PULSE_E2E_REPO_ROOT) || path.resolve(__dirname, '..', '..', '..');
const managedHotDevPidPath = (env: NodeJS.ProcessEnv = process.env): string =>
path.join(repoRootFromEnv(env), 'tmp', 'hot-dev.bg.pid');
export const runtimeStatePath = (env: NodeJS.ProcessEnv = process.env): string => {
const configuredPath = trim(env.PULSE_E2E_RUNTIME_STATE_PATH);
if (configuredPath === '') {
return path.resolve(repoRootFromEnv(env), 'tmp', 'e2e-runtime-state.json');
}
return path.isAbsolute(configuredPath)
? configuredPath
: path.resolve(repoRootFromEnv(env), configuredPath);
};
export const readRuntimeState = (
env: NodeJS.ProcessEnv = process.env,
): RuntimeState | null => {
try {
const raw = fs.readFileSync(runtimeStatePath(env), 'utf8');
return JSON.parse(raw) as RuntimeState;
} catch {
return null;
}
};
export const loadRuntimeBaseURL = (
env: NodeJS.ProcessEnv = process.env,
): string | null => {
const parsed = readRuntimeState(env);
return typeof parsed?.baseURL === 'string' && parsed.baseURL.trim() !== ''
? parsed.baseURL.trim()
: null;
};
export const managedDevBrowserBaseURL = (
env: NodeJS.ProcessEnv = process.env,
): string | null => {
try {
const pid = Number.parseInt(fs.readFileSync(managedHotDevPidPath(env), 'utf8').trim(), 10);
if (!Number.isInteger(pid) || pid <= 0) {
return null;
}
process.kill(pid, 0);
const host = browserHostForBind(env.FRONTEND_DEV_HOST || '127.0.0.1');
const port = trim(env.FRONTEND_DEV_PORT) || '5173';
return `http://${host}:${port}`;
} catch {
return null;
}
};
export const preferredBrowserBaseURL = (
env: NodeJS.ProcessEnv = process.env,
overrides: readonly unknown[] = [],
): string =>
firstNonEmpty(
...overrides,
env.PLAYWRIGHT_BASE_URL,
env.PULSE_BASE_URL,
loadRuntimeBaseURL(env),
managedDevBrowserBaseURL(env),
'http://localhost:7655',
);
// True when the base URL would resolve through the hot-dev pid fallback with
// no explicit env override and no harness-written runtime state — i.e. the
// suite is about to target a developer's live Pulse rather than an e2e stack.
export const browserBaseURLIsImplicitDevRuntime = (
env: NodeJS.ProcessEnv = process.env,
): boolean =>
trim(env.PLAYWRIGHT_BASE_URL) === '' &&
trim(env.PULSE_BASE_URL) === '' &&
loadRuntimeBaseURL(env) === null &&
managedDevBrowserBaseURL(env) !== null;
export const preferredPlaywrightRouteBaseURL = (
env: NodeJS.ProcessEnv = process.env,
overrides: readonly unknown[] = [],
): string => preferredBrowserBaseURL(env, overrides).replace(/\/+$/, '');