import { describe, it, expect, vi } from "vitest"; vi.mock("../src/logger.js", () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() }, })); import { VectorIndex } from "../src/state/vector-index.js"; import { migrateVectorIndex } from "../src/functions/migrate-vector-index.js"; import type { EmbeddingProvider } from "../src/types.js"; describe("VectorIndex.validateDimensions", () => { it("reports no mismatches and an empty dim set on an empty index", () => { const result = new VectorIndex().validateDimensions(384); expect(result.mismatches).toEqual([]); expect(Array.from(result.seenDimensions)).toEqual([]); }); it("reports no mismatches when every vector matches the expected dimension", () => { const idx = new VectorIndex(); idx.add("o1", "s1", new Float32Array(384)); idx.add("o2", "s1", new Float32Array(384)); const result = idx.validateDimensions(384); expect(result.mismatches).toEqual([]); expect(Array.from(result.seenDimensions)).toEqual([384]); }); it("reports every wrong-dimension vector, not just the first", () => { const idx = new VectorIndex(); idx.add("good1", "s1", new Float32Array(384)); idx.add("bad1", "s1", new Float32Array(1536)); idx.add("good2", "s1", new Float32Array(384)); idx.add("bad2", "s1", new Float32Array(768)); const result = idx.validateDimensions(384); expect(result.mismatches).toHaveLength(2); expect(result.mismatches.map((m) => m.obsId).sort()).toEqual(["bad1", "bad2"]); expect(Array.from(result.seenDimensions).sort((a, b) => a - b)).toEqual([ 384, 768, 1536, ]); }); it("flags every entry when the entire index has the wrong dimension", () => { const idx = new VectorIndex(); idx.add("o1", "s1", new Float32Array(384)); idx.add("o2", "s1", new Float32Array(384)); const result = idx.validateDimensions(1536); expect(result.mismatches).toHaveLength(2); expect(Array.from(result.seenDimensions)).toEqual([384]); }); }); describe("migrateVectorIndex", () => { const newProvider: EmbeddingProvider = { name: "test-4d", dimensions: 4, embed: async (_text: string) => new Float32Array([0.1, 0.2, 0.3, 0.4]), embedBatch: async (_texts: string[]) => _texts.map(() => new Float32Array([0.1, 0.2, 0.3, 0.4])), }; function mockKV() { const store = new Map>(); return { get: async (scope: string, key: string): Promise => (store.get(scope)?.get(key) as T) ?? null, set: async (scope: string, key: string, data: T): Promise => { if (!store.has(scope)) store.set(scope, new Map()); store.get(scope)!.set(key, data); return data; }, delete: async (_scope: string, _key: string): Promise => {}, list: async (scope: string): Promise => { const entries = store.get(scope); return entries ? (Array.from(entries.values()) as T[]) : []; }, }; } it("re-embeds observations with new provider dimensions", async () => { const kv = mockKV(); await kv.set("mem:sessions", "ses_1", { id: "ses_1" }); await kv.set("mem:obs:ses_1", "obs_1", { id: "obs_1", sessionId: "ses_1", timestamp: new Date().toISOString(), type: "decision", title: "migration test", facts: ["test"], narrative: "Testing migration", concepts: ["test"], files: [], importance: 5, }); const result = await migrateVectorIndex(kv as never, newProvider); expect(result.success).toBe(true); expect(result.totalProcessed).toBe(1); expect(result.vectorSize).toBe(1); expect(result.failed).toBe(0); }); it("re-embeds memories with new provider dimensions", async () => { const kv = mockKV(); await kv.set("mem:memories", "mem_1", { id: "mem_1", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), type: "fact", title: "migration memory", content: "This memory will be re-embedded", concepts: ["test"], files: [], sessionIds: ["ses_1"], strength: 7, version: 1, isLatest: true, }); const result = await migrateVectorIndex(kv as never, newProvider); expect(result.totalProcessed).toBe(1); expect(result.vectorSize).toBe(1); }); it("handles empty KV gracefully", async () => { const kv = mockKV(); const result = await migrateVectorIndex(kv as never, newProvider); expect(result.success).toBe(true); expect(result.totalProcessed).toBe(0); expect(result.vectorSize).toBe(0); expect(result.failed).toBe(0); }); });