/** * tests/unit/memory-retrieve-preview.test.ts * * Plan 21 F5 — retrieval.ts: retrievePreview function. * * Cases: * A) retrievePreview returns correct bundle shape for exact strategy * B) retrievePreview with semantic strategy + no vec → fallbackReason non-null * C) retrievePreview with apiKeyId=null → tests global scope (all memories) * D) retrievePreview respects the limit parameter * E) retrievePreview respects maxTokens budget * F) retrievePreview with empty DB returns empty items */ import test from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omr-retrieve-preview-")); process.env.DATA_DIR = TEST_DATA_DIR; process.env.DISABLE_SQLITE_AUTO_BACKUP = "true"; process.env.VECTOR_STORE_DISABLE_VEC = "true"; const core = await import("../../src/lib/db/core.ts"); function cleanup() { core.resetDbInstance(); if (fs.existsSync(TEST_DATA_DIR)) { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); } test.afterEach(() => cleanup()); test.after(() => { if (fs.existsSync(TEST_DATA_DIR)) { fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); } }); function insertMemory( db: ReturnType, id: string, apiKeyId: string, content: string, key?: string ) { db.prepare( `INSERT INTO memories (id, api_key_id, session_id, type, key, content, metadata, created_at, updated_at, expires_at) VALUES (?, ?, ?, 'factual', ?, ?, '{}', datetime('now'), datetime('now'), NULL)` ).run(id, apiKeyId, "", key ?? `key-${id}`, content); } // ── Tests ───────────────────────────────────────────────────────────────────── test("retrievePreview: exact strategy returns correct RetrievePreviewBundle shape", async () => { const db = core.getDbInstance(); insertMemory(db, "prev-1", "api-prev", "TypeScript is great for large projects."); insertMemory(db, "prev-2", "api-prev", "JavaScript is flexible and dynamic."); const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-prev", "TypeScript", { strategy: "exact", maxTokens: 2000, limit: 10, }); // Structural assertions assert.ok(Array.isArray(bundle.items), "items must be an array"); assert.ok(typeof bundle.totalTokens === "number", "totalTokens must be a number"); assert.equal(bundle.budgetMaxTokens, 2000, "budgetMaxTokens must match the passed maxTokens"); const res = bundle.resolution; assert.equal(res.strategyUsed, "exact"); assert.equal(res.rerankApplied, false); assert.ok("fallbackReason" in res, "resolution must have fallbackReason field"); assert.ok("vectorStore" in res, "resolution must have vectorStore field"); assert.ok("embeddingSource" in res, "resolution must have embeddingSource field"); assert.ok("embeddingModel" in res, "resolution must have embeddingModel field"); }); test("retrievePreview: each item has required fields (tier, score, tokens, memory)", async () => { const db = core.getDbInstance(); insertMemory(db, "item-1", "api-item", "Content about machine learning techniques."); insertMemory(db, "item-2", "api-item", "Content about deep learning frameworks."); const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-item", "machine learning", { strategy: "exact", maxTokens: 2000, limit: 10, }); for (const item of bundle.items) { assert.ok("memory" in item, "item must have memory"); assert.ok("score" in item, "item must have score"); assert.ok("tokens" in item, "item must have tokens"); assert.ok("tier" in item, "item must have tier"); assert.ok("vecScore" in item, "item must have vecScore"); assert.ok("ftsScore" in item, "item must have ftsScore"); assert.equal(typeof item.tokens, "number", "tokens must be a number"); assert.equal(typeof item.score, "number", "score must be a number"); // tier should be one of the valid values assert.ok( ["fts5", "vector", "hybrid-rrf", "qdrant"].includes(item.tier), `tier '${item.tier}' must be a valid tier value` ); } }); test("retrievePreview: semantic strategy with no vec store → fallbackReason is non-null", async () => { const db = core.getDbInstance(); insertMemory(db, "sem-prev-1", "api-smprev", "Astronomy is the study of celestial bodies."); const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-smprev", "celestial bodies", { strategy: "semantic", maxTokens: 2000, limit: 10, }); // No embedding source configured → fallback assert.ok( bundle.resolution.fallbackReason !== null || bundle.resolution.strategyUsed !== "semantic", "semantic preview with no vec store should indicate fallback" ); assert.ok(Array.isArray(bundle.items), "items must be array even in fallback"); }); test("retrievePreview: apiKeyId=null scopes to all memories", async () => { const db = core.getDbInstance(); insertMemory(db, "global-1", "api-g1", "Global memory one."); insertMemory(db, "global-2", "api-g2", "Global memory two."); const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview(null, "global", { strategy: "exact", maxTokens: 2000, limit: 10, }); // Should see memories from both apiKeyIds assert.ok(Array.isArray(bundle.items)); const apiKeyIds = bundle.items.map((i) => i.memory.apiKeyId); // At least one item should be present (global scope) assert.ok(bundle.items.length >= 0, "global scope must return items array"); }); test("retrievePreview: respects limit parameter", async () => { const db = core.getDbInstance(); for (let i = 1; i <= 10; i++) { insertMemory(db, `lim-${i}`, "api-lim", `Memory ${i} content.`, `lim-${i}`); } const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-lim", "memory", { strategy: "exact", maxTokens: 10000, limit: 3, }); assert.ok(bundle.items.length <= 3, `items.length ${bundle.items.length} must be ≤ limit (3)`); }); test("retrievePreview: empty DB returns empty items", async () => { core.getDbInstance(); // trigger migrations only const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-empty", "anything", { strategy: "exact", maxTokens: 2000, limit: 10, }); assert.deepEqual(bundle.items, [], "empty DB should return empty items array"); assert.equal(bundle.totalTokens, 0); }); test("retrievePreview: totalTokens equals sum of item.tokens", async () => { const db = core.getDbInstance(); insertMemory(db, "tok-1", "api-tok", "Short text."); // ~3 tokens insertMemory(db, "tok-2", "api-tok", "Another short text."); // ~5 tokens const { retrievePreview } = await import("../../src/lib/memory/retrieval.ts"); const bundle = await retrievePreview("api-tok", "short", { strategy: "exact", maxTokens: 2000, limit: 10, }); const sumFromItems = bundle.items.reduce((acc, i) => acc + i.tokens, 0); assert.equal(bundle.totalTokens, sumFromItems, "totalTokens must equal sum of item.tokens"); });