test(cli): dedupe memory runtime spies and cover json/search fallback flows

This commit is contained in:
Peter Steinberger
2026-02-21 19:57:33 +00:00
parent e46634db9a
commit 2d62685ff0

View File

@@ -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<typeof vi.spyOn>) {
return JSON.parse(String(log.mock.calls[0]?.[0] ?? "null")) as Record<string, unknown>;
}
function expectCliSync(sync: ReturnType<typeof vi.fn>) {
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<string, unknown>)?.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();
});
});