test(e2e): orphan sweep at globalSetup

Wipes e2e-* apps and e2e* admin users before the suite starts so a
prior crashed run doesn't accumulate state across runs (45 rows
observed on 2026-05-28). Per-row try/catch keeps it best-effort; a
sweep failure never blocks the suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-28 19:37:57 +02:00
parent 70b66451d6
commit f694a6d504

View File

@@ -18,6 +18,7 @@ export default async function globalSetup(): Promise<void> {
await assertBackendUp();
await fs.mkdir(AUTH_DIR, { recursive: true });
const token = await loginAsAdmin();
await sweepOrphans(token);
await persistAdminStorageState(token);
}
@@ -71,6 +72,57 @@ async function loginAsAdmin(): Promise<string> {
}
}
// Clean up apps + admin users left over from a previous crashed run.
// The convention is that every e2e-created resource has a slug
// starting with `e2e-` (apps) or a username starting with `e2e`
// (admins) — see fixtures/ids.ts. Best-effort: a sweep failure must
// not stop the suite from running.
async function sweepOrphans(token: string): Promise<void> {
const ctx = await request.newContext({
baseURL: API_BASE,
extraHTTPHeaders: { authorization: `Bearer ${token}` }
});
try {
try {
const res = await ctx.get('/api/v1/admin/apps');
if (res.ok()) {
const apps = (await res.json()) as Array<{ slug: string }>;
for (const app of apps) {
if (!app.slug.startsWith('e2e-')) continue;
try {
await ctx.delete(
`/api/v1/admin/apps/${encodeURIComponent(app.slug)}?force=true`
);
} catch {
// Individual delete failure is non-fatal — the per-test
// cleanup will catch it on the next run.
}
}
}
} catch {
// Listing failed; nothing to do but proceed.
}
try {
const res = await ctx.get('/api/v1/admin/admins');
if (res.ok()) {
const admins = (await res.json()) as Array<{ id: string; username: string }>;
for (const a of admins) {
if (!/^e2e/.test(a.username)) continue;
try {
await ctx.delete(`/api/v1/admin/admins/${a.id}`);
} catch {
// Same per-row tolerance as above.
}
}
}
} catch {
// Listing failed; same as above.
}
} finally {
await ctx.dispose();
}
}
// The dashboard reads its session from localStorage under the key
// `picloud.admin.token` (see src/lib/auth.ts). We can't write to
// localStorage without a browser context, so launch a throwaway one,