test(e2e): isolate runs and identities without a new backend endpoint (WP-74)
The three specs shared one mutable backend and said so in their own
comments ("Restart the backend between CI runs"). WP-70 recorded the fix as
a dev-only seed endpoint; it isn't needed. The DB path already routes
through IConfiguration, so playwright.config's webServer hands the backend a
throwaway SQLite file per invocation — the same trick TestWebApplicationFactory
already uses, with zero backend change. And StubIdentityProvider already
honoured X-Subject; the only gap was that nothing sent it. That matters
because the backend has no IsDevelopment() gate anywhere, so a seed endpoint
would have had to invent the codebase's first environment gate.
subjectInterceptor mirrors the existing roleInterceptor and is wired into the
same isDevMode()-only list. Interceptors alone were not enough: the raw XHR
upload and the hand-written letter-preview fetch bypass Angular's chain (as
CLAUDE.md documents), so both now stamp X-Subject explicitly — without that,
every uploaded document still landed under DemoOwner.
reuseExistingServer stays on: flipping it would break local runs for anyone
already serving the docker stack. Each run gets a unique DB filename and
global-setup sweeps only prior runs' leftovers — deleting a fixed path
mid-run risks SQLite silently recreating an empty, unmigrated file under
fullyParallel.
Verified: e2e passes twice back-to-back with no backend restart, and
X-Subject was observed on a real request, not merely wired.
brief-v2.spec.ts keeps the shared identity for now — see the KNOWN GAP note;
a backend staleness bug makes /brief/preview return a sent letter with the
draft watermark for any non-DemoOwner BSN. actors.ts reserves the actor for
whoever fixes it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
/**
|
||||
* WP-74: `playwright.config.ts` gives the backend a UNIQUE throwaway SQLite file
|
||||
* per `npm run e2e` invocation (`<tmpdir>/big-register-e2e-<pid>-<timestamp>.db`),
|
||||
* so Playwright has no webServer teardown hook to delete it once a run ends — this
|
||||
* sweeps them up instead, at the START of the NEXT run.
|
||||
*
|
||||
* Safe regardless of run ordering: `globalSetup` always executes AFTER `webServer`
|
||||
* has already started (Playwright's task order, not something a config can flip),
|
||||
* so THIS run's own file (`process.env['E2E_DB_PATH']`, set by the config module —
|
||||
* same node process, so the assignment is visible here) is always excluded. Every
|
||||
* OTHER matching file belongs to an invocation whose `dotnet run` process has
|
||||
* already exited, so deleting it can't race a live connection.
|
||||
*/
|
||||
export default function globalSetup(): void {
|
||||
const mine = process.env['E2E_DB_PATH'];
|
||||
const dir = os.tmpdir();
|
||||
let entries: string[];
|
||||
try {
|
||||
entries = fs.readdirSync(dir);
|
||||
} catch {
|
||||
return; // best-effort cleanup — a missing/unreadable temp dir isn't this run's problem
|
||||
}
|
||||
|
||||
for (const name of entries) {
|
||||
if (!name.startsWith('big-register-e2e-')) continue;
|
||||
const base = name.replace(/(-shm|-wal)$/, '');
|
||||
if (mine && base === path.basename(mine)) continue; // never this run's own file
|
||||
try {
|
||||
fs.unlinkSync(path.join(dir, name));
|
||||
} catch {
|
||||
// best-effort — a file another leftover process still has open, or already
|
||||
// gone, is not worth failing this run's e2e suite over.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user