Files
app/frontend/tests/sweep.setup.ts
T
stroblmeandClaude Opus 5.5 0ac78764c8 make test-frontend runs against a stack of its own
The suite creates and deletes flows, dashboards and users, and ran against
whatever stack was up, which is the dev instance's data. make test-stack starts
the compose project fluksio-test (compose.test.yml over the local stack's
files) with its own containers, volumes and image tags, served by the local
Traefik on app.test.localhost and api.test.localhost, and leaves it running.
The hostname is fixed, so it no longer comes off the running dev stack, and a
LAN stack makes no difference. The suite's default origin is the test stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
2026-10-15 16:15:32 +02:00

62 lines
2.5 KiB
TypeScript

import { expect, test as setup } from "@playwright/test"
import { apiUrl, firstSuperuser, firstSuperuserPassword } from "./config.ts"
/**
* Remove what an aborted run left behind.
*
* Every spec deletes its own flows and dashboards in `afterAll`, which never
* fires when a run is interrupted, and nothing else looks for them — so they
* sit in the store until somebody removes them by hand. Sweeping before a run
* rather than after one is what makes this work at all: it happens even when
* the run that made the mess died.
*
* `make test-frontend` runs against a stack of its own, but the suite can be
* pointed at any (PLAYWRIGHT_BASE_URL), and that may be somebody's own instance
* with real flows in it, so the predicate has to be incapable of matching one. A name qualifies only if it has the shape every fixture has —
* `test_<spec>_<base36 Date.now()>`, plus an optional `_<suffix>` for a second
* artefact of the same spec — and only if the timestamp it carries is an hour
* old, which nothing the run now starting is about to create can be.
*/
const FIXTURE = /^test_[a-z]+_([0-9a-z]{8})(?:_[a-z]+)?$/
const MIN_AGE_MS = 60 * 60 * 1000
function isStaleFixture(name: string): boolean {
const match = FIXTURE.exec(name)
return (
match !== null && Number.parseInt(match[1], 36) < Date.now() - MIN_AGE_MS
)
}
setup("sweep stale fixtures", async ({ request }) => {
const login = await request.post(`${apiUrl}/api/v1/login/access-token`, {
form: { username: firstSuperuser, password: firstSuperuserPassword },
})
expect(login.ok(), `sweep could not log in: ${login.status()}`).toBeTruthy()
const { access_token } = await login.json()
const headers = { Authorization: `Bearer ${access_token}` }
for (const kind of ["dashboards", "flows"]) {
const listed = await request.get(`${apiUrl}/api/v1/${kind}/`, { headers })
expect(
listed.ok(),
`sweep could not list ${kind}: ${listed.status()}`,
).toBeTruthy()
const { data } = await listed.json()
for (const { name } of data) {
if (!isStaleFixture(name)) continue
const gone = await request.delete(`${apiUrl}/api/v1/${kind}/${name}`, {
headers,
})
// Best effort. One that will not go — a flow with a run still on it,
// say — is left for the next sweep rather than failing a suite it has
// nothing to do with.
console.log(
gone.ok()
? `swept ${kind}/${name}`
: `left ${kind}/${name} (${gone.status()})`,
)
}
}
})