server/boat-lifecycle.test.ts
Rama main · réplica de lectura del repositorio original.
import { createHash } from "node:crypto";
import { createServer, type Server } from "node:http";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
describe("cloud computer lifecycle", () => {
let api: Server;
let sleepBoat: typeof import("./boat.ts").sleepBoat;
let execOnBoat: typeof import("./boat.ts").execOnBoat;
const requests: Array<{ method: string; path: string; command?: string }> = [];
const botId = "browser-session-test";
beforeAll(async () => {
const hash = createHash("sha256").update(botId).digest("hex").slice(0, 6);
const prefix = botId.slice(0, 8).toLowerCase().replace(/[^a-z0-9]/g, "");
const machineName = `ogb-${prefix}-${hash}`;
api = createServer((req, res) => {
const url = new URL(req.url ?? "/", "http://boat.test");
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const parsed = body ? JSON.parse(body) : {};
requests.push({ method: req.method ?? "GET", path: url.pathname, command: parsed.command });
res.writeHead(200, { "content-type": "application/json" });
if (url.pathname === "/api/v1/sandboxes") {
res.end(JSON.stringify({ ok: true, type: "sandbox.list", sandboxes: [{ id: "bx_23456789", name: machineName, state: "ready" }] }));
} else if (url.pathname === "/api/v1/sandboxes/bx_23456789" && req.method === "GET") {
res.end(JSON.stringify({ ok: true, sandbox: { id: "bx_23456789", name: machineName, state: "ready" } }));
} else if (url.pathname.endsWith("/commands")) {
res.end(JSON.stringify({ exitCode: 0, stdout: "", stderr: "" }));
} else {
res.end(JSON.stringify({ ok: true }));
}
});
});
await new Promise<void>((resolve) => api.listen(0, "127.0.0.1", resolve));
const port = (api.address() as any).port;
vi.stubEnv("OMB_BOAT_API", `http://127.0.0.1:${port}/api/v1`);
vi.resetModules();
({ execOnBoat, sleepBoat } = await import("./boat.ts"));
});
afterAll(async () => {
vi.unstubAllEnvs();
await new Promise<void>((resolve) => api.close(() => resolve()));
});
it("asks Chrome to exit before archiving the computer", async () => {
await sleepBoat({ box: { token: "box_test" } } as any, botId);
const commandIndex = requests.findIndex((request) => request.path.endsWith("/commands"));
const stopIndex = requests.findIndex((request) => request.path.endsWith("/stop"));
expect(commandIndex).toBeGreaterThan(-1);
expect(stopIndex).toBeGreaterThan(commandIndex);
expect(requests[commandIndex]?.command).toContain("kill -TERM");
expect(requests[commandIndex]?.command).toContain("pgrep -o -x");
});
it("runs the owner console in the same clean environment as the bot tool", async () => {
requests.length = 0;
await execOnBoat({ box: { token: "box_test" } } as any, botId, `printf '%s' "$BOX_TOKEN"`);
const command = requests.find((request) => request.path.endsWith("/commands"))?.command ?? "";
expect(command).toContain('exec env -i HOME="$HOME"');
expect(command).toContain("/bin/bash -c");
});
it("rejects an oversized owner-console command before contacting the provider", async () => {
requests.length = 0;
await expect(
execOnBoat({ box: { token: "box_test" } } as any, botId, "x".repeat(4001)),
).rejects.toThrow("maximum 4000 characters");
expect(requests).toHaveLength(0);
});
});