diff --git a/src/hooks/install.test.ts b/src/hooks/install.test.ts index ca71fdf0bac..19792ca1bb7 100644 --- a/src/hooks/install.test.ts +++ b/src/hooks/install.test.ts @@ -272,57 +272,57 @@ describe("installHooksFromPath", () => { expect(fs.existsSync(path.join(result.targetDir, "HOOK.md"))).toBe(true); }); - it("rejects hook pack entries that traverse outside package directory", async () => { - const stateDir = makeTempDir(); - const workDir = makeTempDir(); - const pkgDir = path.join(workDir, "package"); - const outsideHookDir = path.join(workDir, "outside"); - fs.mkdirSync(pkgDir, { recursive: true }); - fs.mkdirSync(outsideHookDir, { recursive: true }); - writeHookPackManifest({ - pkgDir, - hooks: ["../outside"], - }); - fs.writeFileSync(path.join(outsideHookDir, "HOOK.md"), "---\nname: outside\n---\n", "utf-8"); - fs.writeFileSync(path.join(outsideHookDir, "handler.ts"), "export default async () => {};\n"); + it("rejects out-of-package hook entries", async () => { + const cases = [ + { + hooks: ["../outside"], + setupLink: false, + expected: "openclaw.hooks entry escapes package directory", + }, + { + hooks: ["./linked"], + setupLink: true, + expected: "openclaw.hooks entry resolves outside package directory", + }, + ] as const; - const result = await installHooksFromPath({ - path: pkgDir, - hooksDir: path.join(stateDir, "hooks"), - }); + for (const testCase of cases) { + const stateDir = makeTempDir(); + const workDir = makeTempDir(); + const pkgDir = path.join(workDir, "package"); + const outsideHookDir = path.join(workDir, "outside"); + const linkedDir = path.join(pkgDir, "linked"); + fs.mkdirSync(pkgDir, { recursive: true }); + fs.mkdirSync(outsideHookDir, { recursive: true }); + fs.writeFileSync(path.join(outsideHookDir, "HOOK.md"), "---\nname: outside\n---\n", "utf-8"); + fs.writeFileSync( + path.join(outsideHookDir, "handler.ts"), + "export default async () => {};\n", + "utf-8", + ); + if (testCase.setupLink) { + try { + fs.symlinkSync( + outsideHookDir, + linkedDir, + process.platform === "win32" ? "junction" : "dir", + ); + } catch { + continue; + } + } + writeHookPackManifest({ + pkgDir, + hooks: [...testCase.hooks], + }); - expectPathInstallFailureContains(result, "openclaw.hooks entry escapes package directory"); - }); + const result = await installHooksFromPath({ + path: pkgDir, + hooksDir: path.join(stateDir, "hooks"), + }); - it("rejects hook pack entries that escape via symlink", async () => { - const stateDir = makeTempDir(); - const workDir = makeTempDir(); - const pkgDir = path.join(workDir, "package"); - const outsideHookDir = path.join(workDir, "outside"); - const linkedDir = path.join(pkgDir, "linked"); - fs.mkdirSync(pkgDir, { recursive: true }); - fs.mkdirSync(outsideHookDir, { recursive: true }); - fs.writeFileSync(path.join(outsideHookDir, "HOOK.md"), "---\nname: outside\n---\n", "utf-8"); - fs.writeFileSync(path.join(outsideHookDir, "handler.ts"), "export default async () => {};\n"); - try { - fs.symlinkSync(outsideHookDir, linkedDir, process.platform === "win32" ? "junction" : "dir"); - } catch { - return; + expectPathInstallFailureContains(result, testCase.expected); } - writeHookPackManifest({ - pkgDir, - hooks: ["./linked"], - }); - - const result = await installHooksFromPath({ - path: pkgDir, - hooksDir: path.join(stateDir, "hooks"), - }); - - expectPathInstallFailureContains( - result, - "openclaw.hooks entry resolves outside package directory", - ); }); }); diff --git a/src/infra/fs-safe.test.ts b/src/infra/fs-safe.test.ts index 9227874cf8a..9783b6b4cce 100644 --- a/src/infra/fs-safe.test.ts +++ b/src/infra/fs-safe.test.ts @@ -437,7 +437,7 @@ describe("fs-safe", () => { }); describe("tilde expansion in file tools", () => { - it("expandHomePrefix respects process.env.HOME changes", async () => { + it("keeps tilde expansion behavior aligned", async () => { const { expandHomePrefix } = await import("./home-dir.js"); const originalHome = process.env.HOME; const fakeHome = path.resolve(path.sep, "tmp", "fake-home-test"); @@ -448,11 +448,8 @@ describe("tilde expansion in file tools", () => { } finally { process.env.HOME = originalHome; } - }); - it("reads a file via ~/path after HOME override", async () => { const root = await tempDirs.make("openclaw-tilde-test-"); - const originalHome = process.env.HOME; process.env.HOME = root; try { await fs.writeFile(path.join(root, "hello.txt"), "tilde-works"); @@ -464,16 +461,7 @@ describe("tilde expansion in file tools", () => { await result.handle.read(buf, 0, buf.length, 0); await result.handle.close(); expect(buf.toString("utf8")).toBe("tilde-works"); - } finally { - process.env.HOME = originalHome; - } - }); - it("writes a file via ~/path after HOME override", async () => { - const root = await tempDirs.make("openclaw-tilde-test-"); - const originalHome = process.env.HOME; - process.env.HOME = root; - try { await writeFileWithinRoot({ rootDir: root, relativePath: "~/output.txt", @@ -484,14 +472,11 @@ describe("tilde expansion in file tools", () => { } finally { process.env.HOME = originalHome; } - }); - it("rejects ~/path that resolves outside root", async () => { - const root = await tempDirs.make("openclaw-tilde-outside-"); - // HOME points to real home, ~/file goes to /home/dev/file which is outside root + const outsideRoot = await tempDirs.make("openclaw-tilde-outside-"); await expect( openFileWithinRoot({ - rootDir: root, + rootDir: outsideRoot, relativePath: "~/escape.txt", }), ).rejects.toMatchObject({ diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts index 4f7048e3bca..c70c2e52f77 100644 --- a/src/plugin-sdk/subpaths.test.ts +++ b/src/plugin-sdk/subpaths.test.ts @@ -52,7 +52,6 @@ const sourceCache = new Map(); const representativeRuntimeSmokeSubpaths = [ "channel-runtime", "conversation-runtime", - "discord", "provider-auth", "provider-setup", "setup", @@ -125,24 +124,28 @@ function expectSourceContract( describe("plugin-sdk subpath exports", () => { it("keeps the curated public list free of internal implementation subpaths", () => { - expect(pluginSdkSubpaths).not.toContain("acpx"); - expect(pluginSdkSubpaths).not.toContain("compat"); - expect(pluginSdkSubpaths).not.toContain("device-pair"); - expect(pluginSdkSubpaths).not.toContain("google"); - expect(pluginSdkSubpaths).not.toContain("lobster"); - expect(pluginSdkSubpaths).not.toContain("pairing-access"); - expect(pluginSdkSubpaths).not.toContain("qwen-portal-auth"); - expect(pluginSdkSubpaths).not.toContain("reply-prefix"); - expect(pluginSdkSubpaths).not.toContain("signal-core"); - expect(pluginSdkSubpaths).not.toContain("synology-chat"); - expect(pluginSdkSubpaths).not.toContain("typing"); - expect(pluginSdkSubpaths).not.toContain("whatsapp"); - expect(pluginSdkSubpaths).not.toContain("whatsapp-action-runtime"); - expect(pluginSdkSubpaths).not.toContain("whatsapp-login-qr"); - expect(pluginSdkSubpaths).not.toContain("secret-input-runtime"); - expect(pluginSdkSubpaths).not.toContain("secret-input-schema"); - expect(pluginSdkSubpaths).not.toContain("zai"); - expect(pluginSdkSubpaths).not.toContain("provider-model-definitions"); + for (const deniedSubpath of [ + "acpx", + "compat", + "device-pair", + "google", + "lobster", + "pairing-access", + "provider-model-definitions", + "qwen-portal-auth", + "reply-prefix", + "secret-input-runtime", + "secret-input-schema", + "signal-core", + "synology-chat", + "typing", + "whatsapp", + "whatsapp-action-runtime", + "whatsapp-login-qr", + "zai", + ]) { + expect(pluginSdkSubpaths).not.toContain(deniedSubpath); + } }); it("keeps core focused on generic shared exports", () => { @@ -568,13 +571,10 @@ describe("plugin-sdk subpath exports", () => { expectSourceMentions("testing", ["removeAckReactionAfterReply", "shouldAckReaction"]); }); - it("exports shared core types used by bundled extensions", () => { + it("keeps core shared types aligned", () => { expectTypeOf().toMatchTypeOf(); expectTypeOf().toMatchTypeOf(); expectTypeOf().toMatchTypeOf(); - }); - - it("keeps core shared types aligned with the channel prelude", () => { expectTypeOf().toMatchTypeOf(); expectTypeOf().toMatchTypeOf(); expectTypeOf().toMatchTypeOf(); diff --git a/test/scripts/committer.test.ts b/test/scripts/committer.test.ts index 623cd2e09e6..1a06733ed4c 100644 --- a/test/scripts/committer.test.ts +++ b/test/scripts/committer.test.ts @@ -57,33 +57,46 @@ afterEach(() => { }); describe("scripts/committer", () => { - it("keeps plain argv paths working", () => { - const repo = createRepo(); - writeRepoFile(repo, "alpha.txt", "alpha\n"); - writeRepoFile(repo, "nested/file with spaces.txt", "beta\n"); + it("accepts supported path argument shapes", () => { + const cases = [ + { + commitMessage: "test: plain argv", + files: [ + ["alpha.txt", "alpha\n"], + ["nested/file with spaces.txt", "beta\n"], + ] as const, + args: ["alpha.txt", "nested/file with spaces.txt"], + expected: ["alpha.txt", "nested/file with spaces.txt"], + }, + { + commitMessage: "test: space blob", + files: [ + ["alpha.txt", "alpha\n"], + ["beta.txt", "beta\n"], + ] as const, + args: ["alpha.txt beta.txt"], + expected: ["alpha.txt", "beta.txt"], + }, + { + commitMessage: "test: newline blob", + files: [ + ["alpha.txt", "alpha\n"], + ["nested/file with spaces.txt", "beta\n"], + ] as const, + args: ["alpha.txt\nnested/file with spaces.txt"], + expected: ["alpha.txt", "nested/file with spaces.txt"], + }, + ] as const; - commitWithHelper(repo, "test: plain argv", "alpha.txt", "nested/file with spaces.txt"); + for (const testCase of cases) { + const repo = createRepo(); + for (const [file, contents] of testCase.files) { + writeRepoFile(repo, file, contents); + } - expect(committedPaths(repo)).toEqual(["alpha.txt", "nested/file with spaces.txt"]); - }); + commitWithHelper(repo, testCase.commitMessage, ...testCase.args); - it("accepts a single space-delimited path blob", () => { - const repo = createRepo(); - writeRepoFile(repo, "alpha.txt", "alpha\n"); - writeRepoFile(repo, "beta.txt", "beta\n"); - - commitWithHelper(repo, "test: space blob", "alpha.txt beta.txt"); - - expect(committedPaths(repo)).toEqual(["alpha.txt", "beta.txt"]); - }); - - it("accepts a single newline-delimited path blob", () => { - const repo = createRepo(); - writeRepoFile(repo, "alpha.txt", "alpha\n"); - writeRepoFile(repo, "nested/file with spaces.txt", "beta\n"); - - commitWithHelper(repo, "test: newline blob", "alpha.txt\nnested/file with spaces.txt"); - - expect(committedPaths(repo)).toEqual(["alpha.txt", "nested/file with spaces.txt"]); + expect(committedPaths(repo)).toEqual(testCase.expected); + } }); });