mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 12:51:34 +00:00
* fix(cli): make commands, completion, and JSON output reliable * fix(cli): reconcile completion coverage with current main * test(cli): keep test routing stable across isolation lanes
108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
import { Command } from "commander";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawPluginApi } from "./api.js";
|
|
import type { Embeddings } from "./embeddings.js";
|
|
import type { MemoryDB } from "./lancedb-store.js";
|
|
import { registerMemoryCli } from "./memory-cli.js";
|
|
|
|
function createHarness(params?: { embedError?: unknown; closeError?: Error }) {
|
|
const registerCli = vi.fn();
|
|
const closeError = params?.closeError;
|
|
const close = closeError
|
|
? vi.fn(async () => {
|
|
throw closeError;
|
|
})
|
|
: vi.fn(async () => {});
|
|
const embed =
|
|
params && Object.hasOwn(params, "embedError")
|
|
? vi.fn(async () => {
|
|
throw params.embedError;
|
|
})
|
|
: vi.fn(async () => [0.1, 0.2]);
|
|
const embeddings: Embeddings = {
|
|
embed,
|
|
close,
|
|
};
|
|
const search = vi.fn(async () => []);
|
|
registerMemoryCli(
|
|
{ registerCli } as unknown as OpenClawPluginApi,
|
|
{ search } as unknown as MemoryDB,
|
|
embeddings,
|
|
() => "main",
|
|
undefined,
|
|
);
|
|
const registrar = registerCli.mock.calls[0]?.[0] as
|
|
| ((params: { program: Command }) => void)
|
|
| undefined;
|
|
if (!registrar) {
|
|
throw new Error("expected memory CLI registrar");
|
|
}
|
|
const program = new Command();
|
|
registrar({ program });
|
|
return { close, embed, program, search };
|
|
}
|
|
|
|
describe("memory-lancedb CLI embedding lifecycle", () => {
|
|
it("closes embeddings after search", async () => {
|
|
const harness = createHarness();
|
|
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
try {
|
|
await harness.program.parseAsync(["node", "openclaw", "ltm", "search", "hello"]);
|
|
} finally {
|
|
log.mockRestore();
|
|
}
|
|
|
|
expect(harness.embed).toHaveBeenCalledTimes(1);
|
|
expect(harness.search).toHaveBeenCalledTimes(1);
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("closes embeddings without masking search failure", async () => {
|
|
const harness = createHarness({
|
|
embedError: new Error("embedding failed"),
|
|
closeError: new Error("close failed"),
|
|
});
|
|
|
|
await expect(
|
|
harness.program.parseAsync(["node", "openclaw", "ltm", "search", "hello"]),
|
|
).rejects.toThrow("embedding failed");
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("rejects an invalid limit before generating an embedding", async () => {
|
|
const harness = createHarness();
|
|
|
|
await expect(
|
|
harness.program.parseAsync([
|
|
"node",
|
|
"openclaw",
|
|
"ltm",
|
|
"search",
|
|
"hello",
|
|
"--limit",
|
|
"5items",
|
|
]),
|
|
).rejects.toThrow("--limit must be a positive integer");
|
|
|
|
expect(harness.embed).not.toHaveBeenCalled();
|
|
expect(harness.search).not.toHaveBeenCalled();
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("preserves a falsy search rejection over cleanup failure", async () => {
|
|
const harness = createHarness({
|
|
embedError: null,
|
|
closeError: new Error("close failed"),
|
|
});
|
|
|
|
const rejection = await harness.program
|
|
.parseAsync(["node", "openclaw", "ltm", "search", "hello"])
|
|
.then(
|
|
() => "resolved",
|
|
(err: unknown) => err,
|
|
);
|
|
expect(rejection).toBeNull();
|
|
expect(harness.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|