chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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<typeof core.getDbInstance>,
|
||||
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");
|
||||
});
|
||||
Reference in New Issue
Block a user