daemon: agent-driven workspace images — repo→image registry (GET/PUT/DELETE /api/repos), spawn resolves custom image, self-healing lvmh-ops control container with docker.sock; 132 tests, e2e 87/87
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// 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}`,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user