Agent Sandbox Registry

awesome-providers

opensandbox/tests/live/live.test.mjs

无中文版

opensandbox/tests/live/live.test.mjs

// Live tier: the plugin over EPP against a real OpenSandbox server. Opt in with OPENSANDBOX_LIVE=1.
//
//   # start a server with the SDK harness python-sdk/e2e/opensandbox/local-up.sh, then:
//   OPENSANDBOX_LIVE=1 node tests/run.mjs live
//
// Reads ABX_OPENSANDBOX_BASE_URL / ABX_OPENSANDBOX_API_KEY (or the OPENSANDBOX_* names printed by local-up.sh env).
import test from "node:test";
import assert from "node:assert/strict";
import { randomBytes } from "node:crypto";
import { EppHost } from "../epp-client.mjs";

const live = process.env.OPENSANDBOX_LIVE === "1";
const baseUrl = process.env.ABX_OPENSANDBOX_BASE_URL ?? process.env.OPENSANDBOX_BASE_URL ?? "http://127.0.0.1:8080";
const apiKey = process.env.ABX_OPENSANDBOX_API_KEY ?? process.env.OPENSANDBOX_API_KEY;
const image = process.env.OPENSANDBOX_TEST_IMAGE ?? "python:3.12-slim";

const rpcError = async (promise) => {
  try {
    await promise;
  } catch (err) {
    return err.rpc;
  }
  assert.fail("expected an RPC error");
};

test("live: full lifecycle, exec, files, ports against a real OpenSandbox", { skip: !live, timeout: 240_000 }, async () => {
  const host = new EppHost();
  await host.initialize({ base_url: baseUrl, api_key: apiKey, default_image: image }, { http_timeout: 30, max_retries: 2 });
  let id;
  const call = (method, params = {}) => host.call(method, { sandbox_id: id, ...params });
  try {
    const rec = await host.call("sandbox.create", { template: "base", timeout: 300, metadata: { name: "abx-live", suite: "node" }, envs: { LIVE_VAR: "bar" }, cpu: 1, memory: 512 });
    id = rec.sandbox_id;
    assert.equal(rec.status, "running");
    assert.equal(rec.template, image);

    // exec
    const r = await call("exec.run", { cmd: "sh", args: ["-c", "echo out; echo err >&2; echo $LIVE_VAR; printf 'x\\r\\ny'; exit 3"], timeout: 30 });
    assert.deepEqual(r, { exit_code: 3, stdout: "out\nbar\nx\r\ny", stderr: "err\n" });
    assert.equal((await call("exec.run", { cmd: "nonexistent-xyz", args: [], timeout: 30 })).exit_code, 127);
    assert.equal((await call("exec.run", { cmd: "cat", args: [], stdin: "piped", timeout: 30 })).stdout, "piped");
    assert.equal((await call("exec.run", { cmd: "id", args: ["-un"], user: "nobody", timeout: 30 })).stdout, "nobody\n");
    assert.equal((await rpcError(call("exec.run", { cmd: "sleep", args: ["10"], timeout: 1 }))).code, -32005);

    const started = await call("exec.start", { cmd: "sh", args: ["-c", "echo a; sleep 0.5; echo b >&2; exit 2"], timeout: 30 });
    const exit = await host.waitNote((n) => n.method === "exec.exit" && n.params.process_id === started.process_id);
    assert.equal(exit.params.exit_code, 2);
    const streamed = host.notes.filter((n) => n.method === "exec.output" && n.params.process_id === started.process_id).map((n) => `${n.params.stream}:${n.params.data}`);
    assert.deepEqual(streamed, ["stdout:a\n", "stderr:b\n"]);

    // processes
    const bg = await call("exec.start", { cmd: "sleep", args: ["60"], timeout: 120, detached: true });
    const { processes } = await call("process.list");
    const sleeper = processes.find((p) => p.command.startsWith("sleep 60"));
    assert.ok(sleeper, JSON.stringify(processes));
    await call("process.kill", { pid: sleeper.pid });
    await host.waitNote((n) => n.method === "exec.exit" && n.params.process_id === bg.process_id);
    assert.equal((await rpcError(call("process.stdin", { pid: 1, data: "x" }))).code, -32003);

    // files: 3 MB of random bytes survive base64 + multipart + download
    const blob = randomBytes(3 * 1024 * 1024);
    await call("files.write", { path: "/tmp/live/blob.bin", content_b64: blob.toString("base64") });
    const back = await call("files.read", { path: "/tmp/live/blob.bin" });
    assert.ok(Buffer.from(back.content_b64, "base64").equals(blob));
    const part = await call("files.read", { path: "/tmp/live/blob.bin", offset: 10, length: 20 });
    assert.ok(Buffer.from(part.content_b64, "base64").equals(blob.subarray(10, 30)));
    await call("files.write", { path: "/tmp/live/sub/hello.txt", content_b64: Buffer.from("hello\n").toString("base64"), mode: 0o640 });
    assert.equal((await call("files.stat", { path: "/tmp/live/sub/hello.txt" })).mode, 640);
    assert.deepEqual((await call("files.list", { path: "/tmp/live" })).entries.map((e) => e.name).sort(), ["blob.bin", "sub"]);
    assert.deepEqual((await call("files.search", { path: "/tmp/live", pattern: "**/*.txt" })).entries.map((e) => e.path), ["/tmp/live/sub/hello.txt"]);
    await call("files.move", { source: "/tmp/live/sub/hello.txt", destination: "/tmp/live/moved.txt" });
    assert.equal((await rpcError(call("files.stat", { path: "/tmp/live/sub/hello.txt" }))).data.sdk_code, "E4001");
    assert.equal((await rpcError(call("files.move", { source: "/tmp/live/moved.txt", destination: "/tmp/live/blob.bin" }))).code, -32006);
    await call("files.remove", { path: "/tmp/live/moved.txt" });
    await call("files.remove", { path: "/tmp/live" });
    assert.equal((await rpcError(call("files.read", { path: "/tmp/live" }))).code, -32002);

    // lifecycle
    assert.equal((await host.call("sandbox.list", { metadata: { suite: "node" }, status: "running" })).sandboxes.some((s) => s.sandbox_id === id), true);
    const ttl = await call("sandbox.set_timeout", { timeout: 900 });
    const delta = Date.parse(ttl.expires_at) - Date.now();
    assert.ok(Math.abs(delta - 900_000) < 5000, `ttl off by ${delta - 900_000} ms`);
    await call("sandbox.pause");
    assert.equal((await call("sandbox.get")).status, "paused");
    await call("sandbox.resume");
    assert.equal((await call("exec.run", { cmd: "echo", args: ["alive"], timeout: 30 })).stdout, "alive\n");

    // ports
    const url = await call("ports.url", { port: 8000 });
    assert.match(url.url, /^http:\/\/.+\/proxy\/8000$/);
    const served = await call("exec.start", { cmd: "python3", args: ["-m", "http.server", "8000", "--directory", "/etc"], timeout: 60, detached: true });
    await new Promise((r) => setTimeout(r, 1500));
    const page = await fetch(`${url.url}/hostname`, { headers: url.headers ?? {} });
    assert.equal(page.status, 200);
    await call("process.kill", { pid: served.process_id });

    // errors
    assert.equal((await rpcError(host.call("sandbox.create", { template: "this-image-does-not-exist.invalid/x:1" }))).code, -32002);
    assert.equal((await rpcError(host.call("sandbox.create", { template: "base", metadata: { "bad key": "v" } }))).code, -32006);
    assert.equal((await rpcError(host.call("sandbox.create", { template: "base", disk: 10 }))).code, -32006);
  } finally {
    if (id) {
      await host.call("sandbox.kill", { sandbox_id: id });
      assert.deepEqual(await host.call("sandbox.kill", { sandbox_id: id }), {}, "second kill is idempotent");
      assert.equal((await rpcError(host.call("sandbox.get", { sandbox_id: id }))).code, -32002);
    }
    await host.close();
  }
});

test("live: a wrong API key is -32001", { skip: !live }, async () => {
  const host = new EppHost();
  await host.initialize({ base_url: baseUrl, api_key: "definitely-wrong-key" });
  const err = await rpcError(host.call("sandbox.list", {}));
  assert.equal(err.code, -32001);
  assert.ok(!JSON.stringify(err).includes("definitely-wrong-key"));
  await host.close();
});