test: add vector dedupe regression coverage
This commit is contained in:
@@ -10,13 +10,13 @@ Docs: https://docs.clawd.bot
|
||||
- Swabble: use the tagged Commander Swift package release.
|
||||
- CLI: add `clawdbot acp client` interactive ACP harness for debugging.
|
||||
- Plugins: route command detection/text chunking helpers through the plugin runtime and drop runtime exports from the SDK.
|
||||
- Memory: add native Gemini embeddings provider for memory search. (#1151) — thanks @steipete.
|
||||
- Memory: add native Gemini embeddings provider for memory search. (#1151)
|
||||
|
||||
### Fixes
|
||||
- Auth profiles: keep auto-pinned preference while allowing rotation on failover; user pins stay locked. (#1138) — thanks @cheeeee.
|
||||
- macOS: avoid touching launchd in Remote over SSH so quitting the app no longer disables the remote gateway. (#1105)
|
||||
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151) — thanks @steipete.
|
||||
- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151) — thanks @steipete.
|
||||
- Memory: index atomically so failed reindex preserves the previous memory database. (#1151)
|
||||
- Memory: avoid sqlite-vec unique constraint failures when reindexing duplicate chunk ids. (#1151)
|
||||
|
||||
## 2026.1.18-3
|
||||
|
||||
|
||||
93
src/memory/manager.vector-dedupe.test.ts
Normal file
93
src/memory/manager.vector-dedupe.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
|
||||
import { buildFileEntry } from "./internal.js";
|
||||
|
||||
vi.mock("./embeddings.js", () => {
|
||||
return {
|
||||
createEmbeddingProvider: async () => ({
|
||||
requestedProvider: "openai",
|
||||
provider: {
|
||||
id: "mock",
|
||||
model: "mock-embed",
|
||||
embedQuery: async () => [0.1, 0.2, 0.3],
|
||||
embedBatch: async (texts: string[]) => texts.map((_, index) => [index + 1, 0, 0]),
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe("memory vector dedupe", () => {
|
||||
let workspaceDir: string;
|
||||
let indexPath: string;
|
||||
let manager: MemoryIndexManager | null = null;
|
||||
|
||||
beforeEach(async () => {
|
||||
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-mem-"));
|
||||
indexPath = path.join(workspaceDir, "index.sqlite");
|
||||
await fs.mkdir(path.join(workspaceDir, "memory"));
|
||||
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "Hello memory.");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
if (manager) {
|
||||
await manager.close();
|
||||
manager = null;
|
||||
}
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("deletes existing vector rows before inserting replacements", async () => {
|
||||
const cfg = {
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: workspaceDir,
|
||||
memorySearch: {
|
||||
provider: "openai",
|
||||
model: "mock-embed",
|
||||
store: { path: indexPath, vector: { enabled: true } },
|
||||
sync: { watch: false, onSessionStart: false, onSearch: false },
|
||||
cache: { enabled: false },
|
||||
},
|
||||
},
|
||||
list: [{ id: "main", default: true }],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await getMemorySearchManager({ cfg, agentId: "main" });
|
||||
expect(result.manager).not.toBeNull();
|
||||
if (!result.manager) throw new Error("manager missing");
|
||||
manager = result.manager;
|
||||
|
||||
const db = (manager as unknown as { db: { exec: (sql: string) => void; prepare: (sql: string) => unknown } }).db;
|
||||
db.exec("CREATE TABLE IF NOT EXISTS chunks_vec (id TEXT PRIMARY KEY, embedding BLOB)");
|
||||
|
||||
const sqlSeen: string[] = [];
|
||||
const originalPrepare = db.prepare.bind(db);
|
||||
db.prepare = (sql: string) => {
|
||||
if (sql.includes("chunks_vec")) {
|
||||
sqlSeen.push(sql);
|
||||
}
|
||||
return originalPrepare(sql);
|
||||
};
|
||||
|
||||
(manager as unknown as { ensureVectorReady: (dims?: number) => Promise<boolean> }).ensureVectorReady =
|
||||
async () => true;
|
||||
|
||||
const entry = await buildFileEntry(path.join(workspaceDir, "MEMORY.md"), workspaceDir);
|
||||
await (manager as unknown as { indexFile: (entry: unknown, options: { source: "memory" }) => Promise<void> }).indexFile(
|
||||
entry,
|
||||
{ source: "memory" },
|
||||
);
|
||||
|
||||
const deleteIndex = sqlSeen.findIndex((sql) => sql.includes("DELETE FROM chunks_vec WHERE id = ?"));
|
||||
const insertIndex = sqlSeen.findIndex((sql) => sql.includes("INSERT INTO chunks_vec"));
|
||||
expect(deleteIndex).toBeGreaterThan(-1);
|
||||
expect(insertIndex).toBeGreaterThan(-1);
|
||||
expect(deleteIndex).toBeLessThan(insertIndex);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user