100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import { execSync } from "node:child_process";
|
|
import { cwd } from "node:process";
|
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { detectMock } = vi.hoisted(() => ({ detectMock: vi.fn() }));
|
|
|
|
vi.mock("../../sandbox/bubblewrap-detect.js", () => ({
|
|
detectBwrap: detectMock,
|
|
}));
|
|
|
|
import { BubblewrapBackend, SandboxUnavailableError } from "../../sandbox/bubblewrap-backend.js";
|
|
import type { SandboxBackend, SandboxRunResult } from "../../sandbox/types.js";
|
|
|
|
const hasBwrap = (() => {
|
|
try {
|
|
return !!execSync("command -v bwrap", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
describe("BubblewrapBackend", () => {
|
|
beforeEach(() => {
|
|
detectMock.mockReset();
|
|
});
|
|
|
|
it("reports bubblewrap capabilities", () => {
|
|
const backend = new BubblewrapBackend();
|
|
expect(backend.capabilities().id).toBe("bubblewrap");
|
|
expect(backend.capabilities().supportsFilesystemPolicy).toBe(true);
|
|
});
|
|
|
|
it("throws on prepare when bwrap unavailable and fail-hard", async () => {
|
|
detectMock.mockResolvedValue({ available: false, reason: "not-installed" });
|
|
const backend = new BubblewrapBackend();
|
|
|
|
await expect(backend.prepare({ allowNetwork: true })).rejects.toBeInstanceOf(SandboxUnavailableError);
|
|
});
|
|
|
|
it("falls back to native when requested", async () => {
|
|
detectMock.mockResolvedValue({ available: false, reason: "not-installed" });
|
|
const runResult: SandboxRunResult = {
|
|
stdout: "native",
|
|
stderr: "",
|
|
exitCode: 0,
|
|
signal: null,
|
|
timedOut: false,
|
|
bufferExceeded: false,
|
|
};
|
|
|
|
const nativeStub: SandboxBackend = {
|
|
capabilities: () => ({ id: "native", supportsNetworkPolicy: false, supportsFilesystemPolicy: false, platform: "any" }),
|
|
prepare: vi.fn(async () => undefined),
|
|
run: vi.fn(async () => runResult),
|
|
dispose: vi.fn(async () => undefined),
|
|
};
|
|
|
|
const backend = new BubblewrapBackend(nativeStub);
|
|
await backend.prepare({ allowNetwork: true, env: {}, allowedReadPaths: [], allowedWritePaths: [], failureMode: "fallback-native" } as any);
|
|
|
|
const result = await backend.run("echo hi", { cwd: cwd(), timeoutMs: 1_000, maxBuffer: 1024, encoding: "utf-8" });
|
|
expect(result.stdout).toBe("native");
|
|
expect(nativeStub.run).toHaveBeenCalled();
|
|
});
|
|
|
|
it("attempts bwrap execution when available", async () => {
|
|
detectMock.mockResolvedValue({ available: true, path: "bwrap" });
|
|
const backend = new BubblewrapBackend();
|
|
await backend.prepare({ allowNetwork: true });
|
|
|
|
const result = await backend.run("echo hello", {
|
|
cwd: cwd(),
|
|
timeoutMs: 5_000,
|
|
maxBuffer: 1024 * 1024,
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
expect(result).toHaveProperty("stdout");
|
|
expect(result).toHaveProperty("stderr");
|
|
});
|
|
|
|
it.skipIf(process.platform !== "linux" || !hasBwrap)("runs real bubblewrap hello integration", async () => {
|
|
vi.doUnmock("../../sandbox/bubblewrap-detect.js");
|
|
const { BubblewrapBackend: RealBackend } = await import("../../sandbox/bubblewrap-backend.js");
|
|
const backend = new RealBackend();
|
|
await backend.prepare({ allowNetwork: true });
|
|
|
|
const result = await backend.run("echo hello", {
|
|
cwd: cwd(),
|
|
timeoutMs: 5_000,
|
|
maxBuffer: 1024 * 1024,
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.stdout.trim()).toBe("hello");
|
|
});
|
|
});
|