// scenarios/repo-images.mjs — repo→custom worker image registry (REST CRUD). // Spawn itself is NOT exercised here: it needs Docker, which the harness must // not require (see spawn-validation.mjs). import { rest } from "../lib.mjs"; export async function run(ctx) { const { r, base, token } = ctx; const unauth = await rest(base, null, "/api/repos"); r.check( "GET /api/repos without token → 401", unauth.status === 401, `got ${unauth.status}`, ); const empty = await rest(base, token, "/api/repos"); r.check( "GET /api/repos on fresh daemon → 200 []", empty.status === 200 && Array.isArray(empty.json) && empty.json.length === 0, `got ${empty.status} ${empty.text}`, ); const put = await rest(base, token, "/api/repos/lvmh/e2e/image", { method: "PUT", body: { image: "lvmh-worker-lvmh--e2e-ab12cd" }, }); r.check( "PUT valid repo/image → 200 {ok:true}", put.status === 200 && put.json?.ok === true, `got ${put.status} ${put.text}`, ); const putTagged = await rest(base, token, "/api/repos/lvmh/e2e2/image", { method: "PUT", body: { image: "lvmh-worker-lvmh--e2e2-ab12cd:1.0" }, }); r.check( "PUT tagged image → 200", putTagged.status === 200, `got ${putTagged.status}`, ); const list = await rest(base, token, "/api/repos"); const registrations = Array.isArray(list.json) ? list.json.filter((x) => x.repo === "lvmh/e2e" || x.repo === "lvmh/e2e2") : []; r.check( "GET /api/repos lists both registrations", list.status === 200 && registrations.length === 2 && registrations[0].repo === "lvmh/e2e" && registrations[0].image === "lvmh-worker-lvmh--e2e-ab12cd" && registrations[1].repo === "lvmh/e2e2" && registrations[1].image === "lvmh-worker-lvmh--e2e2-ab12cd:1.0", `got ${list.status} ${list.text}`, ); for (const image of ["evil-image", "", "lvmh-worker-", "LVMH-worker-x"]) { const bad = await rest(base, token, "/api/repos/lvmh/e2e/image", { method: "PUT", body: { image }, }); r.check( `PUT invalid image ${JSON.stringify(image)} → 400`, bad.status === 400 && typeof bad.json?.error === "string", `got ${bad.status} ${bad.text}`, ); } const badRepo = await rest(base, token, "/api/repos/noslash/image", { method: "PUT", body: { image: "lvmh-worker-x" }, }); r.check( "PUT invalid repo → 400", badRepo.status === 400, `got ${badRepo.status}`, ); const malformed = await fetch(`${base}/api/repos/lvmh/e2e/image`, { method: "PUT", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, body: "{not json", }); r.check( "PUT malformed JSON → 400", malformed.status === 400, `got ${malformed.status}`, ); await malformed.text(); const del = await rest(base, token, "/api/repos/lvmh/e2e/image", { method: "DELETE", }); r.check( "DELETE image → 200 {ok:true}", del.status === 200 && del.json?.ok === true, `got ${del.status}`, ); const delAgain = await rest(base, token, "/api/repos/lvmh/e2e/image", { method: "DELETE", }); r.check( "DELETE absent image → 200 (idempotent)", delAgain.status === 200, `got ${delAgain.status}`, ); const after = await rest(base, token, "/api/repos"); const remaining = Array.isArray(after.json) ? after.json.filter((x) => x.repo === "lvmh/e2e") : null; r.check( "GET after delete no longer lists lvmh/e2e", after.status === 200 && remaining?.length === 0, `got ${after.status} ${after.text}`, ); }