refactor: update memory skill loader, plugin config, and add privacy docs (#4905)
This commit is contained in:
@@ -32,6 +32,7 @@ vi.mock("../skill-loader.ts", () => ({
|
||||
loadDreamPrompt: vi.fn().mockReturnValue("dream prompt"),
|
||||
}));
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Imports (after mocks)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -251,6 +252,7 @@ describe("registerCliCommands", () => {
|
||||
warn: ReturnType<typeof vi.spyOn>;
|
||||
};
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
@@ -267,6 +269,7 @@ describe("registerCliCommands", () => {
|
||||
warn: vi.spyOn(console, "warn").mockImplementation(() => {}),
|
||||
};
|
||||
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
||||
stdoutSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -274,6 +277,7 @@ describe("registerCliCommands", () => {
|
||||
consoleSpy.error.mockRestore();
|
||||
consoleSpy.warn.mockRestore();
|
||||
stderrSpy.mockRestore();
|
||||
stdoutSpy.mockRestore();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -527,6 +531,158 @@ describe("registerCliCommands", () => {
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ apiKey: "m0-key", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return typeof p.ok === "boolean";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(true);
|
||||
expect(parsed.mode).toBe("platform");
|
||||
expect(parsed.validated).toBe(true);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key flow with failed validation when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 401,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ apiKey: "bad-key", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return typeof p.ok === "boolean";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(false);
|
||||
expect(parsed.mode).toBe("platform");
|
||||
expect(parsed.validated).toBe(false);
|
||||
expect(parsed.httpStatus).toBe(401);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for --api-key + --email conflict when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({ apiKey: "key", email: "a@b.com", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === false;
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.error).toContain("Cannot use both");
|
||||
expect(writePluginAuth).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("outputs JSON for email send-code flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ email: "user@example.com", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.codeSent === true;
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.ok).toBe(true);
|
||||
expect(parsed.email).toBe("user@example.com");
|
||||
expect(parsed.nextCommand).toContain("--code");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON for email verify flow when --json is set", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({ api_key: "m0-verified" }),
|
||||
}));
|
||||
|
||||
await initCmd._action!({ email: "u@b.com", code: "123456", json: true });
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === true && p.mode === "platform";
|
||||
} catch { return false; }
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
const parsed = JSON.parse(jsonCall![0] as string);
|
||||
expect(parsed.email).toBe("u@b.com");
|
||||
expect(parsed.message).toContain("Authenticated");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("clears stale apiKey when switching to OSS mode", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
(readPluginAuth as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
apiKey: "m0-old-platform-key",
|
||||
mode: "platform",
|
||||
userId: "testuser",
|
||||
});
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
});
|
||||
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
apiKey: "",
|
||||
mode: "open-source",
|
||||
}),
|
||||
);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
@@ -1456,4 +1612,144 @@ describe("registerCliCommands", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// Restructured init menu flags
|
||||
// ========================================================================
|
||||
|
||||
describe("init — restructured menu", () => {
|
||||
it("registers --mode flag", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const modeOpt = initCmd._options.find((o) => o.flags.includes("--mode"));
|
||||
expect(modeOpt).toBeDefined();
|
||||
});
|
||||
|
||||
it("registers --oss-llm flag", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const opt = initCmd._options.find((o) => o.flags.includes("--oss-llm "));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
|
||||
it("registers --json flag on init", () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
const opt = initCmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// --json flag registration on all commands
|
||||
// ========================================================================
|
||||
|
||||
describe("--json flag registration", () => {
|
||||
for (const name of ["search", "add", "get", "list", "update", "delete", "status", "import", "dream"]) {
|
||||
it(`registers --json on ${name}`, () => {
|
||||
const { mem0 } = setup();
|
||||
const cmd = findCommand(mem0, name)!;
|
||||
const opt = cmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
}
|
||||
|
||||
it("registers --json on config show", () => {
|
||||
const { mem0 } = setup();
|
||||
const configCmd = findCommand(mem0, "config")!;
|
||||
const showCmd = findCommand(configCmd, "show")!;
|
||||
expect(showCmd).toBeDefined();
|
||||
const opt = showCmd._options.find((o) => o.flags.includes("--json"));
|
||||
expect(opt).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// Non-interactive OSS init
|
||||
// ========================================================================
|
||||
|
||||
describe("init --mode open-source (non-interactive)", () => {
|
||||
it("writes LLM, embedder, and vector config for ollama + qdrant", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
userId: "test-user",
|
||||
});
|
||||
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "llm"],
|
||||
expect.objectContaining({ provider: "ollama" }),
|
||||
);
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "embedder"],
|
||||
expect.objectContaining({ provider: "ollama" }),
|
||||
);
|
||||
expect(writePluginConfigField).toHaveBeenCalledWith(
|
||||
["oss", "vectorStore"],
|
||||
expect.objectContaining({ provider: "qdrant" }),
|
||||
);
|
||||
expect(writePluginAuth).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ mode: "open-source", userId: "test-user" }),
|
||||
);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("outputs JSON when --json is passed", async () => {
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true, json: async () => ({}) }));
|
||||
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
await initCmd._action!({
|
||||
mode: "open-source",
|
||||
ossLlm: "ollama",
|
||||
ossEmbedder: "ollama",
|
||||
ossVector: "qdrant",
|
||||
json: true,
|
||||
});
|
||||
|
||||
const jsonCall = stdoutSpy.mock.calls.find((c) => {
|
||||
try {
|
||||
const p = JSON.parse(c[0] as string);
|
||||
return p.ok === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
expect(jsonCall).toBeDefined();
|
||||
if (jsonCall) {
|
||||
const parsed = JSON.parse(jsonCall[0] as string);
|
||||
expect(parsed.mode).toBe("open-source");
|
||||
expect(parsed.config.llm.provider).toBe("ollama");
|
||||
}
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("errors when openai LLM has no key", async () => {
|
||||
const { mem0 } = setup();
|
||||
const initCmd = findCommand(mem0, "init")!;
|
||||
|
||||
// Ensure env var is not set so validation fails
|
||||
const savedEnv = process.env.OPENAI_API_KEY;
|
||||
delete process.env.OPENAI_API_KEY;
|
||||
|
||||
await initCmd._action!({ mode: "open-source", ossLlm: "openai" });
|
||||
|
||||
expect(consoleSpy.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("--oss-llm-key"),
|
||||
);
|
||||
|
||||
// Restore env var
|
||||
if (savedEnv !== undefined) process.env.OPENAI_API_KEY = savedEnv;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,14 +44,14 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
|
||||
expect(cfg.userId.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("autoCapture defaults to true", () => {
|
||||
it("autoCapture defaults to false", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.autoCapture).toBe(true);
|
||||
expect(cfg.autoCapture).toBe(false);
|
||||
});
|
||||
|
||||
it("autoRecall defaults to true", () => {
|
||||
it("autoRecall defaults to false", () => {
|
||||
const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
|
||||
expect(cfg.autoRecall).toBe(true);
|
||||
expect(cfg.autoRecall).toBe(false);
|
||||
});
|
||||
|
||||
it("topK defaults to 5", () => {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { jsonOut, jsonErr, redactSecrets } from "../cli/json-helpers.ts";
|
||||
|
||||
describe("jsonOut", () => {
|
||||
let writeSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); });
|
||||
afterEach(() => { writeSpy.mockRestore(); });
|
||||
|
||||
it("returns false and prints nothing when json is falsy", () => {
|
||||
expect(jsonOut({}, { ok: true })).toBe(false);
|
||||
expect(writeSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns true and prints JSON to stdout when json is true", () => {
|
||||
expect(jsonOut({ json: true }, { ok: true, count: 3 })).toBe(true);
|
||||
expect(writeSpy).toHaveBeenCalledOnce();
|
||||
const parsed = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
||||
expect(parsed).toEqual({ ok: true, count: 3 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("jsonErr", () => {
|
||||
let writeSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true); });
|
||||
afterEach(() => { writeSpy.mockRestore(); });
|
||||
|
||||
it("returns false when json is falsy", () => {
|
||||
expect(jsonErr({}, "bad")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true and prints error JSON to stdout", () => {
|
||||
expect(jsonErr({ json: true }, "Something broke")).toBe(true);
|
||||
const parsed = JSON.parse(writeSpy.mock.calls[0][0] as string);
|
||||
expect(parsed).toEqual({ ok: false, error: "Something broke" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("redactSecrets", () => {
|
||||
it("redacts string values for known secret keys", () => {
|
||||
const input = { apiKey: "m0-abcdefghijklmnop", name: "test" };
|
||||
const result = redactSecrets(input, new Set(["apiKey"]));
|
||||
expect(result.apiKey).toBe("m0-a...mnop");
|
||||
expect(result.name).toBe("test");
|
||||
});
|
||||
|
||||
it("handles short keys", () => {
|
||||
const result = redactSecrets({ apiKey: "ab" }, new Set(["apiKey"]));
|
||||
expect(result.apiKey).toBe("ab***");
|
||||
});
|
||||
|
||||
it("skips non-string values", () => {
|
||||
const result = redactSecrets({ count: 5 }, new Set(["count"]));
|
||||
expect(result.count).toBe(5);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
LLM_PROVIDERS,
|
||||
EMBEDDER_PROVIDERS,
|
||||
VECTOR_PROVIDERS,
|
||||
KNOWN_EMBEDDER_DIMS,
|
||||
buildOssLlmConfig,
|
||||
buildOssEmbedderConfig,
|
||||
buildOssVectorConfig,
|
||||
validateOssFlags,
|
||||
checkQdrantConnectivity,
|
||||
checkOllamaConnectivity,
|
||||
checkPgConnectivity,
|
||||
} from "../cli/oss-wizard.ts";
|
||||
|
||||
describe("LLM_PROVIDERS", () => {
|
||||
it("has 3 providers", () => {
|
||||
expect(LLM_PROVIDERS).toHaveLength(3);
|
||||
expect(LLM_PROVIDERS.map((p) => p.id)).toEqual(["openai", "ollama", "anthropic"]);
|
||||
});
|
||||
|
||||
it("openai requires API key", () => {
|
||||
const openai = LLM_PROVIDERS.find((p) => p.id === "openai")!;
|
||||
expect(openai.needsApiKey).toBe(true);
|
||||
expect(openai.defaultModel).toBe("gpt-5-mini");
|
||||
});
|
||||
|
||||
it("ollama needs no API key but needs URL", () => {
|
||||
const ollama = LLM_PROVIDERS.find((p) => p.id === "ollama")!;
|
||||
expect(ollama.needsApiKey).toBe(false);
|
||||
expect(ollama.needsUrl).toBe(true);
|
||||
expect(ollama.defaultUrl).toBe("http://localhost:11434");
|
||||
});
|
||||
});
|
||||
|
||||
describe("EMBEDDER_PROVIDERS", () => {
|
||||
it("has 2 providers", () => {
|
||||
expect(EMBEDDER_PROVIDERS).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("KNOWN_EMBEDDER_DIMS", () => {
|
||||
it("maps default models to dims", () => {
|
||||
expect(KNOWN_EMBEDDER_DIMS["text-embedding-3-small"]).toBe(1536);
|
||||
expect(KNOWN_EMBEDDER_DIMS["nomic-embed-text"]).toBe(768);
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssLlmConfig", () => {
|
||||
it("builds openai config with API key", () => {
|
||||
const result = buildOssLlmConfig("openai", { apiKey: "sk-test" });
|
||||
expect(result).toEqual({
|
||||
provider: "openai",
|
||||
config: { model: "gpt-5-mini", apiKey: "sk-test" },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds ollama config with custom URL and model", () => {
|
||||
const result = buildOssLlmConfig("ollama", { url: "http://myhost:11434", model: "mistral" });
|
||||
expect(result).toEqual({
|
||||
provider: "ollama",
|
||||
config: { model: "mistral", url: "http://myhost:11434" },
|
||||
});
|
||||
});
|
||||
|
||||
it("builds ollama config with default URL", () => {
|
||||
const result = buildOssLlmConfig("ollama", {});
|
||||
expect(result.config.url).toBe("http://localhost:11434");
|
||||
});
|
||||
|
||||
it("ignores url for non-ollama providers", () => {
|
||||
const result = buildOssLlmConfig("anthropic", { apiKey: "sk-ant", url: "http://ignored" });
|
||||
expect(result.config).not.toHaveProperty("url");
|
||||
expect(result.config).toHaveProperty("apiKey", "sk-ant");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssEmbedderConfig", () => {
|
||||
it("builds openai embedder", () => {
|
||||
const result = buildOssEmbedderConfig("openai", { apiKey: "sk-test" });
|
||||
expect(result.config.model).toBe("text-embedding-3-small");
|
||||
expect(result.dims).toBe(1536);
|
||||
});
|
||||
|
||||
it("builds ollama embedder with url field", () => {
|
||||
const result = buildOssEmbedderConfig("ollama", { url: "http://myhost:11434" });
|
||||
expect(result.config.url).toBe("http://myhost:11434");
|
||||
expect(result.config).not.toHaveProperty("ollama_base_url");
|
||||
expect(result.config.model).toBe("nomic-embed-text");
|
||||
expect(result.dims).toBe(768);
|
||||
});
|
||||
|
||||
it("returns unknown dims for custom model", () => {
|
||||
const result = buildOssEmbedderConfig("ollama", { model: "custom-embed" });
|
||||
expect(result.dims).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("VECTOR_PROVIDERS", () => {
|
||||
it("has 2 providers", () => {
|
||||
expect(VECTOR_PROVIDERS).toHaveLength(2);
|
||||
expect(VECTOR_PROVIDERS.map((p) => p.id)).toEqual(["qdrant", "pgvector"]);
|
||||
});
|
||||
|
||||
it("qdrant requires server connection", () => {
|
||||
const qdrant = VECTOR_PROVIDERS.find((p) => p.id === "qdrant")!;
|
||||
expect(qdrant.needsConnection).toBe(true);
|
||||
expect(qdrant.defaultUrl).toBe("http://localhost:6333");
|
||||
expect(qdrant.setupHint).toContain("docker");
|
||||
});
|
||||
|
||||
it("pgvector requires connection and has setup hint", () => {
|
||||
const pg = VECTOR_PROVIDERS.find((p) => p.id === "pgvector")!;
|
||||
expect(pg.needsConnection).toBe(true);
|
||||
expect(pg.defaultPort).toBe(5432);
|
||||
expect(pg.setupHint).toContain("pgvector");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildOssVectorConfig", () => {
|
||||
it("builds qdrant with default url and dims", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { dims: 1536 });
|
||||
expect(result.config.url).toBe("http://localhost:6333");
|
||||
expect(result.config.onDisk).toBe(true);
|
||||
expect(result.config.dimension).toBe(1536);
|
||||
});
|
||||
|
||||
it("builds qdrant with custom url", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { url: "http://qdrant.local:6333", dims: 768 });
|
||||
expect(result.config.url).toBe("http://qdrant.local:6333");
|
||||
expect(result.config.onDisk).toBe(true);
|
||||
expect(result.config.dimension).toBe(768);
|
||||
});
|
||||
|
||||
it("builds qdrant with api key for cloud", () => {
|
||||
const result = buildOssVectorConfig("qdrant", { url: "https://cloud.qdrant.io", apiKey: "qd-key", dims: 1536 });
|
||||
expect(result.config.apiKey).toBe("qd-key");
|
||||
expect(result.config.url).toBe("https://cloud.qdrant.io");
|
||||
});
|
||||
|
||||
it("builds pgvector with connection details", () => {
|
||||
const result = buildOssVectorConfig("pgvector", {
|
||||
host: "db.local", port: "5432", user: "me", password: "pw", dbname: "mydb", dims: 512,
|
||||
});
|
||||
expect(result.config.host).toBe("db.local");
|
||||
expect(result.config.dimension).toBe(512);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkQdrantConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkQdrantConnectivity("http://localhost:19999");
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("Cannot reach Qdrant");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkOllamaConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkOllamaConnectivity("http://localhost:19998");
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("Cannot reach Ollama");
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkPgConnectivity", () => {
|
||||
it("returns error for unreachable host", async () => {
|
||||
const result = await checkPgConnectivity("localhost", 19997);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.error).toContain("PostgreSQL not reachable");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateOssFlags", () => {
|
||||
it("returns error when openai LLM has no key", () => {
|
||||
const result = validateOssFlags({ ossLlm: "openai" });
|
||||
expect(result.error).toContain("--oss-llm-key");
|
||||
});
|
||||
|
||||
it("passes for ollama with no key", () => {
|
||||
const result = validateOssFlags({ ossLlm: "ollama", ossEmbedder: "ollama", ossVector: "qdrant" });
|
||||
expect(result.error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns error for unknown provider", () => {
|
||||
const result = validateOssFlags({ ossLlm: "bogus" });
|
||||
expect(result.error).toContain("Unknown LLM provider");
|
||||
});
|
||||
|
||||
it("returns error when pgvector missing user", () => {
|
||||
const result = validateOssFlags({
|
||||
ossLlm: "ollama", ossEmbedder: "ollama", ossVector: "pgvector",
|
||||
});
|
||||
expect(result.error).toContain("--oss-vector-user");
|
||||
});
|
||||
});
|
||||
@@ -263,10 +263,11 @@ describe("memory_search execute", () => {
|
||||
scope: "session",
|
||||
});
|
||||
|
||||
// Should call buildSearchOptions with session ID
|
||||
// Should call buildSearchOptions with session ID as 4th arg (sessionKey)
|
||||
expect(ctx.buildSearchOptions).toHaveBeenCalledWith(
|
||||
"testuser",
|
||||
undefined,
|
||||
undefined,
|
||||
"session-abc",
|
||||
);
|
||||
expect(result.details.count).toBe(1);
|
||||
@@ -446,8 +447,8 @@ describe("memory_add execute", () => {
|
||||
|
||||
await tool.execute("call-7", { text: "new fact" });
|
||||
|
||||
// Search should be called for dedup before add
|
||||
expect(searchMock).toHaveBeenCalledOnce();
|
||||
// Mem0 backend handles dedup internally — no separate search call
|
||||
expect(searchMock).not.toHaveBeenCalled();
|
||||
expect(addMock).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user