mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:52:25 +00:00
test: collapse utility plugin suites
This commit is contained in:
@@ -1,118 +0,0 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
const cliMocks = vi.hoisted(() => ({
|
||||
runOpenShellCli: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./cli.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./cli.js")>();
|
||||
return {
|
||||
...actual,
|
||||
runOpenShellCli: cliMocks.runOpenShellCli,
|
||||
};
|
||||
});
|
||||
|
||||
import { createOpenShellSandboxBackendManager } from "./backend.js";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
describe("openshell backend manager", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("checks runtime status with config override from OpenClaw config", async () => {
|
||||
cliMocks.runOpenShellCli.mockResolvedValue({
|
||||
code: 0,
|
||||
stdout: "{}",
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
const manager = createOpenShellSandboxBackendManager({
|
||||
pluginConfig: resolveOpenShellPluginConfig({
|
||||
command: "openshell",
|
||||
from: "openclaw",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await manager.describeRuntime({
|
||||
entry: {
|
||||
containerName: "openclaw-session-1234",
|
||||
backendId: "openshell",
|
||||
runtimeLabel: "openclaw-session-1234",
|
||||
sessionKey: "agent:main",
|
||||
createdAtMs: 1,
|
||||
lastUsedAtMs: 1,
|
||||
image: "custom-source",
|
||||
configLabelKind: "Source",
|
||||
},
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
command: "openshell",
|
||||
from: "custom-source",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
running: true,
|
||||
actualConfigLabel: "custom-source",
|
||||
configLabelMatch: true,
|
||||
});
|
||||
expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
|
||||
context: expect.objectContaining({
|
||||
sandboxName: "openclaw-session-1234",
|
||||
config: expect.objectContaining({
|
||||
from: "custom-source",
|
||||
}),
|
||||
}),
|
||||
args: ["sandbox", "get", "openclaw-session-1234"],
|
||||
});
|
||||
});
|
||||
|
||||
it("removes runtimes via openshell sandbox delete", async () => {
|
||||
cliMocks.runOpenShellCli.mockResolvedValue({
|
||||
code: 0,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
const manager = createOpenShellSandboxBackendManager({
|
||||
pluginConfig: resolveOpenShellPluginConfig({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
}),
|
||||
});
|
||||
|
||||
await manager.removeRuntime({
|
||||
entry: {
|
||||
containerName: "openclaw-session-5678",
|
||||
backendId: "openshell",
|
||||
runtimeLabel: "openclaw-session-5678",
|
||||
sessionKey: "agent:main",
|
||||
createdAtMs: 1,
|
||||
lastUsedAtMs: 1,
|
||||
image: "openclaw",
|
||||
configLabelKind: "Source",
|
||||
},
|
||||
config: {},
|
||||
});
|
||||
|
||||
expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
|
||||
context: expect.objectContaining({
|
||||
sandboxName: "openclaw-session-5678",
|
||||
config: expect.objectContaining({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
}),
|
||||
}),
|
||||
args: ["sandbox", "delete", "openclaw-session-5678"],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,61 +0,0 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildExecRemoteCommand,
|
||||
buildOpenShellBaseArgv,
|
||||
resolveOpenShellCommand,
|
||||
setBundledOpenShellCommandResolverForTest,
|
||||
shellEscape,
|
||||
} from "./cli.js";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
describe("openshell cli helpers", () => {
|
||||
afterEach(() => {
|
||||
setBundledOpenShellCommandResolverForTest();
|
||||
});
|
||||
|
||||
it("builds base argv with gateway overrides", () => {
|
||||
const config = resolveOpenShellPluginConfig({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
gatewayEndpoint: "https://lab.example",
|
||||
});
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual([
|
||||
"/usr/local/bin/openshell",
|
||||
"--gateway",
|
||||
"lab",
|
||||
"--gateway-endpoint",
|
||||
"https://lab.example",
|
||||
]);
|
||||
});
|
||||
|
||||
it("prefers the bundled openshell command when available", () => {
|
||||
setBundledOpenShellCommandResolverForTest(() => "/tmp/node_modules/.bin/openshell");
|
||||
const config = resolveOpenShellPluginConfig(undefined);
|
||||
|
||||
expect(resolveOpenShellCommand("openshell")).toBe("/tmp/node_modules/.bin/openshell");
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual(["/tmp/node_modules/.bin/openshell"]);
|
||||
});
|
||||
|
||||
it("falls back to the PATH command when no bundled openshell is present", () => {
|
||||
setBundledOpenShellCommandResolverForTest(() => null);
|
||||
|
||||
expect(resolveOpenShellCommand("openshell")).toBe("openshell");
|
||||
});
|
||||
|
||||
it("shell escapes single quotes", () => {
|
||||
expect(shellEscape(`a'b`)).toBe(`'a'"'"'b'`);
|
||||
});
|
||||
|
||||
it("wraps exec commands with env and workdir", () => {
|
||||
const command = buildExecRemoteCommand({
|
||||
command: "pwd && printenv TOKEN",
|
||||
workdir: "/sandbox/project",
|
||||
env: {
|
||||
TOKEN: "abc 123",
|
||||
},
|
||||
});
|
||||
expect(command).toContain(`'env'`);
|
||||
expect(command).toContain(`'TOKEN=abc 123'`);
|
||||
expect(command).toContain(`'cd '"'"'/sandbox/project'"'"' && pwd && printenv TOKEN'`);
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
describe("openshell plugin config", () => {
|
||||
it("applies defaults", () => {
|
||||
expect(resolveOpenShellPluginConfig(undefined)).toEqual({
|
||||
mode: "mirror",
|
||||
command: "openshell",
|
||||
gateway: undefined,
|
||||
gatewayEndpoint: undefined,
|
||||
from: "openclaw",
|
||||
policy: undefined,
|
||||
providers: [],
|
||||
gpu: false,
|
||||
autoProviders: true,
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
timeoutMs: 120_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts remote mode", () => {
|
||||
expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
|
||||
});
|
||||
|
||||
it("rejects relative remote paths", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "sandbox",
|
||||
}),
|
||||
).toThrow("OpenShell remote path must be absolute");
|
||||
});
|
||||
|
||||
it("rejects unknown mode", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
mode: "bogus",
|
||||
}),
|
||||
).toThrow("mode must be one of mirror, remote");
|
||||
});
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createSandboxTestContext } from "../../../src/agents/sandbox/test-fixtures.js";
|
||||
import type { OpenShellSandboxBackend } from "./backend.js";
|
||||
import { createOpenShellFsBridge } from "./fs-bridge.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
async function makeTempDir() {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-openshell-fs-"));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
function createBackendMock(): OpenShellSandboxBackend {
|
||||
return {
|
||||
id: "openshell",
|
||||
runtimeId: "openshell-test",
|
||||
runtimeLabel: "openshell-test",
|
||||
workdir: "/sandbox",
|
||||
env: {},
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
buildExecSpec: vi.fn(),
|
||||
runShellCommand: vi.fn(),
|
||||
runRemoteShellScript: vi.fn().mockResolvedValue({
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.alloc(0),
|
||||
code: 0,
|
||||
}),
|
||||
syncLocalPathToRemote: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as OpenShellSandboxBackend;
|
||||
}
|
||||
|
||||
describe("openshell fs bridge", () => {
|
||||
it("writes locally and syncs the file to the remote workspace", async () => {
|
||||
const workspaceDir = await makeTempDir();
|
||||
const backend = createBackendMock();
|
||||
const sandbox = createSandboxTestContext({
|
||||
overrides: {
|
||||
backendId: "openshell",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir: workspaceDir,
|
||||
containerWorkdir: "/sandbox",
|
||||
},
|
||||
});
|
||||
|
||||
const bridge = createOpenShellFsBridge({ sandbox, backend });
|
||||
await bridge.writeFile({
|
||||
filePath: "nested/file.txt",
|
||||
data: "hello",
|
||||
mkdir: true,
|
||||
});
|
||||
|
||||
expect(await fs.readFile(path.join(workspaceDir, "nested", "file.txt"), "utf8")).toBe("hello");
|
||||
expect(backend.syncLocalPathToRemote).toHaveBeenCalledWith(
|
||||
path.join(workspaceDir, "nested", "file.txt"),
|
||||
"/sandbox/nested/file.txt",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps agent mount paths when the sandbox workspace is read-only", async () => {
|
||||
const workspaceDir = await makeTempDir();
|
||||
const agentWorkspaceDir = await makeTempDir();
|
||||
await fs.writeFile(path.join(agentWorkspaceDir, "note.txt"), "agent", "utf8");
|
||||
const backend = createBackendMock();
|
||||
const sandbox = createSandboxTestContext({
|
||||
overrides: {
|
||||
backendId: "openshell",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir,
|
||||
workspaceAccess: "ro",
|
||||
containerWorkdir: "/sandbox",
|
||||
},
|
||||
});
|
||||
|
||||
const bridge = createOpenShellFsBridge({ sandbox, backend });
|
||||
const resolved = bridge.resolvePath({ filePath: "/agent/note.txt" });
|
||||
expect(resolved.hostPath).toBe(path.join(agentWorkspaceDir, "note.txt"));
|
||||
expect(await bridge.readFile({ filePath: "/agent/note.txt" })).toEqual(Buffer.from("agent"));
|
||||
});
|
||||
});
|
||||
@@ -2,10 +2,232 @@ import fsSync from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createSandboxTestContext } from "../../../src/agents/sandbox/test-fixtures.js";
|
||||
import type { OpenShellSandboxBackend } from "./backend.js";
|
||||
import { createOpenShellRemoteFsBridge } from "./remote-fs-bridge.js";
|
||||
import {
|
||||
buildExecRemoteCommand,
|
||||
buildOpenShellBaseArgv,
|
||||
resolveOpenShellCommand,
|
||||
setBundledOpenShellCommandResolverForTest,
|
||||
shellEscape,
|
||||
} from "./cli.js";
|
||||
import { resolveOpenShellPluginConfig } from "./config.js";
|
||||
|
||||
const cliMocks = vi.hoisted(() => ({
|
||||
runOpenShellCli: vi.fn(),
|
||||
}));
|
||||
|
||||
let createOpenShellSandboxBackendManager: typeof import("./backend.js").createOpenShellSandboxBackendManager;
|
||||
|
||||
describe("openshell plugin config", () => {
|
||||
it("applies defaults", () => {
|
||||
expect(resolveOpenShellPluginConfig(undefined)).toEqual({
|
||||
mode: "mirror",
|
||||
command: "openshell",
|
||||
gateway: undefined,
|
||||
gatewayEndpoint: undefined,
|
||||
from: "openclaw",
|
||||
policy: undefined,
|
||||
providers: [],
|
||||
gpu: false,
|
||||
autoProviders: true,
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
timeoutMs: 120_000,
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts remote mode", () => {
|
||||
expect(resolveOpenShellPluginConfig({ mode: "remote" }).mode).toBe("remote");
|
||||
});
|
||||
|
||||
it("rejects relative remote paths", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
remoteWorkspaceDir: "sandbox",
|
||||
}),
|
||||
).toThrow("OpenShell remote path must be absolute");
|
||||
});
|
||||
|
||||
it("rejects unknown mode", () => {
|
||||
expect(() =>
|
||||
resolveOpenShellPluginConfig({
|
||||
mode: "bogus",
|
||||
}),
|
||||
).toThrow("mode must be one of mirror, remote");
|
||||
});
|
||||
});
|
||||
|
||||
describe("openshell cli helpers", () => {
|
||||
afterEach(() => {
|
||||
setBundledOpenShellCommandResolverForTest();
|
||||
});
|
||||
|
||||
it("builds base argv with gateway overrides", () => {
|
||||
const config = resolveOpenShellPluginConfig({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
gatewayEndpoint: "https://lab.example",
|
||||
});
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual([
|
||||
"/usr/local/bin/openshell",
|
||||
"--gateway",
|
||||
"lab",
|
||||
"--gateway-endpoint",
|
||||
"https://lab.example",
|
||||
]);
|
||||
});
|
||||
|
||||
it("prefers the bundled openshell command when available", () => {
|
||||
setBundledOpenShellCommandResolverForTest(() => "/tmp/node_modules/.bin/openshell");
|
||||
const config = resolveOpenShellPluginConfig(undefined);
|
||||
|
||||
expect(resolveOpenShellCommand("openshell")).toBe("/tmp/node_modules/.bin/openshell");
|
||||
expect(buildOpenShellBaseArgv(config)).toEqual(["/tmp/node_modules/.bin/openshell"]);
|
||||
});
|
||||
|
||||
it("falls back to the PATH command when no bundled openshell is present", () => {
|
||||
setBundledOpenShellCommandResolverForTest(() => null);
|
||||
|
||||
expect(resolveOpenShellCommand("openshell")).toBe("openshell");
|
||||
});
|
||||
|
||||
it("shell escapes single quotes", () => {
|
||||
expect(shellEscape(`a'b`)).toBe(`'a'"'"'b'`);
|
||||
});
|
||||
|
||||
it("wraps exec commands with env and workdir", () => {
|
||||
const command = buildExecRemoteCommand({
|
||||
command: "pwd && printenv TOKEN",
|
||||
workdir: "/sandbox/project",
|
||||
env: {
|
||||
TOKEN: "abc 123",
|
||||
},
|
||||
});
|
||||
expect(command).toContain(`'env'`);
|
||||
expect(command).toContain(`'TOKEN=abc 123'`);
|
||||
expect(command).toContain(`'cd '"'"'/sandbox/project'"'"' && pwd && printenv TOKEN'`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("openshell backend manager", () => {
|
||||
beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("./cli.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("./cli.js")>();
|
||||
return {
|
||||
...actual,
|
||||
runOpenShellCli: cliMocks.runOpenShellCli,
|
||||
};
|
||||
});
|
||||
({ createOpenShellSandboxBackendManager } = await import("./backend.js"));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.doUnmock("./cli.js");
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("checks runtime status with config override from OpenClaw config", async () => {
|
||||
cliMocks.runOpenShellCli.mockResolvedValue({
|
||||
code: 0,
|
||||
stdout: "{}",
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
const manager = createOpenShellSandboxBackendManager({
|
||||
pluginConfig: resolveOpenShellPluginConfig({
|
||||
command: "openshell",
|
||||
from: "openclaw",
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await manager.describeRuntime({
|
||||
entry: {
|
||||
containerName: "openclaw-session-1234",
|
||||
backendId: "openshell",
|
||||
runtimeLabel: "openclaw-session-1234",
|
||||
sessionKey: "agent:main",
|
||||
createdAtMs: 1,
|
||||
lastUsedAtMs: 1,
|
||||
image: "custom-source",
|
||||
configLabelKind: "Source",
|
||||
},
|
||||
config: {
|
||||
plugins: {
|
||||
entries: {
|
||||
openshell: {
|
||||
enabled: true,
|
||||
config: {
|
||||
command: "openshell",
|
||||
from: "custom-source",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
running: true,
|
||||
actualConfigLabel: "custom-source",
|
||||
configLabelMatch: true,
|
||||
});
|
||||
expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
|
||||
context: expect.objectContaining({
|
||||
sandboxName: "openclaw-session-1234",
|
||||
config: expect.objectContaining({
|
||||
from: "custom-source",
|
||||
}),
|
||||
}),
|
||||
args: ["sandbox", "get", "openclaw-session-1234"],
|
||||
});
|
||||
});
|
||||
|
||||
it("removes runtimes via openshell sandbox delete", async () => {
|
||||
cliMocks.runOpenShellCli.mockResolvedValue({
|
||||
code: 0,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
const manager = createOpenShellSandboxBackendManager({
|
||||
pluginConfig: resolveOpenShellPluginConfig({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
}),
|
||||
});
|
||||
|
||||
await manager.removeRuntime({
|
||||
entry: {
|
||||
containerName: "openclaw-session-5678",
|
||||
backendId: "openshell",
|
||||
runtimeLabel: "openclaw-session-5678",
|
||||
sessionKey: "agent:main",
|
||||
createdAtMs: 1,
|
||||
lastUsedAtMs: 1,
|
||||
image: "openclaw",
|
||||
configLabelKind: "Source",
|
||||
},
|
||||
config: {},
|
||||
});
|
||||
|
||||
expect(cliMocks.runOpenShellCli).toHaveBeenCalledWith({
|
||||
context: expect.objectContaining({
|
||||
sandboxName: "openclaw-session-5678",
|
||||
config: expect.objectContaining({
|
||||
command: "/usr/local/bin/openshell",
|
||||
gateway: "lab",
|
||||
}),
|
||||
}),
|
||||
args: ["sandbox", "delete", "openclaw-session-5678"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
@@ -19,6 +241,26 @@ afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
|
||||
function createMirrorBackendMock(): OpenShellSandboxBackend {
|
||||
return {
|
||||
id: "openshell",
|
||||
runtimeId: "openshell-test",
|
||||
runtimeLabel: "openshell-test",
|
||||
workdir: "/sandbox",
|
||||
env: {},
|
||||
remoteWorkspaceDir: "/sandbox",
|
||||
remoteAgentWorkspaceDir: "/agent",
|
||||
buildExecSpec: vi.fn(),
|
||||
runShellCommand: vi.fn(),
|
||||
runRemoteShellScript: vi.fn().mockResolvedValue({
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.alloc(0),
|
||||
code: 0,
|
||||
}),
|
||||
syncLocalPathToRemote: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as OpenShellSandboxBackend;
|
||||
}
|
||||
|
||||
function translateRemotePath(value: string, roots: { workspace: string; agent: string }) {
|
||||
if (value === "/sandbox" || value.startsWith("/sandbox/")) {
|
||||
return path.join(roots.workspace, value.slice("/sandbox".length));
|
||||
@@ -55,7 +297,10 @@ async function runLocalShell(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function createBackendMock(roots: { workspace: string; agent: string }): OpenShellSandboxBackend {
|
||||
function createRemoteBackendMock(roots: {
|
||||
workspace: string;
|
||||
agent: string;
|
||||
}): OpenShellSandboxBackend {
|
||||
return {
|
||||
id: "openshell",
|
||||
runtimeId: "openshell-test",
|
||||
@@ -226,14 +471,63 @@ async function applyMutation(args: string[], stdin?: Buffer) {
|
||||
throw new Error(`unknown mutation operation: ${operation}`);
|
||||
}
|
||||
|
||||
describe("openshell remote fs bridge", () => {
|
||||
describe("openshell fs bridges", () => {
|
||||
it("writes locally and syncs the file to the remote workspace", async () => {
|
||||
const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
|
||||
const backend = createMirrorBackendMock();
|
||||
const sandbox = createSandboxTestContext({
|
||||
overrides: {
|
||||
backendId: "openshell",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir: workspaceDir,
|
||||
containerWorkdir: "/sandbox",
|
||||
},
|
||||
});
|
||||
|
||||
const { createOpenShellFsBridge } = await import("./fs-bridge.js");
|
||||
const bridge = createOpenShellFsBridge({ sandbox, backend });
|
||||
await bridge.writeFile({
|
||||
filePath: "nested/file.txt",
|
||||
data: "hello",
|
||||
mkdir: true,
|
||||
});
|
||||
|
||||
expect(await fs.readFile(path.join(workspaceDir, "nested", "file.txt"), "utf8")).toBe("hello");
|
||||
expect(backend.syncLocalPathToRemote).toHaveBeenCalledWith(
|
||||
path.join(workspaceDir, "nested", "file.txt"),
|
||||
"/sandbox/nested/file.txt",
|
||||
);
|
||||
});
|
||||
|
||||
it("maps agent mount paths when the sandbox workspace is read-only", async () => {
|
||||
const workspaceDir = await makeTempDir("openclaw-openshell-fs-");
|
||||
const agentWorkspaceDir = await makeTempDir("openclaw-openshell-agent-");
|
||||
await fs.writeFile(path.join(agentWorkspaceDir, "note.txt"), "agent", "utf8");
|
||||
const backend = createMirrorBackendMock();
|
||||
const sandbox = createSandboxTestContext({
|
||||
overrides: {
|
||||
backendId: "openshell",
|
||||
workspaceDir,
|
||||
agentWorkspaceDir,
|
||||
workspaceAccess: "ro",
|
||||
containerWorkdir: "/sandbox",
|
||||
},
|
||||
});
|
||||
|
||||
const { createOpenShellFsBridge } = await import("./fs-bridge.js");
|
||||
const bridge = createOpenShellFsBridge({ sandbox, backend });
|
||||
const resolved = bridge.resolvePath({ filePath: "/agent/note.txt" });
|
||||
expect(resolved.hostPath).toBe(path.join(agentWorkspaceDir, "note.txt"));
|
||||
expect(await bridge.readFile({ filePath: "/agent/note.txt" })).toEqual(Buffer.from("agent"));
|
||||
});
|
||||
|
||||
it("writes, reads, renames, and removes files without local host paths", async () => {
|
||||
const workspaceDir = await makeTempDir("openclaw-openshell-remote-local-");
|
||||
const remoteWorkspaceDir = await makeTempDir("openclaw-openshell-remote-workspace-");
|
||||
const remoteAgentDir = await makeTempDir("openclaw-openshell-remote-agent-");
|
||||
const remoteWorkspaceRealDir = await fs.realpath(remoteWorkspaceDir);
|
||||
const remoteAgentRealDir = await fs.realpath(remoteAgentDir);
|
||||
const backend = createBackendMock({
|
||||
const backend = createRemoteBackendMock({
|
||||
workspace: remoteWorkspaceRealDir,
|
||||
agent: remoteAgentRealDir,
|
||||
});
|
||||
@@ -246,6 +540,7 @@ describe("openshell remote fs bridge", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { createOpenShellRemoteFsBridge } = await import("./remote-fs-bridge.js");
|
||||
const bridge = createOpenShellRemoteFsBridge({ sandbox, backend });
|
||||
await bridge.writeFile({
|
||||
filePath: "nested/file.txt",
|
||||
Reference in New Issue
Block a user