import { expect, test } from "vitest"; import { QDRANT_REQUIRED_INDEXES, reconcileCollection } from "../src/workspaces/qdrant-collection.js"; function fakeRequest(info: any | undefined, { create = true, index = true } = {}) { let current = info; let created = false; return async (url: string, init?: any) => { if (init?.method === "PUT" && /\/index$/.test(url)) { if (!index) return { status: 409, ok: false, json: async () => ({}) } as any; // Simulate the index being created: the collection becomes fully compatible. current = compatible(); return { status: 200, ok: true, json: async () => ({}) } as any; } if (init?.method === "PUT") { if (!create) return { status: 409, ok: false, json: async () => ({}) } as any; created = true; current = compatible(); return { status: 200, ok: true, json: async () => ({}) } as any; } if (current === undefined) return { status: 404, ok: false, json: async () => ({}) } as any; return { status: 200, ok: true, json: async () => ({ result: current }) } as any; }; } const payloadSchema = Object.fromEntries(QDRANT_REQUIRED_INDEXES.map((f) => [f, { data_type: "keyword" }])); const compatible = (size = 1024, distance = "Cosine", schema = payloadSchema) => ({ config: { params: { vectors: { size, distance } } }, payload_schema: schema, }); test("Evidence maintenance adds an absent BM25 vector without changing the dense contract", async () => { const info = compatible(); const requests: Array<{ url: string; init?: any }> = []; const request = async (url: string, init?: any) => { requests.push({ url, init }); if (init?.method === "PUT" && /\/vectors\/bm25$/.test(url)) { expect(JSON.parse(String(init.body))).toEqual({ sparse: { modifier: "idf" } }); (info.config.params as any).sparse_vectors = { bm25: { modifier: "idf" } }; return { status: 200, ok: true, json: async () => ({}) } as any; } return { status: 200, ok: true, json: async () => ({ result: info }) } as any; }; const result = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "evidence_maintenance", request, }); expect(result).toEqual({ ok: true, state: "upgraded" }); expect(info.config.params.vectors).toEqual({ size: 1024, distance: "Cosine" }); expect(requests.filter(({ init }) => init?.method === "PUT")).toHaveLength(1); expect(requests[1]?.url).toBe("http://qdrant:6333/collections/c/vectors/bm25"); }); test("Evidence maintenance creates a missing collection with both required vector contracts", async () => { let info: any; let createdBody: any; const request = async (url: string, init?: any) => { if (init?.method === "PUT") { createdBody = JSON.parse(String(init.body)); info = { config: { params: createdBody }, payload_schema: payloadSchema }; return { status: 200, ok: true, json: async () => ({}) } as any; } if (info === undefined) return { status: 404, ok: false, json: async () => ({}) } as any; return { status: 200, ok: true, json: async () => ({ result: info }) } as any; }; const result = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "evidence_maintenance", request, }); expect(result).toEqual({ ok: true, state: "ready" }); expect(createdBody).toEqual({ vectors: { size: 1024, distance: "Cosine" }, sparse_vectors: { bm25: { modifier: "idf" } }, }); }); test("Evidence maintenance refuses an incompatible BM25 definition without mutating", async () => { const info = compatible(); (info.config.params as any).sparse_vectors = { bm25: { modifier: "none" } }; const requests: Array<{ url: string; init?: any }> = []; const request = async (url: string, init?: any) => { requests.push({ url, init }); return { status: 200, ok: true, json: async () => ({ result: info }) } as any; }; const result = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "evidence_maintenance", request, }); expect(result).toEqual({ ok: false, code: "semantic_index_incompatible" }); expect(requests.filter(({ init }) => init?.method === "PUT")).toEqual([]); }); test("ordinary session reconciliation does not add BM25", async () => { const info = compatible(); const requests: Array<{ url: string; init?: any }> = []; const request = async (url: string, init?: any) => { requests.push({ url, init }); return { status: 200, ok: true, json: async () => ({ result: info }) } as any; }; const result = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "self_heal", request, }); expect(result).toEqual({ ok: true, state: "ready" }); expect(requests.filter(({ url }) => /\/vectors\/bm25$/.test(url))).toEqual([]); }); test("self-heal creates a missing compatible collection", async () => { const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "self_heal", request: fakeRequest(undefined), }); expect(r.ok).toBe(true); }); test("require_existing refuses a missing collection", async () => { const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "require_existing", request: fakeRequest(undefined), }); expect(r).toEqual({ ok: false, code: "semantic_index_incompatible" }); }); test("self-heal adds missing keyword indexes", async () => { const schema = { ...payloadSchema }; delete schema["workspace_revision"]; const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "self_heal", request: fakeRequest(compatible(1024, "Cosine", schema)), }); expect(r).toMatchObject({ ok: true, state: "repaired" }); }); test("refuses incompatible dimensions or distance without mutating", async () => { for (const info of [compatible(768, "Cosine"), compatible(1024, "Dot")]) { const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "self_heal", request: fakeRequest(info), }); expect(r).toEqual({ ok: false, code: "semantic_index_incompatible" }); } }); test("self-heal creates with the Qdrant-valid distance enum", async () => { let createdBody: any; const base = fakeRequest(undefined); const request = async (url: string, init?: any) => { if (init?.method === "PUT" && !/\/index$/.test(url)) createdBody = JSON.parse(String(init.body)); return base(url, init); }; const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "self_heal", request, }); expect(r.ok).toBe(true); expect(createdBody.vectors.distance).toBe("Cosine"); }); test("ready compatible collection passes", async () => { const r = await reconcileCollection({ baseUrl: "http://qdrant:6333", collection: "c", dimensions: 1024, distance: "cosine", mode: "require_existing", request: fakeRequest(compatible()), }); expect(r).toMatchObject({ ok: true, state: "ready" }); });