server/boat-trial.test.ts
Rama main · réplica de lectura del repositorio original.
import { createServer, type Server } from "node:http";
import type { AddressInfo } from "node:net";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
describe("Boat trial provisioning", () => {
let api: Server;
let provisionBoat: typeof import("./boat.ts").provisionBoat;
const createBodies: Array<{ ttlSeconds: number; noEnv: boolean }> = [];
const createKeys: string[] = [];
beforeAll(async () => {
api = createServer((req, res) => {
const url = new URL(req.url ?? "/", "http://boat.test");
let raw = "";
req.on("data", (chunk) => (raw += chunk));
req.on("end", () => {
res.setHeader("content-type", "application/json");
if (url.pathname === "/api/v1/sandboxes" && req.method === "GET") {
return res.end(JSON.stringify({ ok: true, type: "sandbox.list", sandboxes: [] }));
}
if (url.pathname === "/api/v1/sandboxes" && req.method === "POST") {
const body = JSON.parse(raw);
createBodies.push(body);
createKeys.push(String(req.headers["idempotency-key"] ?? ""));
if (createBodies.length === 1) {
res.writeHead(400);
return res.end(JSON.stringify({
ok: false,
error: { code: "trial_auto_stop_required", details: { maxTtlSeconds: 7200 } },
}));
}
res.writeHead(202);
return res.end(JSON.stringify({ ok: true, type: "sandbox.created", sandbox: { id: "bx_23456789", state: "ready" } }));
}
if (url.pathname === "/api/v1/sandboxes/bx_23456789" && req.method === "PATCH") {
return res.end(JSON.stringify({ ok: true }));
}
if (url.pathname === "/api/v1/sandboxes/bx_23456789" && req.method === "GET") {
return res.end(JSON.stringify({ ok: true, sandbox: { id: "bx_23456789", state: "ready" } }));
}
if (url.pathname.endsWith("/commands")) {
return res.end(JSON.stringify({ ok: true, exitCode: 0, stdout: "", stderr: "" }));
}
if (url.pathname.endsWith("/desktop")) {
return res.end(JSON.stringify({ ok: true, desktopUrl: "https://desktop.example/vnc" }));
}
res.writeHead(404).end(JSON.stringify({ ok: false, message: "unexpected request" }));
});
});
await new Promise<void>((resolve) => api.listen(0, "127.0.0.1", resolve));
// SAFETY: the test server was bound as TCP above, not to a Unix socket.
const port = (api.address() as AddressInfo).port;
vi.stubEnv("OMB_BOAT_API", `http://127.0.0.1:${port}/api/v1`);
vi.resetModules();
({ provisionBoat } = await import("./boat.ts"));
});
afterAll(async () => {
vi.unstubAllEnvs();
await new Promise<void>((resolve) => api.close(() => resolve()));
});
it("retries the structured trial TTL refusal exactly once at the allowed ceiling", async () => {
// SAFETY: AppConfig's remaining sections are optional; this test supplies
// the only credential the Boat path reads.
const result = await provisionBoat({ box: { token: "box_trial" } } as any, "trial-bot", "Trial Bot");
expect(result.boxId).toBe("bx_23456789");
expect(createBodies).toEqual([
{ ttlSeconds: 8 * 60 * 60, noEnv: true },
{ ttlSeconds: 2 * 60 * 60, noEnv: true },
]);
expect(createKeys[0]).toMatch(/^[0-9a-f-]{36}$/);
expect(createKeys[1]).toMatch(/^[0-9a-f-]{36}$/);
expect(createKeys[1]).not.toBe(createKeys[0]);
});
});