mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
test: expose OpenShell cleanup HTTP contract mismatch
This commit is contained in:
1 parent
b79c76f0b6
commit
da1fdac7f0
1 file changed
+64
-2
@@ -1,6 +1,7 @@
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { createServer, type Server } from "node:net";
|
||||
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
||||
import { createServer as createHttpServer } from "node:http";
|
||||
import { lstatSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { OpenShellPodmanObserver } from "../src/adapters/openshell-podman-observer.js";
|
||||
@@ -38,3 +39,64 @@ it.skipIf(!supported)("does not treat an engine error or missing original invent
|
||||
await expect(f.observer.observe(f.binding, selected)).rejects.toMatchObject({ code: "environment_cleanup_unconfirmed" });
|
||||
await expect(f.observer.observe(f.binding)).rejects.toMatchObject({ code: "environment_cleanup_unconfirmed" });
|
||||
});
|
||||
|
||||
async function httpFixture(failure?: "404" | "malformed") {
|
||||
const root = realpathSync(mkdtempSync(join(tmpdir(), "os-")));
|
||||
closers.push(() => rmSync(root, { recursive: true, force: true }));
|
||||
const socket = join(root, "s"), id = "compute-one", workloadId = "a".repeat(64), supervisorId = "b".repeat(64);
|
||||
const ownedVolumes = [`openshell-sandbox-${id}-workspace`, `openshell-control-${id}`, `openshell-channel-${id}`];
|
||||
const unrelated = { Id: "c".repeat(64), Names: ["unrelated"], Labels: {} };
|
||||
let containers = [unrelated,
|
||||
{ Id: workloadId, Names: ["workload"], Labels: { "openshell.managed": "true", "openshell.ai/sandbox-id": id, "openshell.ai/isolation-role": "sandbox", "openshell.ai/sandbox-workspace": "workspace" } },
|
||||
{ Id: supervisorId, Names: [`openshell-supervisor-${id}`], Labels: {} }];
|
||||
let volumes = [...ownedVolumes, "unrelated-volume"], volumeMissingRoute = false;
|
||||
const requests: Array<{ method: string | undefined; path: string | undefined }> = [];
|
||||
const server = createHttpServer((req, res) => {
|
||||
requests.push({ method: req.method, path: req.url });
|
||||
if (failure === "404" || req.url?.startsWith("/libpod/") || volumeMissingRoute && req.url === "/volumes") {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not Found\n"); return;
|
||||
}
|
||||
if (failure === "malformed") { res.writeHead(200); res.end("not JSON\n"); return; }
|
||||
let body: unknown;
|
||||
if (req.url === "/containers/json?all=true") body = containers;
|
||||
else if (req.url === `/containers/${workloadId}/json` || req.url === `/containers/${supervisorId}/json`)
|
||||
body = { Id: req.url.split("/")[2], Mounts: ownedVolumes.map(Name => ({ Type: "volume", Name })) };
|
||||
else if (req.url === "/volumes") body = { Volumes: volumes.map(Name => ({ Name })), Warnings: [] };
|
||||
else { res.writeHead(404); res.end("Not Found\n"); return; }
|
||||
res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(body));
|
||||
});
|
||||
await new Promise<void>(resolve => server.listen(socket, resolve));
|
||||
closers.push(() => new Promise<void>(resolve => server.close(() => resolve())));
|
||||
const observer = new OpenShellPodmanObserver(), st = lstatSync(socket);
|
||||
// Exercise the real HTTP reader on macOS too; rootless socket selection has its Linux cells above.
|
||||
const selection = vi.spyOn(observer as unknown as { socket(path: string): unknown }, "socket")
|
||||
.mockImplementation(path => ({ socket: path, socketDevice: st.dev, socketInode: st.ino, uid: st.uid, host: "engine.test" }));
|
||||
closers.push(() => selection.mockRestore());
|
||||
return {
|
||||
observer, socket, requests, binding: { resource: { id }, placement: { workspace: "workspace" } } as EnvironmentBinding,
|
||||
retireContainers: () => { containers = [unrelated]; volumes = [ownedVolumes[0]!, "unrelated-volume"]; },
|
||||
retireVolumes: () => { volumes = ["unrelated-volume"]; },
|
||||
loseVolumeRoute: () => { volumeMissingRoute = true; },
|
||||
};
|
||||
}
|
||||
|
||||
it("uses the engine's HTTP routes and positive inventories to verify exact dependent cleanup", async () => {
|
||||
const f = await httpFixture(), selected = await f.observer.capture(f.binding, f.socket);
|
||||
expect(selected).toMatchObject({ resourceId: "compute-one", workloadId: "a".repeat(64), supervisorId: "b".repeat(64) });
|
||||
expect(await f.observer.observe(f.binding, selected)).toEqual({ workload: true, supervisor: true, volumes: true });
|
||||
f.retireContainers();
|
||||
expect(await f.observer.observe(f.binding, selected)).toEqual({ workload: false, supervisor: false, volumes: true });
|
||||
f.retireVolumes();
|
||||
expect(await f.observer.observe(f.binding, selected)).toEqual({ workload: false, supervisor: false, volumes: false });
|
||||
f.loseVolumeRoute();
|
||||
await expect(f.observer.observe(f.binding, selected)).rejects.toMatchObject({ code: "environment_cleanup_unconfirmed" });
|
||||
expect(f.requests.every(req => req.method === "GET")).toBe(true);
|
||||
expect(f.requests.some(req => req.path?.startsWith("/libpod/"))).toBe(false);
|
||||
});
|
||||
|
||||
it.each(["404", "malformed"] as const)("keeps an HTTP %s inventory response unconfirmed", async failure => {
|
||||
const f = await httpFixture(failure);
|
||||
await expect(f.observer.capture(f.binding, f.socket)).rejects.toMatchObject({ code: "environment_cleanup_unconfirmed" });
|
||||
expect(f.requests).toHaveLength(1);
|
||||
expect(f.requests[0]?.method).toBe("GET");
|
||||
});
|
||||
Reference in new issue
Block a user