From 59657913fd3be0b5388be269fdbd84cb6819cf34 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 20 Apr 2026 20:05:37 +0100 Subject: [PATCH] perf(test): prune duplicate memory host tests --- .../host/batch-error-utils.test.ts | 32 --- src/memory-host-sdk/host/qmd-process.test.ts | 154 ----------- .../host/query-expansion.test.ts | 244 ------------------ 3 files changed, 430 deletions(-) delete mode 100644 src/memory-host-sdk/host/batch-error-utils.test.ts delete mode 100644 src/memory-host-sdk/host/qmd-process.test.ts delete mode 100644 src/memory-host-sdk/host/query-expansion.test.ts diff --git a/src/memory-host-sdk/host/batch-error-utils.test.ts b/src/memory-host-sdk/host/batch-error-utils.test.ts deleted file mode 100644 index c92c9cbac39..00000000000 --- a/src/memory-host-sdk/host/batch-error-utils.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { extractBatchErrorMessage, formatUnavailableBatchError } from "./batch-error-utils.js"; - -describe("extractBatchErrorMessage", () => { - it("returns the first top-level error message", () => { - expect( - extractBatchErrorMessage([ - { response: { body: { error: { message: "nested" } } } }, - { error: { message: "top-level" } }, - ]), - ).toBe("nested"); - }); - - it("falls back to nested response error message", () => { - expect( - extractBatchErrorMessage([{ response: { body: { error: { message: "nested-only" } } } }, {}]), - ).toBe("nested-only"); - }); - - it("accepts plain string response bodies", () => { - expect(extractBatchErrorMessage([{ response: { body: "provider plain-text error" } }])).toBe( - "provider plain-text error", - ); - }); -}); - -describe("formatUnavailableBatchError", () => { - it("formats errors and non-error values", () => { - expect(formatUnavailableBatchError(new Error("boom"))).toBe("error file unavailable: boom"); - expect(formatUnavailableBatchError("unreachable")).toBe("error file unavailable: unreachable"); - }); -}); diff --git a/src/memory-host-sdk/host/qmd-process.test.ts b/src/memory-host-sdk/host/qmd-process.test.ts deleted file mode 100644 index f706f525af3..00000000000 --- a/src/memory-host-sdk/host/qmd-process.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { EventEmitter } from "node:events"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -const spawnMock = vi.hoisted(() => vi.fn()); - -vi.mock("node:child_process", async () => { - const actual = await vi.importActual("node:child_process"); - return { - ...actual, - spawn: spawnMock, - }; -}); - -import { checkQmdBinaryAvailability, resolveCliSpawnInvocation } from "./qmd-process.js"; - -function createMockChild() { - const child = new EventEmitter() as EventEmitter & { - kill: ReturnType; - }; - child.kill = vi.fn(); - return child; -} - -let tempDir = ""; -let platformSpy: { mockRestore(): void } | null = null; -const originalPath = process.env.PATH; -const originalPathExt = process.env.PATHEXT; - -beforeEach(async () => { - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-qmd-win-spawn-")); - platformSpy = vi.spyOn(process, "platform", "get").mockReturnValue("win32"); -}); - -afterEach(async () => { - platformSpy?.mockRestore(); - process.env.PATH = originalPath; - process.env.PATHEXT = originalPathExt; - spawnMock.mockReset(); - if (tempDir) { - await fs.rm(tempDir, { recursive: true, force: true }); - tempDir = ""; - } -}); - -describe("resolveCliSpawnInvocation", () => { - it("unwraps npm cmd shims to a direct node entrypoint", async () => { - const binDir = path.join(tempDir, "node_modules", ".bin"); - const packageDir = path.join(tempDir, "node_modules", "qmd"); - const scriptPath = path.join(packageDir, "dist", "cli.js"); - await fs.mkdir(path.dirname(scriptPath), { recursive: true }); - await fs.mkdir(binDir, { recursive: true }); - await fs.writeFile(path.join(binDir, "qmd.cmd"), "@echo off\r\n", "utf8"); - await fs.writeFile( - path.join(packageDir, "package.json"), - JSON.stringify({ name: "qmd", version: "0.0.0", bin: { qmd: "dist/cli.js" } }), - "utf8", - ); - await fs.writeFile(scriptPath, "module.exports = {};\n", "utf8"); - - process.env.PATH = `${binDir};${originalPath ?? ""}`; - process.env.PATHEXT = ".CMD;.EXE"; - - const invocation = resolveCliSpawnInvocation({ - command: "qmd", - args: ["query", "hello"], - env: process.env, - packageName: "qmd", - }); - - expect(invocation.command).toBe(process.execPath); - expect(invocation.argv).toEqual([scriptPath, "query", "hello"]); - expect(invocation.shell).not.toBe(true); - expect(invocation.windowsHide).toBe(true); - }); - - it("fails closed when a Windows cmd shim cannot be resolved without shell execution", async () => { - const binDir = path.join(tempDir, "bad-bin"); - await fs.mkdir(binDir, { recursive: true }); - await fs.writeFile(path.join(binDir, "qmd.cmd"), "@echo off\r\nREM no entrypoint\r\n", "utf8"); - - process.env.PATH = `${binDir};${originalPath ?? ""}`; - process.env.PATHEXT = ".CMD;.EXE"; - - expect(() => - resolveCliSpawnInvocation({ - command: "qmd", - args: ["query", "hello"], - env: process.env, - packageName: "qmd", - }), - ).toThrow(/without shell execution/); - }); - - it("keeps bare commands bare when no Windows wrapper exists on PATH", () => { - process.env.PATH = originalPath ?? ""; - process.env.PATHEXT = ".CMD;.EXE"; - - const invocation = resolveCliSpawnInvocation({ - command: "qmd", - args: ["query", "hello"], - env: process.env, - packageName: "qmd", - }); - - expect(invocation.command).toBe("qmd"); - expect(invocation.argv).toEqual(["query", "hello"]); - expect(invocation.shell).not.toBe(true); - }); -}); - -describe("checkQmdBinaryAvailability", () => { - it("returns available when the qmd process spawns successfully", async () => { - const child = createMockChild(); - spawnMock.mockImplementationOnce(() => { - queueMicrotask(() => child.emit("spawn")); - return child; - }); - - await expect( - checkQmdBinaryAvailability({ command: "qmd", env: process.env, cwd: tempDir }), - ).resolves.toEqual({ available: true }); - expect(child.kill).toHaveBeenCalled(); - }); - - it("returns unavailable when the qmd process cannot be spawned", async () => { - const child = createMockChild(); - const err = Object.assign(new Error("spawn qmd ENOENT"), { code: "ENOENT" }); - spawnMock.mockImplementationOnce(() => { - queueMicrotask(() => child.emit("error", err)); - return child; - }); - - await expect( - checkQmdBinaryAvailability({ command: "qmd", env: process.env, cwd: tempDir }), - ).resolves.toEqual({ available: false, error: "spawn qmd ENOENT" }); - }); - - it("does not treat close-before-spawn as a successful availability probe", async () => { - const child = createMockChild(); - const err = Object.assign(new Error("spawn qmd ENOENT"), { code: "ENOENT" }); - spawnMock.mockImplementationOnce(() => { - queueMicrotask(() => child.emit("close")); - queueMicrotask(() => child.emit("error", err)); - return child; - }); - - await expect( - checkQmdBinaryAvailability({ command: "qmd", env: process.env, cwd: tempDir }), - ).resolves.toEqual({ available: false, error: "spawn qmd ENOENT" }); - }); -}); diff --git a/src/memory-host-sdk/host/query-expansion.test.ts b/src/memory-host-sdk/host/query-expansion.test.ts deleted file mode 100644 index f1e9bff520e..00000000000 --- a/src/memory-host-sdk/host/query-expansion.test.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { expandQueryForFts, extractKeywords } from "./query-expansion.js"; - -describe("extractKeywords", () => { - it("extracts keywords from English conversational query", () => { - const keywords = extractKeywords("that thing we discussed about the API"); - expect(keywords).toContain("discussed"); - expect(keywords).toContain("api"); - // Should not include stop words - expect(keywords).not.toContain("that"); - expect(keywords).not.toContain("thing"); - expect(keywords).not.toContain("we"); - expect(keywords).not.toContain("about"); - expect(keywords).not.toContain("the"); - }); - - it("extracts keywords from Chinese conversational query", () => { - const keywords = extractKeywords("之前讨论的那个方案"); - expect(keywords).toContain("讨论"); - expect(keywords).toContain("方案"); - // Should not include stop words - expect(keywords).not.toContain("之前"); - expect(keywords).not.toContain("的"); - expect(keywords).not.toContain("那个"); - }); - - it("extracts keywords from mixed language query", () => { - const keywords = extractKeywords("昨天讨论的 API design"); - expect(keywords).toContain("讨论"); - expect(keywords).toContain("api"); - expect(keywords).toContain("design"); - }); - - it("returns specific technical terms", () => { - const keywords = extractKeywords("what was the solution for the CFR bug"); - expect(keywords).toContain("solution"); - expect(keywords).toContain("cfr"); - expect(keywords).toContain("bug"); - }); - - it("extracts keywords from Korean conversational query", () => { - const keywords = extractKeywords("어제 논의한 배포 전략"); - expect(keywords).toContain("논의한"); - expect(keywords).toContain("배포"); - expect(keywords).toContain("전략"); - // Should not include stop words - expect(keywords).not.toContain("어제"); - }); - - it("strips Korean particles to extract stems", () => { - const keywords = extractKeywords("서버에서 발생한 에러를 확인"); - expect(keywords).toContain("서버"); - expect(keywords).toContain("에러"); - expect(keywords).toContain("확인"); - }); - - it("filters Korean stop words including inflected forms", () => { - const keywords = extractKeywords("나는 그리고 그래서"); - expect(keywords).not.toContain("나"); - expect(keywords).not.toContain("나는"); - expect(keywords).not.toContain("그리고"); - expect(keywords).not.toContain("그래서"); - }); - - it("filters inflected Korean stop words not explicitly listed", () => { - const keywords = extractKeywords("그녀는 우리는"); - expect(keywords).not.toContain("그녀는"); - expect(keywords).not.toContain("우리는"); - expect(keywords).not.toContain("그녀"); - expect(keywords).not.toContain("우리"); - }); - - it("does not produce bogus single-char stems from particle stripping", () => { - const keywords = extractKeywords("논의"); - expect(keywords).toContain("논의"); - expect(keywords).not.toContain("논"); - }); - - it("strips longest Korean trailing particles first", () => { - const keywords = extractKeywords("기능으로 설명"); - expect(keywords).toContain("기능"); - expect(keywords).not.toContain("기능으"); - }); - - it("keeps stripped ASCII stems for mixed Korean tokens", () => { - const keywords = extractKeywords("API를 배포했다"); - expect(keywords).toContain("api"); - expect(keywords).toContain("배포했다"); - }); - - it("handles mixed Korean and English query", () => { - const keywords = extractKeywords("API 배포에 대한 논의"); - expect(keywords).toContain("api"); - expect(keywords).toContain("배포"); - expect(keywords).toContain("논의"); - }); - - it("extracts keywords from Japanese conversational query", () => { - const keywords = extractKeywords("昨日話したデプロイ戦略"); - expect(keywords).toContain("デプロイ"); - expect(keywords).toContain("戦略"); - expect(keywords).not.toContain("昨日"); - }); - - it("handles mixed Japanese and English query", () => { - const keywords = extractKeywords("昨日話したAPIのバグ"); - expect(keywords).toContain("api"); - expect(keywords).toContain("バグ"); - expect(keywords).not.toContain("した"); - }); - - it("filters Japanese stop words", () => { - const keywords = extractKeywords("これ それ そして どう"); - expect(keywords).not.toContain("これ"); - expect(keywords).not.toContain("それ"); - expect(keywords).not.toContain("そして"); - expect(keywords).not.toContain("どう"); - }); - - it("extracts keywords from Spanish conversational query", () => { - const keywords = extractKeywords("ayer hablamos sobre la estrategia de despliegue"); - expect(keywords).toContain("estrategia"); - expect(keywords).toContain("despliegue"); - expect(keywords).not.toContain("ayer"); - expect(keywords).not.toContain("sobre"); - }); - - it("extracts keywords from Portuguese conversational query", () => { - const keywords = extractKeywords("ontem falamos sobre a estratégia de implantação"); - expect(keywords).toContain("estratégia"); - expect(keywords).toContain("implantação"); - expect(keywords).not.toContain("ontem"); - expect(keywords).not.toContain("sobre"); - }); - - it("filters Spanish and Portuguese question stop words", () => { - const keywords = extractKeywords("cómo cuando donde porquê quando onde"); - expect(keywords).not.toContain("cómo"); - expect(keywords).not.toContain("cuando"); - expect(keywords).not.toContain("donde"); - expect(keywords).not.toContain("porquê"); - expect(keywords).not.toContain("quando"); - expect(keywords).not.toContain("onde"); - }); - - it("extracts keywords from Arabic conversational query", () => { - const keywords = extractKeywords("بالأمس ناقشنا استراتيجية النشر"); - expect(keywords).toContain("ناقشنا"); - expect(keywords).toContain("استراتيجية"); - expect(keywords).toContain("النشر"); - expect(keywords).not.toContain("بالأمس"); - }); - - it("filters Arabic question stop words", () => { - const keywords = extractKeywords("كيف متى أين ماذا"); - expect(keywords).not.toContain("كيف"); - expect(keywords).not.toContain("متى"); - expect(keywords).not.toContain("أين"); - expect(keywords).not.toContain("ماذا"); - }); - - it("handles empty query", () => { - expect(extractKeywords("")).toEqual([]); - expect(extractKeywords(" ")).toEqual([]); - }); - - it("handles query with only stop words", () => { - const keywords = extractKeywords("the a an is are"); - expect(keywords.length).toBe(0); - }); - - it("removes duplicate keywords", () => { - const keywords = extractKeywords("test test testing"); - const testCount = keywords.filter((k) => k === "test").length; - expect(testCount).toBe(1); - }); - - describe("with trigram tokenizer", () => { - const trigramOpts = { ftsTokenizer: "trigram" as const }; - - it("emits whole CJK block instead of unigrams in trigram mode", () => { - const defaultKeywords = extractKeywords("之前讨论的那个方案"); - const trigramKeywords = extractKeywords("之前讨论的那个方案", trigramOpts); - // Default mode produces bigrams - expect(defaultKeywords).toContain("讨论"); - expect(defaultKeywords).toContain("方案"); - // Trigram mode emits the whole contiguous CJK block (FTS5 trigram - // requires >= 3 chars per term; individual characters return no results) - expect(trigramKeywords).toContain("之前讨论的那个方案"); - expect(trigramKeywords).not.toContain("讨论"); - expect(trigramKeywords).not.toContain("方案"); - }); - - it("skips Japanese kanji bigrams in trigram mode", () => { - const defaultKeywords = extractKeywords("経済政策について"); - const trigramKeywords = extractKeywords("経済政策について", trigramOpts); - // Default mode adds kanji bigrams: 経済, 済政, 政策 - expect(defaultKeywords).toContain("経済"); - expect(defaultKeywords).toContain("済政"); - expect(defaultKeywords).toContain("政策"); - // Trigram mode keeps the full kanji block but skips bigram splitting - expect(trigramKeywords).toContain("経済政策"); - expect(trigramKeywords).not.toContain("済政"); - }); - - it("still filters stop words in trigram mode", () => { - const keywords = extractKeywords("これ それ そして どう", trigramOpts); - expect(keywords).not.toContain("これ"); - expect(keywords).not.toContain("それ"); - expect(keywords).not.toContain("そして"); - expect(keywords).not.toContain("どう"); - }); - - it("does not affect English keyword extraction", () => { - const keywords = extractKeywords("that thing we discussed about the API", trigramOpts); - expect(keywords).toContain("discussed"); - expect(keywords).toContain("api"); - expect(keywords).not.toContain("that"); - expect(keywords).not.toContain("the"); - }); - }); -}); - -describe("expandQueryForFts", () => { - it("returns original query and extracted keywords", () => { - const result = expandQueryForFts("that API we discussed"); - expect(result.original).toBe("that API we discussed"); - expect(result.keywords).toContain("api"); - expect(result.keywords).toContain("discussed"); - }); - - it("builds expanded OR query for FTS", () => { - const result = expandQueryForFts("the solution for bugs"); - expect(result.expanded).toContain("OR"); - expect(result.expanded).toContain("solution"); - expect(result.expanded).toContain("bugs"); - }); - - it("returns original query when no keywords extracted", () => { - const result = expandQueryForFts("the"); - expect(result.keywords.length).toBe(0); - expect(result.expanded).toBe("the"); - }); -});