Skip to content

Commit d350ac3

Browse files
committed
test: use platform spy helpers
1 parent 7ee5fe0 commit d350ac3

8 files changed

Lines changed: 277 additions & 304 deletions

File tree

extensions/memory-core/src/memory/qmd-manager.test.ts

Lines changed: 143 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { DatabaseSync } from "node:sqlite";
66
import { setTimeout as scheduleNativeTimeout } from "node:timers";
77
import type { Mock } from "vitest";
88
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
9+
import { withMockedWindowsPlatform } from "../../../../src/test-utils/vitest-spies.js";
910

1011
const { logWarnMock, logDebugMock, logInfoMock } = vi.hoisted(() => ({
1112
logWarnMock: vi.fn(),
@@ -1917,47 +1918,47 @@ describe("QmdMemoryManager", () => {
19171918
});
19181919

19191920
it("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 ?? ""}`;
19371938

1938-
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" });
19401941

1941-
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+
}
19551956

1956-
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
});
19621963

19631964
it("keeps mixed Han-script BM25 queries intact before qmd search", async () => {
@@ -3210,125 +3211,125 @@ describe("QmdMemoryManager", () => {
32103211
});
32113212

32123213
it("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;
32433244

3244-
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", "[]");
32483252
return child;
3249-
}
3250-
emitAndClose(child, "stdout", "[]");
3251-
return child;
3252-
});
3253+
});
32533254

3254-
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" });
32563257

3257-
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);
32663267

3267-
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
});
32733274

32743275
it("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;
32943295

3295-
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+
}
33013309
const 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", "[]");
33063311
return child;
3307-
}
3308-
const child = createMockChild({ autoClose: false });
3309-
emitAndClose(child, "stdout", "[]");
3310-
return child;
3311-
});
3312+
});
33123313

3313-
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
});
33333334

33343335
it("passes manager-scoped XDG env to mcporter commands", async () => {

0 commit comments

Comments
 (0)