evOo-local/packages/adapters/src/box-sandbox.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

486 lines
18 KiB
TypeScript

import type { Command200Response } from "@asciidev/box-sdk";
import { afterEach, describe, expect, it, vi } from "vitest";
import { BoxSandboxProvider, type BoxSandboxSdk, isUnrecoverableBoxError } from "./box-sandbox.js";
import { desktopCommandResponder } from "./linux-desktop.test-support.js";
const context = {
operationId: "test",
traceId: "trace",
spaceId: "workspace",
userId: "user",
botId: "bot-a",
signal: new AbortController().signal,
};
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe("BoxSandboxProvider", () => {
it("creates no-env boxes and implements execution and file operations", async () => {
const fixture = boxFixture();
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
expect(computer).toMatchObject({
id: "bx_testbox2",
providerRef: "bx_testbox2",
kind: "box",
fresh: true,
});
expect(fixture.create).toHaveBeenCalledWith(
{
createBoxRequest: {
ttlSeconds: 7200,
noEnv: true,
env: { RAKAZO_BOT_ID: "bot-a", RAKAZO_SANDBOX: "computer" },
},
},
{ signal: context.signal },
);
await provider.prepare(computer, context);
expect(
fixture.command.mock.calls.some(([request]) =>
request.commandRequest.command.includes("rakazo-home/.browser-profiles"),
),
).toBe(true);
const events = [];
for await (const event of provider.execute(
computer,
{ argv: ["echo", "hello"], cwd: "notes", env: { TEST_VALUE: "works" } },
context,
)) {
events.push(event);
}
expect(events).toEqual([
{ type: "stdout", data: "hello\n" },
{ type: "exit", code: 0 },
]);
const executeRequest = fixture.command.mock.calls.find(([request]) =>
request.commandRequest.command.includes("TEST_VALUE=works"),
)?.[0];
expect(executeRequest?.commandRequest.cwd).toBe("rakazo-home/notes");
await provider.writeFile(
computer,
{ path: "bin/tool", content: Uint8Array.from([0, 255, 1]), executable: true },
context,
);
expect(await provider.readFile(computer, "bin/tool", context)).toEqual(
Uint8Array.from([0, 255, 1]),
);
expect(await provider.listFiles(computer, "bin", context)).toEqual([
{ path: "bin/tool", kind: "file", size: 3, executable: true },
]);
const exported = [];
for await (const file of provider.exportWorkspace(computer, context)) exported.push(file);
expect(exported).toEqual([
{ path: "bin/tool", content: Uint8Array.from([0, 255, 1]), executable: true },
]);
});
it("resumes archived boxes and replaces deleted references", async () => {
const archived = boxFixture({ state: "archived" });
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, archived.client);
const reconnected = await provider.provision(
{
botId: "bot-a",
homePath: "/unused",
providerRef: "bx_existing2",
providerKind: "box",
},
context,
);
expect(reconnected).toMatchObject({ providerRef: "bx_existing2", fresh: false });
expect(archived.resume).toHaveBeenCalledWith(
{
boxId: "bx_existing2",
resumeRequest: { noEnv: true, ttlSeconds: 7200 },
},
{ signal: context.signal },
);
const missing = boxFixture();
missing.get.mockRejectedValueOnce({ status: 404 });
const replacing = new BoxSandboxProvider({ apiKey: "test-key" }, missing.client);
const replacement = await replacing.provision(
{
botId: "bot-b",
homePath: "/unused",
providerRef: "bx_missing2",
providerKind: "box",
},
context,
);
expect(replacement).toMatchObject({ providerRef: "bx_testbox2", fresh: true });
});
it("downloads binary files over the JSON endpoint limit through the SDK artifact route", async () => {
const content = Buffer.alloc(6 * 1024 * 1024, 0xff);
content[0] = 0;
const fetchMock = vi
.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(
new Response(content, { headers: { "content-length": String(content.byteLength) } }),
);
try {
const provider = new BoxSandboxProvider({
apiKey: "test-key",
apiUrl: "https://box.test/api/box/v1",
});
const computer = {
id: "bx_testbox2",
providerRef: "bx_testbox2",
kind: "box" as const,
botId: "bot-a",
};
const filePath = "notes/a file & more.bin";
const downloaded = await provider.readFile(computer, filePath, context);
expect(Buffer.from(downloaded).equals(content)).toBe(true);
expect(fetchMock).toHaveBeenCalledTimes(1);
const [request, init] = fetchMock.mock.calls[0]!;
const url = new URL(String(request));
expect(url.pathname).toBe("/api/box/v1/boxes/bx_testbox2/artifacts");
expect(url.searchParams.get("path")).toBe(`/home/user/rakazo-home/${filePath}`);
expect(new Headers(init?.headers).get("Authorization")).toBe("Bearer test-key");
expect(init?.signal).toBe(context.signal);
} finally {
fetchMock.mockRestore();
}
});
it("exports browser profile files over 5 MiB without losing their bytes", async () => {
const fixture = boxFixture();
const content = Buffer.alloc(6 * 1024 * 1024, 0xab);
fixture.files.set("/home/user/rakazo-home/.browser-profiles/chrome/Default/History", content);
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
const exported = [];
for await (const file of provider.exportWorkspace(computer, context)) exported.push(file);
expect(exported).toHaveLength(1);
expect(exported[0]?.path).toBe(".browser-profiles/chrome/Default/History");
expect(Buffer.from(exported[0]!.content).equals(content)).toBe(true);
expect(fixture.artifactRaw).toHaveBeenCalledWith(
{
boxId: computer.id,
path: "/home/user/rakazo-home/.browser-profiles/chrome/Default/History",
},
{ signal: context.signal },
);
});
it.each([undefined, "1", "20"])(
"cancels oversized downloads with content-length %s",
async (contentLength) => {
const fixture = boxFixture();
const cancel = vi.fn();
const pull = vi.fn((controller: ReadableStreamDefaultController<Uint8Array>) => {
controller.enqueue(Uint8Array.from([1, 2, 3]));
});
const body = new ReadableStream({ pull, cancel }, { highWaterMark: 0 });
fixture.artifactRaw.mockResolvedValueOnce({
raw: new Response(body, {
headers: contentLength ? { "content-length": contentLength } : undefined,
}),
});
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
await expect(
provider.readFile(computer, "large.bin", context, { maxBytes: 4 }),
).rejects.toThrow("computer file exceeds 4 bytes");
expect(pull).toHaveBeenCalledTimes(contentLength === "20" ? 0 : 2);
expect(cancel).toHaveBeenCalledTimes(1);
expect(body.locked).toBe(false);
},
);
it("accepts empty files and files exactly at the download limit", async () => {
const fixture = boxFixture();
fixture.files.set("/home/user/rakazo-home/exact.bin", Uint8Array.from([0, 255]));
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
expect(await provider.readFile(computer, "empty.bin", context, { maxBytes: 0 })).toHaveLength(
0,
);
expect(await provider.readFile(computer, "exact.bin", context, { maxBytes: 2 })).toEqual(
Uint8Array.from([0, 255]),
);
});
it("propagates interrupted downloads instead of returning partial files", async () => {
const fixture = boxFixture();
const aborted = new DOMException("download aborted", "AbortError");
let first = true;
const body = new ReadableStream({
pull(controller) {
if (first) controller.enqueue(Uint8Array.from([1, 2, 3]));
else controller.error(aborted);
first = false;
},
});
fixture.artifactRaw.mockResolvedValueOnce({ raw: new Response(body) });
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
await expect(provider.readFile(computer, "partial.bin", context)).rejects.toBe(aborted);
expect(body.locked).toBe(false);
});
it("captures independent desktops and exposes protected ports", async () => {
const fixture = boxFixture();
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
await provider.prepare(computer, context);
const observation = await provider.observe(computer, context);
expect(observation).toMatchObject({
mimeType: "image/png",
width: 1280,
height: 800,
cursor: { x: 10, y: 20 },
});
expect(observation.image).toEqual(Uint8Array.from([1, 2, 3]));
const screen = await provider.connectScreen(
computer,
{ view: "stream", interactive: false },
context,
);
expect(new URL(screen.url!).searchParams.get("path")).toMatch(
/^websockify\?_token=secret&token=view-/,
);
expect(screen.url).toContain("_token=secret");
await provider.act(
computer,
{ actions: [{ kind: "pointer", type: "click", x: 100, y: 200 }], observe: false },
context,
);
expect(
fixture.command.mock.calls.some(([request]) =>
request.commandRequest.command.includes("xdotool mousemove 100 200 click 1"),
),
).toBe(true);
await expect(provider.observe(computer, { ...context, botId: "bot-b" })).resolves.toMatchObject(
{ width: 1280 },
);
expect(provider.describe().capabilities.multiScreen).toBe(true);
});
it("archives on stop and permanently deletes on destroy", async () => {
const fixture = boxFixture();
const provider = new BoxSandboxProvider({ apiKey: "test-key" }, fixture.client);
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
fixture.stop.mockResolvedValueOnce(actionResponse("archiving"));
fixture.get
.mockResolvedValueOnce(infoResponse("bx_testbox2", "ready"))
.mockResolvedValue(infoResponse("bx_testbox2", "archived"));
await provider.stop(computer, context);
expect(fixture.stop).toHaveBeenCalledWith({ boxId: "bx_testbox2" }, { signal: context.signal });
await provider.destroy(computer, context);
expect(fixture.deleteBox).toHaveBeenCalledWith("bx_testbox2");
});
it("bounds a stalled permanent-delete request", async () => {
const deadline = new AbortController();
vi.spyOn(AbortSignal, "timeout").mockReturnValue(deadline.signal);
const fetchMock = vi.fn(
async (_url: string | URL | Request, init?: RequestInit): Promise<Response> =>
new Promise((_resolve, reject) => {
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), {
once: true,
});
}),
);
vi.stubGlobal("fetch", fetchMock);
const provider = new BoxSandboxProvider({ apiKey: "test-key" });
const pending = provider.destroy(
{ id: "box-test", providerRef: "box-test", botId: "bot-test", kind: "box" },
context,
);
deadline.abort(new DOMException("Timed out", "TimeoutError"));
await expect(pending).rejects.toThrow("Box box-test was not deleted within 60 seconds");
expect(fetchMock).toHaveBeenCalledOnce();
expect(fetchMock.mock.calls[0]?.[1]?.signal).toBe(deadline.signal);
});
it("keeps the same deadline on permanent-delete status polling", async () => {
const deadline = new AbortController();
vi.spyOn(AbortSignal, "timeout").mockReturnValue(deadline.signal);
const fetchMock = vi
.fn()
.mockResolvedValueOnce(new Response(null, { status: 202 }))
.mockImplementationOnce(
async (_url: string | URL | Request, init?: RequestInit): Promise<Response> =>
new Promise((_resolve, reject) => {
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), {
once: true,
});
}),
);
vi.stubGlobal("fetch", fetchMock);
const provider = new BoxSandboxProvider({ apiKey: "test-key" });
const pending = provider.destroy(
{ id: "box-test", providerRef: "box-test", botId: "bot-test", kind: "box" },
context,
);
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
deadline.abort(new DOMException("Timed out", "TimeoutError"));
await expect(pending).rejects.toThrow("Box box-test was not deleted within 60 seconds");
expect(fetchMock.mock.calls[0]?.[1]?.signal).toBe(deadline.signal);
expect(fetchMock.mock.calls[1]?.[1]?.signal).toBe(deadline.signal);
});
it("recognizes provider 404 errors without swallowing transient failures", () => {
expect(isUnrecoverableBoxError({ status: 404 })).toBe(true);
expect(isUnrecoverableBoxError(new Error("temporary Box outage"))).toBe(false);
});
});
function boxFixture(options: { state?: string } = {}) {
const files = new Map<string, Uint8Array>();
const respond = desktopCommandResponder();
const create = vi.fn(async () => createResponse());
const fixture = {
state: options.state ?? "ready",
create,
get: vi.fn(async ({ boxId }: { boxId: string }) => infoResponse(boxId, fixture.state)),
resume: vi.fn(async ({ boxId }: { boxId: string }) => {
fixture.state = "ready";
return actionResponse("ready", boxId);
}),
stop: vi.fn(async ({ boxId }: { boxId: string }) => actionResponse("archiving", boxId)),
update: vi.fn(async ({ boxId }: { boxId: string }) => infoResponse(boxId, fixture.state)),
deleteBox: vi.fn(async () => undefined),
desktop: vi.fn(async () => ({
ok: true,
type: "desktop.url",
success: true,
desktopUrl: "https://vnc.box.test/vnc.html?password=pw&_token=secret",
mode: "vnc",
})),
commandStatus: vi.fn(),
command: vi.fn(async (request: { commandRequest: { command: string; cwd?: string } }) => {
const command = request.commandRequest.command;
const response = respond(command);
if (response) return finished(response);
if (command.includes("host ") && command.includes("--private")) {
const port = command.match(/host (\d+)/)?.[1];
return finished({ stdout: `https://box-test-${port}.on.ascii.dev?_token=secret` });
}
if (command.includes("TEST_VALUE=works")) return finished({ stdout: "hello\n" });
if (command.includes("find ") && command.includes("rakazo-home/bin")) {
const target = "/home/user/rakazo-home/bin/tool";
return finished({
stdout: `${Buffer.from(target).toString("base64")}\tfile\t3\t1\n`,
});
}
if (
command.includes("find ") &&
command.includes("rakazo-home") &&
command.includes("-type f")
) {
const stdout = [...files.entries()]
.filter(([filePath]) => filePath.startsWith("/home/user/rakazo-home/"))
.map(([filePath, content]) => {
const executable = filePath.endsWith("/bin/tool") ? "1" : "0";
return `${Buffer.from(filePath).toString("base64")}\tfile\t${content.byteLength}\t${executable}\n`;
})
.join("");
return finished({ stdout });
}
if (command.includes("import -window root")) {
return finished({
stdout: [
"WIDTH=1920",
"HEIGHT=1080",
"X=21",
"Y=34",
"WINDOW=42",
`TITLE=${Buffer.from("Box browser").toString("base64")}`,
"",
].join("\n"),
});
}
return finished();
}),
writeFile: vi.fn(
async ({ fileWriteRequest }: { fileWriteRequest: { path: string; content: string } }) => {
files.set(
fileWriteRequest.path,
Uint8Array.from(Buffer.from(fileWriteRequest.content, "base64")),
);
return {
ok: true,
type: "file.written" as const,
success: true,
path: fileWriteRequest.path,
encoding: "base64" as const,
size: files.get(fileWriteRequest.path)?.byteLength ?? 0,
};
},
),
artifactRaw: vi.fn(async ({ path }: { path: string }) => {
const content = path.startsWith("/tmp/rakazo-observe-")
? Uint8Array.from([1, 2, 3])
: (files.get(path) ?? new Uint8Array());
return { raw: new Response(Buffer.from(content)) };
}),
};
return { ...fixture, files, client: fixture as unknown as BoxSandboxSdk };
}
function box(id: string, state: string) {
return {
id,
name: "Test Box",
state,
desktopAvailable: true,
snapshotAvailable: state === "archived",
};
}
function createResponse() {
return {
ok: true,
type: "box.created",
status: "provisioning",
ttlSeconds: null,
box: box("bx_testbox2", "provisioning"),
};
}
function infoResponse(id: string, state: string) {
return { ok: true, type: "box.info", box: box(id, state) };
}
function actionResponse(status: string, id = "bx_testbox2") {
return { ok: true, type: "box.action", id, status, box: box(id, status) };
}
function finished(overrides: Partial<Command200Response> = {}): Command200Response {
return {
ok: true,
type: "command.finished",
success: true,
exitCode: 0,
stdout: "",
stderr: "",
timedOut: false,
...overrides,
} as Command200Response;
}