test(memory): speed up batch coverage
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user