Files
OmniRoute/tests/unit/retrieval-scoring-split.test.ts
Diego Rodrigues de Sa e Souza f60fd398b6 refactor(memory): extract pure scoring/conversion helpers from retrieval.ts (#5733)
retrieval.ts (1192 lines — ABOVE its 1171 frozen baseline) is the memory
retrieval engine (DB + vector + rerank network). Extract the pure, DB-free
scoring/conversion helpers (+ the MemoryRow row shape they share) verbatim into
a self-contained leaf, leaving all DB/vector/network code in the host:

  - retrieval/scoring.ts (104)  interface MemoryRow + estimateTokens,
    parseMetadata, rowToMemory, getRelevanceScore

Host retrieval.ts: 1192 -> 1072 — back UNDER the 1171 frozen baseline (the split
also repairs the pre-existing file-size drift). The leaf imports only ../types,
never the host, so check:cycles stays clean (no cycle). MemoryRow moved to the
leaf and imported back as a type by the host's DB row functions. The public
estimateTokens is re-exported from the leaf; the host also imports it for its
internal token-budget loops. The other three helpers were module-internal, so
the public API is unchanged (7 exports). Bodies moved byte-identical.

Behavior-preserving: 38 existing memory-retrieval consumer tests stay green
(rerank 5, hybrid 6, semantic 6, engine-status 9, stats-api 12); new
tests/unit/retrieval-scoring-split.test.ts (11 assertions) pins
estimateTokens (ceil(len/4)) + parseMetadata + rowToMemory mapping +
getRelevanceScore (+20 phrase / +3 token) and guards the public API.

Refs #3501.
2026-06-30 20:33:38 -03:00

112 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Characterization + API-surface test: retrieval.ts god-file decomposition.
*
* The pure, DB-free scoring/conversion helpers (+ the MemoryRow row shape) were
* extracted verbatim from src/lib/memory/retrieval.ts into the self-contained
* leaf src/lib/memory/retrieval/scoring.ts (imports only ../types — does NOT
* import the host, so no cycle). The DB/vector/rerank engine stays in the host.
*
* Verifies that:
* 1. estimateTokens / parseMetadata / rowToMemory / getRelevanceScore behave
* correctly (pure formulas pinned).
* 2. The host retrieval.ts still exposes the FULL public API (7 names; the
* public estimateTokens is now re-exported from the leaf).
* 3. The scoring leaf exports its pieces directly.
*
* Pure value assertions — no DB handle is opened.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
estimateTokens,
parseMetadata,
rowToMemory,
getRelevanceScore,
} from "../../src/lib/memory/retrieval/scoring.ts";
describe("retrieval/scoring — estimateTokens (~1 token / 4 chars)", () => {
it("returns 0 for empty / non-string", () => {
assert.equal(estimateTokens(""), 0);
assert.equal(estimateTokens(undefined as unknown as string), 0);
});
it("ceils length/4", () => {
assert.equal(estimateTokens("abc"), 1); // ceil(3/4)
assert.equal(estimateTokens("12345678"), 2); // ceil(8/4)
assert.equal(estimateTokens("123456789"), 3); // ceil(9/4)
});
});
describe("retrieval/scoring — parseMetadata", () => {
it("returns {} for non-string / blank / invalid JSON", () => {
assert.deepEqual(parseMetadata(null), {});
assert.deepEqual(parseMetadata(""), {});
assert.deepEqual(parseMetadata("not json"), {});
});
it("parses a JSON object", () => {
assert.deepEqual(parseMetadata('{"a":1,"b":"x"}'), { a: 1, b: "x" });
});
});
describe("retrieval/scoring — rowToMemory", () => {
const mem = rowToMemory({
id: "m1",
type: "fact" as never,
content: "hello world",
metadata: '{"topic":"greeting"}',
});
it("maps content + parsed metadata + defaults accessCount to 0", () => {
assert.equal(mem.content, "hello world");
assert.deepEqual(mem.metadata, { topic: "greeting" });
assert.equal(mem.accessCount, 0);
assert.equal(mem.key, "");
});
});
describe("retrieval/scoring — getRelevanceScore (literal string match, no RegExp)", () => {
const mem = rowToMemory({
id: "m1",
type: "fact" as never,
content: "hello world",
metadata: '{"topic":"greeting"}',
});
it("returns 0 for a blank query", () => {
assert.equal(getRelevanceScore(mem, ""), 0);
assert.equal(getRelevanceScore(mem, " "), 0);
});
it("scores +20 for a full-phrase hit and +3 per token occurrence", () => {
// content "hello world": phrase +20, 1× "hello" +3 = 23
assert.equal(getRelevanceScore(mem, "hello"), 23);
// metadata holds "greeting": phrase +20, 1× "greeting" +3 = 23
assert.equal(getRelevanceScore(mem, "greeting"), 23);
});
it("returns 0 when nothing matches", () => {
assert.equal(getRelevanceScore(mem, "zzz"), 0);
});
});
// ── host public API surface ──────────────────────────────────────────────────
const host = await import("../../src/lib/memory/retrieval.ts");
describe("retrieval.ts public API surface (7 names)", () => {
it("re-exports estimateTokens (now sourced from the leaf) as a function", () => {
assert.equal(typeof host.estimateTokens, "function");
});
it("keeps the three engine entrypoints as functions", () => {
assert.equal(typeof host.retrieveMemories, "function");
assert.equal(typeof host.retrievePreview, "function");
assert.equal(typeof host.engineStatus, "function");
});
});
describe("scoring.ts exports its pieces directly", () => {
it("the moved helpers are functions on the leaf", async () => {
const s = await import("../../src/lib/memory/retrieval/scoring.ts");
for (const fn of ["estimateTokens", "parseMetadata", "rowToMemory", "getRelevanceScore"]) {
assert.equal(typeof s[fn], "function", fn);
}
});
});