diff --git a/dashboard/tests/e2e/global-setup.ts b/dashboard/tests/e2e/global-setup.ts index c77fcdc..2ebab9c 100644 --- a/dashboard/tests/e2e/global-setup.ts +++ b/dashboard/tests/e2e/global-setup.ts @@ -18,6 +18,7 @@ export default async function globalSetup(): Promise { 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 { } } +// 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 { + 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,