Files
fusion/packages/engine/src/__tests__/sandbox/bubblewrap-backend.test.ts
Fusion 3427e8b471 fix(FN-4637): harden bubblewrap runner and extend coverage
Fusion-Task-Id: FN-4637
Fusion-Task-Lineage: 564c5692-3aaf-4396-9306-a395703cf365
2026-05-15 10:50:03 -07:00

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");
});
});