refactor: drop orgId, projectId, enableGraph config options, update CLI prompts, and clean up related code (#4734)

This commit is contained in:
Kartik
2026-04-09 14:57:33 +05:30
committed by GitHub
parent 081eca6d8f
commit 88f696a60a
20 changed files with 624 additions and 206 deletions
+387 -9
View File
@@ -4,9 +4,18 @@
* 2. initPromise poisoning fix (retry after failure)
* 3. Graceful SQLite fallback in OSSProvider
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { mem0ConfigSchema, createProvider } from "./index.ts";
/** Stub vector-store classes required by OSSProvider._init's patching loop. */
function vectorStubs() {
return {
PGVector: class { initialize() { return Promise.resolve(); } },
RedisDB: class { initialize() { return Promise.resolve(); } },
Qdrant: class { initialize() { return Promise.resolve(); } },
};
}
// ---------------------------------------------------------------------------
// 1. Config: disableHistory passthrough
// ---------------------------------------------------------------------------
@@ -60,6 +69,7 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => {
beforeEach(() => {
capturedConfig = undefined;
memoryCallCount = 0;
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
@@ -81,9 +91,14 @@ describe("OSSProvider — disableHistory passthrough to Memory", () => {
}
async delete() {}
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("passes disableHistory: true to Memory when configured", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
@@ -130,6 +145,7 @@ describe("OSSProvider — initPromise retry after failure", () => {
beforeEach(() => {
callCount = 0;
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
@@ -154,9 +170,14 @@ describe("OSSProvider — initPromise retry after failure", () => {
}
async delete() {}
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("retries initialization after a transient failure", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
@@ -184,14 +205,21 @@ describe("OSSProvider — initPromise retry after failure", () => {
// ---------------------------------------------------------------------------
describe("OSSProvider — graceful SQLite fallback", () => {
let capturedConfigs: Record<string, unknown>[];
/** When set, the mock Memory constructor always throws with this message. */
let forceConstructorError: string | null;
beforeEach(() => {
capturedConfigs = [];
forceConstructorError = null;
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
constructor(config: Record<string, unknown>) {
capturedConfigs.push({ ...config });
if (forceConstructorError) {
throw new Error(forceConstructorError);
}
if (!config.disableHistory) {
throw new Error("Could not locate the bindings file");
}
@@ -211,9 +239,14 @@ describe("OSSProvider — graceful SQLite fallback", () => {
}
async delete() {}
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("retries with disableHistory: true when initial construction fails", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { createProvider } = await import("./index.ts");
@@ -242,14 +275,8 @@ describe("OSSProvider — graceful SQLite fallback", () => {
});
it("does not retry when disableHistory is already true", async () => {
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
constructor(config: Record<string, unknown>) {
// Fail even with disableHistory (e.g. vector store issue)
throw new Error("vector store connection refused");
}
},
}));
// Force the constructor to always throw, regardless of disableHistory
forceConstructorError = "vector store connection refused";
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
@@ -274,6 +301,7 @@ describe("PlatformProvider — initPromise retry after failure", () => {
beforeEach(() => {
callCount = 0;
vi.resetModules();
vi.doMock("mem0ai", () => ({
default: class MockMemoryClient {
@@ -300,6 +328,10 @@ describe("PlatformProvider — initPromise retry after failure", () => {
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("retries initialization after a transient failure", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
@@ -320,3 +352,349 @@ describe("PlatformProvider — initPromise retry after failure", () => {
expect(callCount).toBe(2);
});
});
// ---------------------------------------------------------------------------
// 6. OSSProvider: _buildConfig covers all branches
// ---------------------------------------------------------------------------
describe("OSSProvider — _buildConfig branch coverage", () => {
let capturedConfig: Record<string, unknown> | undefined;
beforeEach(() => {
capturedConfig = undefined;
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
constructor(config: Record<string, unknown>) {
capturedConfig = { ...config };
}
async search() { return { results: [] }; }
async get() { return {}; }
async getAll() { return []; }
async add() { return { results: [] }; }
async delete() {}
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("builds config with custom embedder, llm, vectorStore, and historyDbPath", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: {
embedder: { provider: "openai", config: { apiKey: "sk-e", model: "text-embedding-3-small" } },
llm: { provider: "openai", config: { apiKey: "sk-l", model: "gpt-4" } },
vectorStore: { provider: "qdrant", config: { host: "localhost", port: 6333 } },
historyDbPath: "/tmp/history.db",
disableHistory: true,
},
});
const api = { resolvePath: (p: string) => `/resolved${p}` } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
expect(capturedConfig).toBeDefined();
expect(capturedConfig!.embedder).toEqual({
provider: "openai",
config: { model: "text-embedding-3-small", apiKey: "sk-e" },
});
expect(capturedConfig!.llm).toEqual({
provider: "openai",
config: expect.objectContaining({ model: "gpt-4", apiKey: "sk-l" }),
});
expect(capturedConfig!.vectorStore).toEqual({ provider: "qdrant", config: { host: "localhost", port: 6333 } });
expect(capturedConfig!.historyDbPath).toBe("/resolved/tmp/history.db");
expect(capturedConfig!.disableHistory).toBe(true);
});
it("strips empty-string values from embedder and llm config", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: {
embedder: { provider: "openai", config: { apiKey: "", model: "custom-model" } },
llm: { provider: "openai", config: { apiKey: "", model: "" } },
disableHistory: true,
},
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
expect(capturedConfig).toBeDefined();
// Empty apiKey should be stripped, leaving only the non-empty model
const embedderCfg = (capturedConfig!.embedder as any).config;
expect(embedderCfg.apiKey).toBeUndefined();
expect(embedderCfg.model).toBe("custom-model");
// Both empty keys in llm should be stripped, defaults applied
const llmCfg = (capturedConfig!.llm as any).config;
expect(llmCfg.apiKey).toBeUndefined();
});
it("falls back to default provider when embedder/llm provider is empty", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: {
embedder: { provider: "", config: { apiKey: "sk-e" } },
llm: { provider: "", config: { apiKey: "sk-l" } },
disableHistory: true,
},
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
expect(capturedConfig).toBeDefined();
// Empty provider should fall back to "openai" default
expect((capturedConfig!.embedder as any).provider).toBe("openai");
expect((capturedConfig!.llm as any).provider).toBe("openai");
});
});
// ---------------------------------------------------------------------------
// 7. OSSProvider: vector store dimension patching
// ---------------------------------------------------------------------------
describe("OSSProvider — vector store dimension patching", () => {
let capturedModule: any;
beforeEach(() => {
vi.resetModules();
vi.doMock("mem0ai/oss", () => {
const mod = {
Memory: class MockMemory {
constructor() {}
async search() { return { results: [] }; }
async get() { return {}; }
async getAll() { return []; }
async add() { return { results: [] }; }
async delete() {}
},
PGVector: class {
config: any;
dimension: any;
_initializePromise: any;
initialize() { return Promise.resolve("pg-initialized"); }
},
RedisDB: class {
config: any;
_initializePromise: any;
initialize() { return Promise.resolve("redis-initialized"); }
},
Qdrant: class {
config: any;
dimension: any;
_initializePromise: any;
initialize() { return Promise.resolve("qdrant-initialized"); }
},
};
capturedModule = mod;
return mod;
});
});
afterEach(() => {
vi.restoreAllMocks();
});
async function triggerInit() {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
});
const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any);
await provider.search("test", { user_id: "u1" });
}
it("copies config.dimension to embeddingModelDims and this.dimension", async () => {
await triggerInit();
const pg = new capturedModule.PGVector();
pg.config = { dimension: 1536 };
await pg.initialize();
expect(pg.config.embeddingModelDims).toBe(1536);
expect(pg.dimension).toBe(1536);
});
it("returns resolved promise when no dimensions are known", async () => {
await triggerInit();
const pg = new capturedModule.PGVector();
pg.config = {};
const result = await pg.initialize();
expect(result).toBeUndefined();
});
it("runs original initialize only once via cached promise", async () => {
await triggerInit();
const q = new capturedModule.Qdrant();
q.config = { dimension: 768 };
const first = await q.initialize();
const second = await q.initialize();
expect(first).toBe("qdrant-initialized");
expect(second).toBe("qdrant-initialized");
expect(q._initializePromise).toBeDefined();
});
it("skips missing vector store classes without crashing", async () => {
// Override with a mock that omits PGVector entirely
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class {
constructor() {}
async search() { return { results: [] }; }
async get() { return {}; }
async getAll() { return []; }
async add() { return { results: [] }; }
async delete() {}
},
PGVector: undefined, // explicitly absent — tests the !VectorCls guard
RedisDB: class { initialize() { return Promise.resolve(); } },
Qdrant: class { initialize() { return Promise.resolve(); } },
}));
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
});
const provider = createProvider(cfg, { resolvePath: (p: string) => p } as any);
// Should not throw even though PGVector is missing
const results = await provider.search("test", { user_id: "u1" });
expect(results).toBeDefined();
});
});
// ---------------------------------------------------------------------------
// 8. OSSProvider: history() error handler
// ---------------------------------------------------------------------------
describe("OSSProvider — history error handling", () => {
/** When set, the mock history() throws this value instead of an Error. */
let historyThrowValue: unknown;
beforeEach(() => {
historyThrowValue = new Error("history not available");
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
constructor() {}
async search() { return { results: [] }; }
async get() { return {}; }
async getAll() { return []; }
async add() { return { results: [] }; }
async delete() {}
async history() { throw historyThrowValue; }
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("returns empty array and warns when history() throws an Error", async () => {
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
const result = await provider.history("mem-123");
expect(result).toEqual([]);
expect(warnSpy).toHaveBeenCalledWith(
"[mem0] OSS history() failed:",
"history not available",
);
warnSpy.mockRestore();
});
it("handles non-Error thrown values in history()", async () => {
historyThrowValue = "raw string error";
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
const result = await provider.history("mem-456");
expect(result).toEqual([]);
expect(warnSpy).toHaveBeenCalledWith(
"[mem0] OSS history() failed:",
"raw string error",
);
warnSpy.mockRestore();
});
});
// ---------------------------------------------------------------------------
// 9. OSSProvider: customPrompt passthrough
// ---------------------------------------------------------------------------
describe("OSSProvider — customPrompt passthrough", () => {
let capturedConfig: Record<string, unknown> | undefined;
beforeEach(() => {
capturedConfig = undefined;
vi.resetModules();
vi.doMock("mem0ai/oss", () => ({
Memory: class MockMemory {
constructor(config: Record<string, unknown>) {
capturedConfig = { ...config };
}
async search() { return { results: [] }; }
async get() { return {}; }
async getAll() { return []; }
async add() { return { results: [] }; }
async delete() {}
},
...vectorStubs(),
}));
});
afterEach(() => {
vi.restoreAllMocks();
});
it("passes customPrompt to Memory config when provided", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
customPrompt: "Extract only user preferences.",
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
await provider.search("test", { user_id: "u1" });
expect(capturedConfig).toBeDefined();
expect(capturedConfig!.customPrompt).toBe("Extract only user preferences.");
});
});