From 2d62685ff02e99f37f296f54f1b7ecc456cdfca2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 21 Feb 2026 19:57:33 +0000 Subject: [PATCH] test(cli): dedupe memory runtime spies and cover json/search fallback flows --- src/cli/memory-cli.test.ts | 108 +++++++++++++++++++++++++++++++++---- 1 file changed, 99 insertions(+), 9 deletions(-) diff --git a/src/cli/memory-cli.test.ts b/src/cli/memory-cli.test.ts index cfa82d0fd..c75ce11df 100644 --- a/src/cli/memory-cli.test.ts +++ b/src/cli/memory-cli.test.ts @@ -39,6 +39,18 @@ afterEach(() => { }); describe("memory cli", () => { + function spyRuntimeLogs() { + return vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + } + + function spyRuntimeErrors() { + return vi.spyOn(defaultRuntime, "error").mockImplementation(() => {}); + } + + function firstLoggedJson(log: ReturnType) { + return JSON.parse(String(log.mock.calls[0]?.[0] ?? "null")) as Record; + } + function expectCliSync(sync: ReturnType) { expect(sync).toHaveBeenCalledWith( expect.objectContaining({ reason: "cli", force: false, progress: expect.any(Function) }), @@ -92,7 +104,7 @@ describe("memory cli", () => { }); mockManager({ ...params.manager, close }); - const error = vi.spyOn(defaultRuntime, "error").mockImplementation(() => {}); + const error = spyRuntimeErrors(); await runMemoryCli(params.args); params.beforeExpect?.(); @@ -123,7 +135,7 @@ describe("memory cli", () => { close, }); - const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + const log = spyRuntimeLogs(); await runMemoryCli(["status"]); expect(log).toHaveBeenCalledWith(expect.stringContaining("Vector: ready")); @@ -152,7 +164,7 @@ describe("memory cli", () => { close, }); - const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + const log = spyRuntimeLogs(); await runMemoryCli(["status", "--agent", "main"]); expect(log).toHaveBeenCalledWith(expect.stringContaining("Vector: unavailable")); @@ -170,7 +182,7 @@ describe("memory cli", () => { close, }); - const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + const log = spyRuntimeLogs(); await runMemoryCli(["status", "--deep"]); expect(probeEmbeddingAvailability).toHaveBeenCalled(); @@ -213,7 +225,7 @@ describe("memory cli", () => { close, }); - vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + spyRuntimeLogs(); await runMemoryCli(["status", "--index"]); expectCliSync(sync); @@ -226,7 +238,7 @@ describe("memory cli", () => { const sync = vi.fn(async () => {}); mockManager({ sync, close }); - const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + const log = spyRuntimeLogs(); await runMemoryCli(["index"]); expectCliSync(sync); @@ -240,7 +252,7 @@ describe("memory cli", () => { await withQmdIndexDb("sqlite-bytes", async (dbPath) => { mockManager({ sync, status: () => ({ backend: "qmd", dbPath }), close }); - const log = vi.spyOn(defaultRuntime, "log").mockImplementation(() => {}); + const log = spyRuntimeLogs(); await runMemoryCli(["index"]); expectCliSync(sync); @@ -256,7 +268,7 @@ describe("memory cli", () => { await withQmdIndexDb("", async (dbPath) => { mockManager({ sync, status: () => ({ backend: "qmd", dbPath }), close }); - const error = vi.spyOn(defaultRuntime, "error").mockImplementation(() => {}); + const error = spyRuntimeErrors(); await runMemoryCli(["index"]); expectCliSync(sync); @@ -305,7 +317,7 @@ describe("memory cli", () => { }); mockManager({ search, close }); - const error = vi.spyOn(defaultRuntime, "error").mockImplementation(() => {}); + const error = spyRuntimeErrors(); await runMemoryCli(["search", "oops"]); expect(search).toHaveBeenCalled(); @@ -313,4 +325,82 @@ describe("memory cli", () => { expect(error).toHaveBeenCalledWith(expect.stringContaining("Memory search failed: boom")); expect(process.exitCode).toBe(1); }); + + it("prints status json output when requested", async () => { + const close = vi.fn(async () => {}); + mockManager({ + probeVectorAvailability: vi.fn(async () => true), + status: () => makeMemoryStatus({ workspaceDir: undefined }), + close, + }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["status", "--json"]); + + const payload = firstLoggedJson(log); + expect(Array.isArray(payload)).toBe(true); + expect((payload[0] as Record)?.agentId).toBe("main"); + expect(close).toHaveBeenCalled(); + }); + + it("logs default message when memory manager is missing", async () => { + getMemorySearchManager.mockResolvedValueOnce({ manager: null }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["status"]); + + expect(log).toHaveBeenCalledWith("Memory search disabled."); + }); + + it("logs backend unsupported message when index has no sync", async () => { + const close = vi.fn(async () => {}); + mockManager({ + status: () => makeMemoryStatus(), + close, + }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["index"]); + + expect(log).toHaveBeenCalledWith("Memory backend does not support manual reindex."); + expect(close).toHaveBeenCalled(); + }); + + it("prints no matches for empty search results", async () => { + const close = vi.fn(async () => {}); + const search = vi.fn(async () => []); + mockManager({ search, close }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["search", "hello"]); + + expect(search).toHaveBeenCalledWith("hello", { + maxResults: undefined, + minScore: undefined, + }); + expect(log).toHaveBeenCalledWith("No matches."); + expect(close).toHaveBeenCalled(); + }); + + it("prints search results as json when requested", async () => { + const close = vi.fn(async () => {}); + const search = vi.fn(async () => [ + { + path: "memory/2026-01-12.md", + startLine: 1, + endLine: 2, + score: 0.5, + snippet: "Hello", + }, + ]); + mockManager({ search, close }); + + const log = spyRuntimeLogs(); + await runMemoryCli(["search", "hello", "--json"]); + + const payload = firstLoggedJson(log); + expect(Array.isArray(payload.results)).toBe(true); + expect(payload.results as unknown[]).toHaveLength(1); + expect(close).toHaveBeenCalled(); + }); });