From f3c37a946cc49d26e5fe65a1043f7286a86aa4a4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 24 Apr 2026 11:30:43 +0100 Subject: [PATCH] test: fold sandbox skill sync coverage --- src/agents/sandbox-skills.test.ts | 94 ------------------- .../sandbox.resolveSandboxContext.test.ts | 71 +++++++++++++- 2 files changed, 70 insertions(+), 95 deletions(-) delete mode 100644 src/agents/sandbox-skills.test.ts diff --git a/src/agents/sandbox-skills.test.ts b/src/agents/sandbox-skills.test.ts deleted file mode 100644 index 8074b9cd2a9..00000000000 --- a/src/agents/sandbox-skills.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import type { OpenClawConfig } from "../config/config.js"; -import { captureEnv } from "../test-utils/env.js"; -import { resolveSandboxContext } from "./sandbox/context.js"; -import { writeSkill } from "./skills.e2e-test-helpers.js"; - -vi.mock("./sandbox/docker.js", () => ({ - ensureSandboxContainer: vi.fn(async () => "openclaw-sbx-test"), -})); - -vi.mock("./sandbox/browser.js", () => ({ - ensureSandboxBrowser: vi.fn(async () => null), -})); - -vi.mock("./sandbox/prune.js", () => ({ - maybePruneSandboxes: vi.fn(async () => undefined), -})); - -vi.mock("./sandbox/registry.js", () => ({ - updateRegistry: vi.fn(async () => undefined), -})); - -describe("sandbox skill mirroring", () => { - let envSnapshot: ReturnType; - let tempRoot = ""; - - beforeAll(async () => { - tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sandbox-skills-")); - }); - - beforeEach(() => { - envSnapshot = captureEnv(["OPENCLAW_BUNDLED_SKILLS_DIR"]); - }); - - afterEach(() => { - envSnapshot.restore(); - }); - - afterAll(async () => { - if (tempRoot) { - await fs.rm(tempRoot, { recursive: true, force: true }); - } - }); - - const runContext = async (workspaceAccess: "none" | "ro") => { - const bundledDir = await fs.mkdtemp(path.join(tempRoot, "bundled-")); - await fs.mkdir(bundledDir, { recursive: true }); - - process.env.OPENCLAW_BUNDLED_SKILLS_DIR = bundledDir; - - const workspaceDir = await fs.mkdtemp(path.join(tempRoot, "workspace-")); - await writeSkill({ - dir: path.join(workspaceDir, "skills", "demo-skill"), - name: "demo-skill", - description: "Demo skill", - }); - - const cfg: OpenClawConfig = { - agents: { - defaults: { - sandbox: { - mode: "all", - scope: "session", - workspaceAccess, - workspaceRoot: path.join(bundledDir, "sandboxes"), - }, - }, - }, - }; - - const context = await resolveSandboxContext({ - config: cfg, - sessionKey: "agent:main:main", - workspaceDir, - }); - - return { context, workspaceDir }; - }; - - it.each(["ro", "none"] as const)( - "copies skills into the sandbox when workspaceAccess is %s", - async (workspaceAccess) => { - const { context } = await runContext(workspaceAccess); - - expect(context?.enabled).toBe(true); - const skillPath = path.join(context?.workspaceDir ?? "", "skills", "demo-skill", "SKILL.md"); - await expect(fs.readFile(skillPath, "utf-8")).resolves.toContain("demo-skill"); - }, - 20_000, - ); -}); diff --git a/src/agents/sandbox.resolveSandboxContext.test.ts b/src/agents/sandbox.resolveSandboxContext.test.ts index 3e4b4e699bd..24ddf9236cb 100644 --- a/src/agents/sandbox.resolveSandboxContext.test.ts +++ b/src/agents/sandbox.resolveSandboxContext.test.ts @@ -1,14 +1,47 @@ -import { describe, expect, it, vi } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { registerSandboxBackend } from "./sandbox/backend.js"; import { ensureSandboxWorkspaceForSession, resolveSandboxContext } from "./sandbox/context.js"; const updateRegistryMock = vi.hoisted(() => vi.fn()); +const syncSkillsToWorkspaceMock = vi.hoisted(() => vi.fn(async () => undefined)); vi.mock("./sandbox/registry.js", () => ({ updateRegistry: updateRegistryMock, })); +vi.mock("../infra/skills-remote.js", () => ({ + getRemoteSkillEligibility: vi.fn(() => ({ note: "test-remote" })), +})); + +vi.mock("./exec-defaults.js", () => ({ + canExecRequestNode: vi.fn(() => false), +})); + +vi.mock("./skills.js", () => ({ + syncSkillsToWorkspace: syncSkillsToWorkspaceMock, +})); + +let sandboxFixtureRoot = ""; +let sandboxFixtureCount = 0; + +async function createSandboxFixtureDir(prefix: string): Promise { + const dir = path.join(sandboxFixtureRoot, `${prefix}-${sandboxFixtureCount++}`); + await fs.mkdir(dir, { recursive: true }); + return dir; +} + +beforeAll(async () => { + sandboxFixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sandbox-context-")); +}); + +afterAll(async () => { + await fs.rm(sandboxFixtureRoot, { recursive: true, force: true }); +}); + describe("resolveSandboxContext", () => { it("does not sandbox the agent main session in non-main mode", async () => { const cfg: OpenClawConfig = { @@ -138,4 +171,40 @@ describe("resolveSandboxContext", () => { restore(); } }, 15_000); + + it("requests skill sync for read-only sandbox workspaces", async () => { + syncSkillsToWorkspaceMock.mockClear(); + const bundledDir = await createSandboxFixtureDir("bundled"); + const workspaceDir = await createSandboxFixtureDir("workspace"); + + const cfg: OpenClawConfig = { + agents: { + defaults: { + sandbox: { + mode: "all", + scope: "session", + workspaceAccess: "ro", + workspaceRoot: path.join(bundledDir, "sandboxes"), + }, + }, + }, + }; + + const result = await ensureSandboxWorkspaceForSession({ + config: cfg, + sessionKey: "agent:main:main", + workspaceDir, + }); + + expect(result).not.toBeNull(); + expect(syncSkillsToWorkspaceMock).toHaveBeenCalledWith( + expect.objectContaining({ + sourceWorkspaceDir: workspaceDir, + targetWorkspaceDir: result?.workspaceDir, + config: cfg, + agentId: "main", + eligibility: { remote: { note: "test-remote" } }, + }), + ); + }, 15_000); });