diff --git a/extensions/memory-lancedb/index.test.ts b/extensions/memory-lancedb/index.test.ts index 18fe30791..1d0923528 100644 --- a/extensions/memory-lancedb/index.test.ts +++ b/extensions/memory-lancedb/index.test.ts @@ -14,11 +14,12 @@ import fs from "node:fs/promises"; import path from "node:path"; import os from "node:os"; -// Skip if no OpenAI API key -const OPENAI_API_KEY = process.env.OPENAI_API_KEY; -const describeWithKey = OPENAI_API_KEY ? describe : describe.skip; +const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "test-key"; +const HAS_OPENAI_KEY = Boolean(process.env.OPENAI_API_KEY); +const liveEnabled = HAS_OPENAI_KEY && process.env.CLAWDBOT_LIVE_TEST === "1"; +const describeLive = liveEnabled ? describe : describe.skip; -describeWithKey("memory plugin e2e", () => { +describe("memory plugin e2e", () => { let tmpDir: string; let dbPath: string; @@ -159,7 +160,7 @@ describeWithKey("memory plugin e2e", () => { }); // Live tests that require OpenAI API key and actually use LanceDB -describeWithKey("memory plugin live tests", () => { +describeLive("memory plugin live tests", () => { let tmpDir: string; let dbPath: string; @@ -176,6 +177,7 @@ describeWithKey("memory plugin live tests", () => { test("memory tools work end-to-end", async () => { const { default: memoryPlugin } = await import("./index.js"); + const liveApiKey = process.env.OPENAI_API_KEY ?? ""; // Mock plugin API const registeredTools: any[] = []; @@ -191,7 +193,7 @@ describeWithKey("memory plugin live tests", () => { config: {}, pluginConfig: { embedding: { - apiKey: OPENAI_API_KEY, + apiKey: liveApiKey, model: "text-embedding-3-small", }, dbPath, diff --git a/src/memory/manager.batch.test.ts b/src/memory/manager.batch.test.ts index 643f39baf..9c9745e4a 100644 --- a/src/memory/manager.batch.test.ts +++ b/src/memory/manager.batch.test.ts @@ -30,6 +30,7 @@ describe("memory indexing with OpenAI batches", () => { let workspaceDir: string; let indexPath: string; let manager: MemoryIndexManager | null = null; + let setTimeoutSpy: ReturnType; beforeEach(async () => { embedBatch.mockClear(); @@ -37,6 +38,18 @@ describe("memory indexing with OpenAI batches", () => { embedBatch.mockImplementation(async (texts: string[]) => texts.map((_text, index) => [index + 1, 0, 0]), ); + const realSetTimeout = setTimeout; + setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((( + handler: TimerHandler, + timeout?: number, + ...args: unknown[] + ) => { + const delay = typeof timeout === "number" ? timeout : 0; + if (delay > 0 && delay <= 2000) { + return realSetTimeout(handler, 0, ...args); + } + return realSetTimeout(handler, delay, ...args); + }) as typeof setTimeout); workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-mem-batch-")); indexPath = path.join(workspaceDir, "index.sqlite"); await fs.mkdir(path.join(workspaceDir, "memory")); @@ -44,6 +57,7 @@ describe("memory indexing with OpenAI batches", () => { afterEach(async () => { vi.unstubAllGlobals(); + setTimeoutSpy.mockRestore(); if (manager) { await manager.close(); manager = null; diff --git a/src/memory/manager.embedding-batches.test.ts b/src/memory/manager.embedding-batches.test.ts index fc786be90..f2f08f16c 100644 --- a/src/memory/manager.embedding-batches.test.ts +++ b/src/memory/manager.embedding-batches.test.ts @@ -44,7 +44,7 @@ describe("memory embedding batches", () => { it("splits large files across multiple embedding batches", async () => { const line = "a".repeat(200); - const content = Array.from({ length: 200 }, () => line).join("\n"); + const content = Array.from({ length: 50 }, () => line).join("\n"); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-03.md"), content); const cfg = { @@ -78,7 +78,7 @@ describe("memory embedding batches", () => { it("keeps small files in a single embedding batch", async () => { const line = "b".repeat(120); - const content = Array.from({ length: 12 }, () => line).join("\n"); + const content = Array.from({ length: 4 }, () => line).join("\n"); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-04.md"), content); const cfg = { @@ -109,7 +109,7 @@ describe("memory embedding batches", () => { it("reports sync progress totals", async () => { const line = "c".repeat(120); - const content = Array.from({ length: 20 }, () => line).join("\n"); + const content = Array.from({ length: 8 }, () => line).join("\n"); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-05.md"), content); const cfg = { @@ -150,7 +150,7 @@ describe("memory embedding batches", () => { it("retries embeddings on rate limit errors", async () => { const line = "d".repeat(120); - const content = Array.from({ length: 12 }, () => line).join("\n"); + const content = Array.from({ length: 4 }, () => line).join("\n"); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-06.md"), content); let calls = 0; @@ -162,6 +162,19 @@ describe("memory embedding batches", () => { return texts.map(() => [0, 1, 0]); }); + const realSetTimeout = setTimeout; + const setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((( + handler: TimerHandler, + timeout?: number, + ...args: unknown[] + ) => { + const delay = typeof timeout === "number" ? timeout : 0; + if (delay > 0 && delay <= 2000) { + return realSetTimeout(handler, 0, ...args); + } + return realSetTimeout(handler, delay, ...args); + }) as typeof setTimeout); + const cfg = { agents: { defaults: { @@ -183,15 +196,18 @@ describe("memory embedding batches", () => { expect(result.manager).not.toBeNull(); if (!result.manager) throw new Error("manager missing"); manager = result.manager; - - await manager.sync({ force: true }); + try { + await manager.sync({ force: true }); + } finally { + setTimeoutSpy.mockRestore(); + } expect(calls).toBe(3); }, 10000); it("retries embeddings on transient 5xx errors", async () => { const line = "e".repeat(120); - const content = Array.from({ length: 12 }, () => line).join("\n"); + const content = Array.from({ length: 4 }, () => line).join("\n"); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-08.md"), content); let calls = 0; @@ -203,6 +219,19 @@ describe("memory embedding batches", () => { return texts.map(() => [0, 1, 0]); }); + const realSetTimeout = setTimeout; + const setTimeoutSpy = vi.spyOn(global, "setTimeout").mockImplementation((( + handler: TimerHandler, + timeout?: number, + ...args: unknown[] + ) => { + const delay = typeof timeout === "number" ? timeout : 0; + if (delay > 0 && delay <= 2000) { + return realSetTimeout(handler, 0, ...args); + } + return realSetTimeout(handler, delay, ...args); + }) as typeof setTimeout); + const cfg = { agents: { defaults: { @@ -224,8 +253,11 @@ describe("memory embedding batches", () => { expect(result.manager).not.toBeNull(); if (!result.manager) throw new Error("manager missing"); manager = result.manager; - - await manager.sync({ force: true }); + try { + await manager.sync({ force: true }); + } finally { + setTimeoutSpy.mockRestore(); + } expect(calls).toBe(3); }, 10000);