import type { APIRequestContext } from '@playwright/test'; import { adminApi } from './api'; // Resources to delete after a test, in LIFO order. Tests register // their creations and the registry tears everything down in // `run()` — typically called from `test.afterEach`. // // A non-2xx status (other than 404) is treated as a real failure and // logged to stderr. The previous shape silently swallowed every // error, so a backend that started returning 500 on cleanup would // have leaked orphans invisibly across runs. 404 stays tolerated — // the test may have already deleted the resource itself. interface CleanupItem { label: string; path: string; } export class CleanupRegistry { private items: CleanupItem[] = []; app(slugOrId: string): void { this.items.push({ label: `app=${slugOrId}`, path: `/api/v1/admin/apps/${encodeURIComponent(slugOrId)}?force=true` }); } adminUser(userId: string): void { this.items.push({ label: `admin=${userId}`, path: `/api/v1/admin/admins/${userId}` }); } apiKey(keyId: string): void { this.items.push({ label: `key=${keyId}`, path: `/api/v1/admin/api-keys/${keyId}` }); } async run(): Promise { if (this.items.length === 0) return; const api = await adminApi(); try { // Copy-then-reverse so a defensive double-`run()` (or a // caller that inspects the registry after a partial // teardown) doesn't see the items in a re-reversed order. for (const item of [...this.items].reverse()) { await deleteAndReport(api, item); } } finally { await api.dispose(); this.items = []; } } } async function deleteAndReport( api: APIRequestContext, item: CleanupItem ): Promise { try { const res = await api.delete(item.path); // 2xx and 404 are both "this resource is no longer here" — fine. if (!res.ok() && res.status() !== 404) { console.warn( `[cleanup] ${item.label} failed: HTTP ${res.status()} ${await res.text()}` ); } } catch (err) { // Network-level failure (request never reached the server, // timeout, etc.). Log so a leak doesn't accumulate silently. console.warn(`[cleanup] ${item.label} failed: ${(err as Error).message}`); } }