test(memory): speed up batch coverage

This commit is contained in:
Peter Steinberger
2026-01-23 06:21:47 +00:00
parent d4db45e8a9
commit 070944f64f
3 changed files with 63 additions and 15 deletions

View File

@@ -14,11 +14,12 @@ import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import os from "node:os"; import os from "node:os";
// Skip if no OpenAI API key const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? "test-key";
const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const HAS_OPENAI_KEY = Boolean(process.env.OPENAI_API_KEY);
const describeWithKey = OPENAI_API_KEY ? describe : describe.skip; 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 tmpDir: string;
let dbPath: string; let dbPath: string;
@@ -159,7 +160,7 @@ describeWithKey("memory plugin e2e", () => {
}); });
// Live tests that require OpenAI API key and actually use LanceDB // 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 tmpDir: string;
let dbPath: string; let dbPath: string;
@@ -176,6 +177,7 @@ describeWithKey("memory plugin live tests", () => {
test("memory tools work end-to-end", async () => { test("memory tools work end-to-end", async () => {
const { default: memoryPlugin } = await import("./index.js"); const { default: memoryPlugin } = await import("./index.js");
const liveApiKey = process.env.OPENAI_API_KEY ?? "";
// Mock plugin API // Mock plugin API
const registeredTools: any[] = []; const registeredTools: any[] = [];
@@ -191,7 +193,7 @@ describeWithKey("memory plugin live tests", () => {
config: {}, config: {},
pluginConfig: { pluginConfig: {
embedding: { embedding: {
apiKey: OPENAI_API_KEY, apiKey: liveApiKey,
model: "text-embedding-3-small", model: "text-embedding-3-small",
}, },
dbPath, dbPath,

View File

@@ -30,6 +30,7 @@ describe("memory indexing with OpenAI batches", () => {
let workspaceDir: string; let workspaceDir: string;
let indexPath: string; let indexPath: string;
let manager: MemoryIndexManager | null = null; let manager: MemoryIndexManager | null = null;
let setTimeoutSpy: ReturnType<typeof vi.spyOn>;
beforeEach(async () => { beforeEach(async () => {
embedBatch.mockClear(); embedBatch.mockClear();
@@ -37,6 +38,18 @@ describe("memory indexing with OpenAI batches", () => {
embedBatch.mockImplementation(async (texts: string[]) => embedBatch.mockImplementation(async (texts: string[]) =>
texts.map((_text, index) => [index + 1, 0, 0]), 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-")); workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-mem-batch-"));
indexPath = path.join(workspaceDir, "index.sqlite"); indexPath = path.join(workspaceDir, "index.sqlite");
await fs.mkdir(path.join(workspaceDir, "memory")); await fs.mkdir(path.join(workspaceDir, "memory"));
@@ -44,6 +57,7 @@ describe("memory indexing with OpenAI batches", () => {
afterEach(async () => { afterEach(async () => {
vi.unstubAllGlobals(); vi.unstubAllGlobals();
setTimeoutSpy.mockRestore();
if (manager) { if (manager) {
await manager.close(); await manager.close();
manager = null; manager = null;

View File

@@ -44,7 +44,7 @@ describe("memory embedding batches", () => {
it("splits large files across multiple embedding batches", async () => { it("splits large files across multiple embedding batches", async () => {
const line = "a".repeat(200); 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); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-03.md"), content);
const cfg = { const cfg = {
@@ -78,7 +78,7 @@ describe("memory embedding batches", () => {
it("keeps small files in a single embedding batch", async () => { it("keeps small files in a single embedding batch", async () => {
const line = "b".repeat(120); 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); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-04.md"), content);
const cfg = { const cfg = {
@@ -109,7 +109,7 @@ describe("memory embedding batches", () => {
it("reports sync progress totals", async () => { it("reports sync progress totals", async () => {
const line = "c".repeat(120); 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); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-05.md"), content);
const cfg = { const cfg = {
@@ -150,7 +150,7 @@ describe("memory embedding batches", () => {
it("retries embeddings on rate limit errors", async () => { it("retries embeddings on rate limit errors", async () => {
const line = "d".repeat(120); 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); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-06.md"), content);
let calls = 0; let calls = 0;
@@ -162,6 +162,19 @@ describe("memory embedding batches", () => {
return texts.map(() => [0, 1, 0]); 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 = { const cfg = {
agents: { agents: {
defaults: { defaults: {
@@ -183,15 +196,18 @@ describe("memory embedding batches", () => {
expect(result.manager).not.toBeNull(); expect(result.manager).not.toBeNull();
if (!result.manager) throw new Error("manager missing"); if (!result.manager) throw new Error("manager missing");
manager = result.manager; manager = result.manager;
try {
await manager.sync({ force: true }); await manager.sync({ force: true });
} finally {
setTimeoutSpy.mockRestore();
}
expect(calls).toBe(3); expect(calls).toBe(3);
}, 10000); }, 10000);
it("retries embeddings on transient 5xx errors", async () => { it("retries embeddings on transient 5xx errors", async () => {
const line = "e".repeat(120); 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); await fs.writeFile(path.join(workspaceDir, "memory", "2026-01-08.md"), content);
let calls = 0; let calls = 0;
@@ -203,6 +219,19 @@ describe("memory embedding batches", () => {
return texts.map(() => [0, 1, 0]); 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 = { const cfg = {
agents: { agents: {
defaults: { defaults: {
@@ -224,8 +253,11 @@ describe("memory embedding batches", () => {
expect(result.manager).not.toBeNull(); expect(result.manager).not.toBeNull();
if (!result.manager) throw new Error("manager missing"); if (!result.manager) throw new Error("manager missing");
manager = result.manager; manager = result.manager;
try {
await manager.sync({ force: true }); await manager.sync({ force: true });
} finally {
setTimeoutSpy.mockRestore();
}
expect(calls).toBe(3); expect(calls).toBe(3);
}, 10000); }, 10000);