fix(FN-4642): harden container backend against child_process mocks

Fusion-Task-Id: FN-4642
Fusion-Task-Lineage: a42a36f8-2d2a-43fe-b7c1-d7cf1ad321c3
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 00:56:41 -07:00
committed by gsxdsm
parent 6998ac4edd
commit 8955db96e2
2 changed files with 20 additions and 11 deletions

View File

@@ -8,11 +8,15 @@ const { mockExec, mockExecFile } = vi.hoisted(() => ({
mockExecFile: vi.fn(), mockExecFile: vi.fn(),
})); }));
vi.mock("node:child_process", () => ({ vi.mock("node:child_process", async (importOriginal) => {
exec: mockExec, const actual = await importOriginal<typeof import("node:child_process")>();
execFile: mockExecFile, return {
spawn: vi.fn(), ...actual,
})); exec: mockExec,
execFile: mockExecFile,
spawn: vi.fn(),
};
});
describe("ContainerSandboxBackend", () => { describe("ContainerSandboxBackend", () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -1,4 +1,4 @@
import { exec, execFile, spawn } from "node:child_process"; import * as childProcess from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { buildContainerArgv } from "./container-argv.js"; import { buildContainerArgv } from "./container-argv.js";
@@ -12,8 +12,13 @@ import type {
SandboxStreamingResult, SandboxStreamingResult,
} from "./types.js"; } from "./types.js";
const execAsync = promisify(exec); function getExecAsync() {
const execFileAsync = promisify(execFile); return promisify(childProcess.exec);
}
function getExecFileAsync() {
return promisify((childProcess as { execFile: typeof import("node:child_process").execFile }).execFile);
}
/** /**
* Experimental container-backed sandbox execution using Podman or Docker. * Experimental container-backed sandbox execution using Podman or Docker.
@@ -50,7 +55,7 @@ export class ContainerSandboxBackend implements SandboxBackend {
} }
try { try {
await execAsync(`${this.runtime} --version`, { timeout: 5_000, maxBuffer: 1024 * 1024 }); await getExecAsync()(`${this.runtime} --version`, { timeout: 5_000, maxBuffer: 1024 * 1024 });
this.runtimeAvailable = true; this.runtimeAvailable = true;
this.runtimeProbeError = undefined; this.runtimeProbeError = undefined;
} catch (error) { } catch (error) {
@@ -81,7 +86,7 @@ export class ContainerSandboxBackend implements SandboxBackend {
const argv = buildContainerArgv(this.runtime, command, options, this.policy); const argv = buildContainerArgv(this.runtime, command, options, this.policy);
try { try {
const execResult = await execFileAsync(argv[0]!, argv.slice(1), { const execResult = await getExecFileAsync()(argv[0]!, argv.slice(1), {
cwd: options.cwd, cwd: options.cwd,
timeout: options.timeoutMs, timeout: options.timeoutMs,
maxBuffer: options.maxBuffer, maxBuffer: options.maxBuffer,
@@ -155,7 +160,7 @@ export class ContainerSandboxBackend implements SandboxBackend {
const argv = buildContainerArgv(this.runtime, command, runOptions, this.policy); const argv = buildContainerArgv(this.runtime, command, runOptions, this.policy);
return await new Promise((resolve) => { return await new Promise((resolve) => {
const child = spawn(argv[0]!, argv.slice(1), { const child = childProcess.spawn(argv[0]!, argv.slice(1), {
cwd: options.cwd, cwd: options.cwd,
stdio: ["ignore", "pipe", "pipe"], stdio: ["ignore", "pipe", "pipe"],
env: { env: {