chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,546 @@
/**
* Unit tests for sandboxProcessWrapper
*
* Tests buildSandboxedSpawnArgs() behavior:
* - enabled=false: returns original command/args with NOOP_CLEANUP
* - docker type: logs warning and returns original command/args
* - macos-seatbelt type: returns sandbox-exec command
* - linux-bwrap type: returns bwrap command
* - error handling: re-throws errors from SandboxInvoker.buildInvocation
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as fs from "fs";
import * as path from "path";
import log from "electron-log";
import { SandboxInvoker } from "./SandboxInvoker";
import { buildSandboxedSpawnArgs } from "./sandboxProcessWrapper";
import type { SandboxProcessConfig } from "@shared/types/sandbox";
// =============================================================================
// Mock electron-log
// =============================================================================
const mockLogWarn = vi.fn();
const mockLogInfo = vi.fn();
const mockLogError = vi.fn();
vi.mock("electron-log", () => ({
default: {
warn: (...args: unknown[]) => mockLogWarn(...args),
info: (...args: unknown[]) => mockLogInfo(...args),
error: (...args: unknown[]) => mockLogError(...args),
},
}));
// =============================================================================
// Mock fs
// =============================================================================
const mockExistsSync = vi.fn();
const mockMkdirSync = vi.fn();
const mockUnlinkSync = vi.fn();
const mockRealpathSync = vi.fn();
vi.mock("fs", () => ({
existsSync: (...args: unknown[]) => mockExistsSync(...args),
mkdirSync: (...args: unknown[]) => mockMkdirSync(...args),
unlinkSync: (...args: unknown[]) => mockUnlinkSync(...args),
realpathSync: (...args: unknown[]) => mockRealpathSync(...args),
}));
// =============================================================================
// Mock SandboxInvoker
// =============================================================================
const mockBuildInvocation = vi.fn();
vi.mock("./SandboxInvoker", () => ({
SandboxInvoker: vi.fn().mockImplementation(() => ({
buildInvocation: mockBuildInvocation,
})),
}));
// =============================================================================
// Test helpers
// =============================================================================
const NOOP_CLEANUP = () => {};
const createBaseConfig = (): SandboxProcessConfig => ({
enabled: true,
type: "macos-seatbelt",
projectWorkspaceDir: "/tmp/ws",
networkEnabled: false,
fallback: "degrade_to_off",
});
// =============================================================================
// Tests
// =============================================================================
describe("buildSandboxedSpawnArgs", () => {
beforeEach(() => {
vi.clearAllMocks();
mockExistsSync.mockReturnValue(true); // Default: paths exist
mockMkdirSync.mockReturnValue(undefined);
mockUnlinkSync.mockReturnValue(undefined);
mockRealpathSync.mockImplementation((p: string) => p);
mockBuildInvocation.mockReset();
});
// ---------------------------------------------------------------------------
// Test: enabled=false returns original command/args with NOOP_CLEANUP
// ---------------------------------------------------------------------------
describe("when enabled is false", () => {
it("should return original command and args unchanged", async () => {
const config: SandboxProcessConfig = {
enabled: false,
type: "macos-seatbelt",
projectWorkspaceDir: "/tmp/ws",
networkEnabled: false,
fallback: "degrade_to_off",
};
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
expect(result.command).toBe("/bin/ls");
expect(result.args).toEqual(["-la"]);
});
it("should return NOOP_CLEANUP function", async () => {
const config: SandboxProcessConfig = {
enabled: false,
type: "macos-seatbelt",
projectWorkspaceDir: "/tmp/ws",
networkEnabled: false,
fallback: "degrade_to_off",
};
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
// cleanupSandbox should be a noop function (truthy and returns undefined)
expect(typeof result.cleanupSandbox).toBe("function");
expect(result.cleanupSandbox()).toBeUndefined();
});
it("should not call SandboxInvoker when disabled", async () => {
const config: SandboxProcessConfig = {
enabled: false,
type: "macos-seatbelt",
projectWorkspaceDir: "/tmp/ws",
networkEnabled: false,
fallback: "degrade_to_off",
};
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config);
expect(SandboxInvoker).not.toHaveBeenCalled();
});
});
// ---------------------------------------------------------------------------
// Test: docker type logs warning and returns original command/args
// ---------------------------------------------------------------------------
describe("when type is docker", () => {
it("should log warning about docker not supported", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "docker",
};
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config);
expect(mockLogWarn).toHaveBeenCalledWith(
expect.stringContaining("Docker process-level sandbox not supported"),
);
});
it("should return original command and args unchanged", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "docker",
};
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
expect(result.command).toBe("/bin/ls");
expect(result.args).toEqual(["-la"]);
});
it("should return NOOP_CLEANUP", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "docker",
};
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
// cleanupSandbox should be a noop function (truthy and returns undefined)
expect(typeof result.cleanupSandbox).toBe("function");
expect(result.cleanupSandbox()).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// Test: macos-seatbelt type returns sandbox-exec command
// ---------------------------------------------------------------------------
describe("when type is macos-seatbelt", () => {
it("should return sandbox-exec command", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
expect(result.command).toBe("/usr/bin/sandbox-exec");
expect(result.args[0]).toBe("-f");
expect(result.args[2]).toBe("/bin/ls");
});
it("should create workspace directory if it does not exist", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
projectWorkspaceDir: "/tmp/nonexistent-ws",
};
mockExistsSync.mockImplementation((p: string) => {
if (p === "/tmp/nonexistent-ws") return false;
return true;
});
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config);
expect(mockMkdirSync).toHaveBeenCalledWith("/tmp/nonexistent-ws", {
recursive: true,
});
});
it("should return a cleanup function that deletes seatbelt profile", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
// cleanupSandbox should be a function (not NOOP_CLEANUP)
expect(typeof result.cleanupSandbox).toBe("function");
expect(result.cleanupSandbox).not.toBe(NOOP_CLEANUP);
// Calling cleanup should delete the profile file
result.cleanupSandbox();
expect(mockUnlinkSync).toHaveBeenCalledWith(
"/tmp/qimingclaw-sandbox-123.sb",
);
});
it("should handle cleanup gracefully when profile file does not exist", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
mockUnlinkSync.mockImplementation(() => {
throw new Error("ENOENT: no such file");
});
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
// Should not throw
expect(() => result.cleanupSandbox()).not.toThrow();
});
});
// ---------------------------------------------------------------------------
// Test: linux-bwrap type returns bwrap command
// ---------------------------------------------------------------------------
describe("when type is linux-bwrap", () => {
it("should return bwrap command", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "linux-bwrap",
projectWorkspaceDir: "/tmp/ws",
linuxBwrapPath: "/usr/bin/bwrap",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/bwrap",
args: [
"--die-with-parent",
"--new-session",
"--unshare-user-try",
"--unshare-pid",
"--unshare-uts",
"--unshare-cgroup-try",
"--dev-bind",
"/dev",
"/dev",
"--proc",
"/proc",
"--tmpfs",
"/tmp",
"--ro-bind",
"/",
"/",
"--bind",
"/tmp/ws",
"/tmp/ws",
"--chdir",
"/tmp",
"--",
"/bin/ls",
"-la",
],
cwd: "/tmp",
});
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
config,
);
expect(result.command).toBe("/usr/bin/bwrap");
expect(result.args).toContain("--bind");
expect(result.args).toContain("/tmp/ws");
});
it("should pass writablePaths to SandboxInvoker", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "linux-bwrap",
projectWorkspaceDir: "/tmp/ws",
linuxBwrapPath: "/usr/bin/bwrap",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/bwrap",
args: ["--die-with-parent", "--", "/bin/ls", "-la"],
cwd: "/tmp",
});
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config, [
"/tmp/extra-writable",
]);
expect(mockBuildInvocation).toHaveBeenCalledWith(
expect.objectContaining({
writablePaths: ["/tmp/ws", "/tmp/extra-writable"],
}),
);
});
it("should filter out empty writablePaths", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "linux-bwrap",
projectWorkspaceDir: "/tmp/ws",
linuxBwrapPath: "/usr/bin/bwrap",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/bwrap",
args: ["--die-with-parent", "--", "/bin/ls", "-la"],
cwd: "/tmp",
});
// Only truly empty strings (length 0) are filtered; whitespace strings pass through
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config, [
"",
"/tmp/valid-path",
]);
expect(mockBuildInvocation).toHaveBeenCalledWith(
expect.objectContaining({
writablePaths: ["/tmp/ws", "/tmp/valid-path"],
}),
);
});
});
// ---------------------------------------------------------------------------
// Test: error handling - re-throws errors from SandboxInvoker.buildInvocation
// ---------------------------------------------------------------------------
describe("error handling", () => {
it("should re-throw errors from SandboxInvoker.buildInvocation", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
const expectedError = new Error("Sandbox invocation failed");
mockBuildInvocation.mockRejectedValue(expectedError);
await expect(
buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config),
).rejects.toThrow("Sandbox invocation failed");
});
it("should log error before re-throwing", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "linux-bwrap",
};
const expectedError = new Error("bwrap not found");
mockBuildInvocation.mockRejectedValue(expectedError);
await expect(
buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config),
).rejects.toThrow();
expect(mockLogError).toHaveBeenCalledWith(
"[SandboxProcessWrapper] Sandbox wrapping failed:",
expectedError,
);
});
});
// ---------------------------------------------------------------------------
// Test: extraWritablePaths parameter
// ---------------------------------------------------------------------------
describe("extraWritablePaths parameter", () => {
it("should include extraWritablePaths in writable paths", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config, [
"/tmp/extra1",
"/tmp/extra2",
]);
expect(mockBuildInvocation).toHaveBeenCalledWith(
expect.objectContaining({
writablePaths: ["/tmp/ws", "/tmp/extra1", "/tmp/extra2"],
}),
);
});
it("should handle empty extraWritablePaths", async () => {
const config: SandboxProcessConfig = {
...createBaseConfig(),
type: "macos-seatbelt",
};
mockBuildInvocation.mockResolvedValue({
command: "/usr/bin/sandbox-exec",
args: ["-f", "/tmp/profile.sb", "/bin/ls", "-la"],
cwd: "/tmp",
seatbeltProfilePath: "/tmp/qimingclaw-sandbox-123.sb",
});
await buildSandboxedSpawnArgs("/bin/ls", ["-la"], "/tmp", config, []);
expect(mockBuildInvocation).toHaveBeenCalledWith(
expect.objectContaining({
writablePaths: ["/tmp/ws"],
}),
);
});
});
// ---------------------------------------------------------------------------
// Test: sandboxConfig undefined (same as enabled=false)
// ---------------------------------------------------------------------------
describe("when sandboxConfig is undefined", () => {
it("should return original command and args", async () => {
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
undefined,
);
expect(result.command).toBe("/bin/ls");
expect(result.args).toEqual(["-la"]);
});
it("should return NOOP_CLEANUP", async () => {
const result = await buildSandboxedSpawnArgs(
"/bin/ls",
["-la"],
"/tmp",
undefined,
);
// cleanupSandbox should be a noop function (truthy and returns undefined)
expect(typeof result.cleanupSandbox).toBe("function");
expect(result.cleanupSandbox()).toBeUndefined();
});
});
});