
























@@ -6,6 +6,7 @@ import type { DatabaseSync } from "node:sqlite";
66import { setTimeout as scheduleNativeTimeout } from "node:timers";
77import type { Mock } from "vitest";
88import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
9+import { withMockedWindowsPlatform } from "../../../../src/test-utils/vitest-spies.js";
9101011const { logWarnMock, logDebugMock, logInfoMock } = vi.hoisted(() => ({
1112logWarnMock: vi.fn(),
@@ -1917,47 +1918,47 @@ describe("QmdMemoryManager", () => {
19171918});
1918191919191920it("resolves bare qmd command to a Windows-compatible spawn invocation", async () => {
1920-const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
1921-const previousPath = process.env.PATH;
1922-try {
1923-const nodeModulesDir = path.join(tmpRoot, "node_modules");
1924-const shimDir = path.join(nodeModulesDir, ".bin");
1925-const packageDir = path.join(nodeModulesDir, "qmd");
1926-const scriptPath = path.join(packageDir, "dist", "cli.js");
1927-await fs.mkdir(path.dirname(scriptPath), { recursive: true });
1928-await fs.mkdir(shimDir, { recursive: true });
1929-await fs.writeFile(path.join(shimDir, "qmd.cmd"), "@echo off\r\n", "utf8");
1930-await fs.writeFile(
1931-path.join(packageDir, "package.json"),
1932-JSON.stringify({ name: "qmd", version: "0.0.0", bin: { qmd: "dist/cli.js" } }),
1933-"utf8",
1934-);
1935-await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
1936-process.env.PATH = `${shimDir};${previousPath ?? ""}`;
1921+await withMockedWindowsPlatform(async () => {
1922+ const previousPath = process.env.PATH;
1923+ try {
1924+ const nodeModulesDir = path.join(tmpRoot, "node_modules");
1925+ const shimDir = path.join(nodeModulesDir, ".bin");
1926+ const packageDir = path.join(nodeModulesDir, "qmd");
1927+ const scriptPath = path.join(packageDir, "dist", "cli.js");
1928+ await fs.mkdir(path.dirname(scriptPath), { recursive: true });
1929+ await fs.mkdir(shimDir, { recursive: true });
1930+ await fs.writeFile(path.join(shimDir, "qmd.cmd"), "@echo off\r\n", "utf8");
1931+ await fs.writeFile(
1932+ path.join(packageDir, "package.json"),
1933+ JSON.stringify({ name: "qmd", version: "0.0.0", bin: { qmd: "dist/cli.js" } }),
1934+ "utf8",
1935+ );
1936+ await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
1937+ process.env.PATH = `${shimDir};${previousPath ?? ""}`;
193719381938-const { manager } = await createManager({ mode: "status" });
1939-await manager.sync({ reason: "manual" });
1939+ const { manager } = await createManager({ mode: "status" });
1940+ await manager.sync({ reason: "manual" });
194019411941-const qmdCalls = spawnMock.mock.calls.filter((call: unknown[]) => {
1942-const args = call[1] as string[] | undefined;
1943-return (
1944-Array.isArray(args) &&
1945-args.some((token) => token === "update" || token === "search" || token === "query")
1946-);
1947-});
1948-expect(qmdCalls.length).toBeGreaterThan(0);
1949-for (const call of qmdCalls) {
1950-const command = String(call[0]);
1951-const options = call[2] as { shell?: boolean } | undefined;
1952-expect(command).not.toMatch(/(^|[\\/])qmd\.cmd$/i);
1953-expect(options?.shell).not.toBe(true);
1954-}
1942+ const qmdCalls = spawnMock.mock.calls.filter((call: unknown[]) => {
1943+ const args = call[1] as string[] | undefined;
1944+ return (
1945+ Array.isArray(args) &&
1946+ args.some((token) => token === "update" || token === "search" || token === "query")
1947+ );
1948+ });
1949+ expect(qmdCalls.length).toBeGreaterThan(0);
1950+ for (const call of qmdCalls) {
1951+ const command = String(call[0]);
1952+ const options = call[2] as { shell?: boolean } | undefined;
1953+ expect(command).not.toMatch(/(^|[\\/])qmd\.cmd$/i);
1954+ expect(options?.shell).not.toBe(true);
1955+ }
195519561956-await manager.close();
1957-} finally {
1958-platformSpy.mockRestore();
1959-process.env.PATH = previousPath;
1960-}
1957+ await manager.close();
1958+ } finally {
1959+ process.env.PATH = previousPath;
1960+}
1961+});
19611962});
1962196319631964it("keeps mixed Han-script BM25 queries intact before qmd search", async () => {
@@ -3210,125 +3211,125 @@ describe("QmdMemoryManager", () => {
32103211});
3211321232123213it("resolves mcporter to a direct Windows entrypoint without enabling shell mode", async () => {
3213-const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
3214-const previousPath = process.env.PATH;
3215-try {
3216-const nodeModulesDir = path.join(tmpRoot, "node_modules");
3217-const shimDir = path.join(nodeModulesDir, ".bin");
3218-const packageDir = path.join(nodeModulesDir, "mcporter");
3219-const scriptPath = path.join(packageDir, "dist", "cli.js");
3220-await fs.mkdir(path.dirname(scriptPath), { recursive: true });
3221-await fs.mkdir(shimDir, { recursive: true });
3222-await fs.writeFile(path.join(shimDir, "mcporter.cmd"), "@echo off\r\n", "utf8");
3223-await fs.writeFile(
3224-path.join(packageDir, "package.json"),
3225-JSON.stringify({ name: "mcporter", version: "0.0.0", bin: { mcporter: "dist/cli.js" } }),
3226-"utf8",
3227-);
3228-await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
3229-process.env.PATH = `${shimDir};${previousPath ?? ""}`;
3230-3231-cfg = {
3232- ...cfg,
3233-memory: {
3234-backend: "qmd",
3235-qmd: {
3236-includeDefaultMemory: false,
3237-update: { interval: "0s", debounceMs: 60_000, onBoot: false },
3238-paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
3239-mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
3214+await withMockedWindowsPlatform(async () => {
3215+const previousPath = process.env.PATH;
3216+try {
3217+const nodeModulesDir = path.join(tmpRoot, "node_modules");
3218+const shimDir = path.join(nodeModulesDir, ".bin");
3219+const packageDir = path.join(nodeModulesDir, "mcporter");
3220+const scriptPath = path.join(packageDir, "dist", "cli.js");
3221+await fs.mkdir(path.dirname(scriptPath), { recursive: true });
3222+await fs.mkdir(shimDir, { recursive: true });
3223+await fs.writeFile(path.join(shimDir, "mcporter.cmd"), "@echo off\r\n", "utf8");
3224+await fs.writeFile(
3225+path.join(packageDir, "package.json"),
3226+JSON.stringify({ name: "mcporter", version: "0.0.0", bin: { mcporter: "dist/cli.js" } }),
3227+"utf8",
3228+);
3229+await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8");
3230+process.env.PATH = `${shimDir};${previousPath ?? ""}`;
3231+3232+cfg = {
3233+ ...cfg,
3234+memory: {
3235+backend: "qmd",
3236+qmd: {
3237+includeDefaultMemory: false,
3238+update: { interval: "0s", debounceMs: 60_000, onBoot: false },
3239+paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
3240+mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
3241+},
32403242},
3241-},
3242-} as OpenClawConfig;
3243+} as OpenClawConfig;
324332443244-spawnMock.mockImplementation((_cmd: string, args: string[]) => {
3245-const child = createMockChild({ autoClose: false });
3246-if (args[0] === "call") {
3247-emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
3245+spawnMock.mockImplementation((_cmd: string, args: string[]) => {
3246+const child = createMockChild({ autoClose: false });
3247+if (args[0] === "call") {
3248+emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
3249+return child;
3250+}
3251+emitAndClose(child, "stdout", "[]");
32483252return child;
3249-}
3250-emitAndClose(child, "stdout", "[]");
3251-return child;
3252-});
3253+});
325332543254-const { manager } = await createManager();
3255-await manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" });
3255+ const { manager } = await createManager();
3256+ await manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" });
325632573257-const mcporterCall = spawnMock.mock.calls.find((call: unknown[]) =>
3258-(call[1] as string[] | undefined)?.includes("call"),
3259-);
3260-const searchCall = requireValue(mcporterCall, "mcporter search call missing");
3261-const callCommand = searchCall[0];
3262-expect(typeof callCommand).toBe("string");
3263-const options = searchCall[2] as { shell?: boolean } | undefined;
3264-expect(callCommand).not.toBe("mcporter.cmd");
3265-expect(options?.shell).not.toBe(true);
3258+ const mcporterCall = spawnMock.mock.calls.find((call: unknown[]) =>
3259+ (call[1] as string[] | undefined)?.includes("call"),
3260+ );
3261+ const searchCall = requireValue(mcporterCall, "mcporter search call missing");
3262+ const callCommand = searchCall[0];
3263+ expect(typeof callCommand).toBe("string");
3264+ const options = searchCall[2] as { shell?: boolean } | undefined;
3265+ expect(callCommand).not.toBe("mcporter.cmd");
3266+ expect(options?.shell).not.toBe(true);
326632673267-await manager.close();
3268-} finally {
3269-platformSpy.mockRestore();
3270-process.env.PATH = previousPath;
3271-}
3268+ await manager.close();
3269+ } finally {
3270+ process.env.PATH = previousPath;
3271+}
3272+});
32723273});
3273327432743275it("fails closed on Windows EINVAL cmd-shim failures instead of retrying through the shell", async () => {
3275-const platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32");
3276-const previousPath = process.env.PATH;
3277-try {
3278-const shimDir = await fs.mkdtemp(path.join(tmpRoot, "mcporter-shim-"));
3279-await fs.writeFile(path.join(shimDir, "mcporter.cmd"), "@echo off\n");
3280-process.env.PATH = `${shimDir};${previousPath ?? ""}`;
3281-3282-cfg = {
3283- ...cfg,
3284-memory: {
3285-backend: "qmd",
3286-qmd: {
3287-includeDefaultMemory: false,
3288-update: { interval: "0s", debounceMs: 60_000, onBoot: false },
3289-paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
3290-mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
3276+await withMockedWindowsPlatform(async () => {
3277+const previousPath = process.env.PATH;
3278+try {
3279+const shimDir = await fs.mkdtemp(path.join(tmpRoot, "mcporter-shim-"));
3280+await fs.writeFile(path.join(shimDir, "mcporter.cmd"), "@echo off\n");
3281+process.env.PATH = `${shimDir};${previousPath ?? ""}`;
3282+3283+cfg = {
3284+ ...cfg,
3285+memory: {
3286+backend: "qmd",
3287+qmd: {
3288+includeDefaultMemory: false,
3289+update: { interval: "0s", debounceMs: 60_000, onBoot: false },
3290+paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
3291+mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
3292+},
32913293},
3292-},
3293-} as OpenClawConfig;
3294+} as OpenClawConfig;
329432953295-let firstCallCommand: string | null = null;
3296-spawnMock.mockImplementation((cmd: string, args: string[]) => {
3297-if (args[0] === "call" && firstCallCommand === null) {
3298-firstCallCommand = cmd;
3299-}
3300-if (args[0] === "call" && typeof cmd === "string" && cmd.toLowerCase().endsWith(".cmd")) {
3296+let firstCallCommand: string | null = null;
3297+spawnMock.mockImplementation((cmd: string, args: string[]) => {
3298+if (args[0] === "call" && firstCallCommand === null) {
3299+firstCallCommand = cmd;
3300+}
3301+if (args[0] === "call" && typeof cmd === "string" && cmd.toLowerCase().endsWith(".cmd")) {
3302+const child = createMockChild({ autoClose: false });
3303+queueMicrotask(() => {
3304+const err = Object.assign(new Error("spawn EINVAL"), { code: "EINVAL" });
3305+child.emit("error", err);
3306+});
3307+return child;
3308+}
33013309const child = createMockChild({ autoClose: false });
3302-queueMicrotask(() => {
3303-const err = Object.assign(new Error("spawn EINVAL"), { code: "EINVAL" });
3304-child.emit("error", err);
3305-});
3310+emitAndClose(child, "stdout", "[]");
33063311return child;
3307-}
3308-const child = createMockChild({ autoClose: false });
3309-emitAndClose(child, "stdout", "[]");
3310-return child;
3311-});
3312+});
331233133313-const { manager } = await createManager();
3314-await expect(
3315-manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" }),
3316-).rejects.toThrow(/without shell execution|EINVAL/);
3317-const attemptedCmdShim = (firstCallCommand ?? "").toLowerCase().endsWith(".cmd");
3318-if (attemptedCmdShim) {
3319-expect(
3320-spawnMock.mock.calls.some(
3321-(call: unknown[]) =>
3322-call[0] === "mcporter" &&
3323-(call[2] as { shell?: boolean } | undefined)?.shell === true,
3324-),
3325-).toBe(false);
3314+const { manager } = await createManager();
3315+await expect(
3316+manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" }),
3317+).rejects.toThrow(/without shell execution|EINVAL/);
3318+const attemptedCmdShim = (firstCallCommand ?? "").toLowerCase().endsWith(".cmd");
3319+if (attemptedCmdShim) {
3320+expect(
3321+spawnMock.mock.calls.some(
3322+(call: unknown[]) =>
3323+call[0] === "mcporter" &&
3324+(call[2] as { shell?: boolean } | undefined)?.shell === true,
3325+),
3326+).toBe(false);
3327+}
3328+await manager.close();
3329+} finally {
3330+process.env.PATH = previousPath;
33263331}
3327-await manager.close();
3328-} finally {
3329-platformSpy.mockRestore();
3330-process.env.PATH = previousPath;
3331-}
3332+});
33323333});
3333333433343335it("passes manager-scoped XDG env to mcporter commands", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。