const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const root = path.join(__dirname, '..'); const read = file => fs.readFileSync(path.join(root, file), 'utf8'); // Load the module with its two dependencies replaced, so these tests exercise // the budgeting and failure handling without a Milvus behind them. function load(mocks) { const module = { exports: {} }; vm.runInNewContext(read('src/utils/learningRetrieval.js'), { module, exports: module.exports, console: { warn() {}, info() {} }, require(name) { if (Object.hasOwn(mocks, name)) return mocks[name]; throw new Error('unexpected import: ' + name); } }); return module.exports; } const passthrough = { './clinicalRetrieval': { normalizeMcpSearchResponse: r => r.results || [], dedupeSources: s => s, cleanSourceExcerpt: t => String(t || '') } }; test('My Resources retrieves with its own budget, not the assistant’s', async () => { let captured = null; const lib = load({ ...passthrough, './clinicalMcpClient': { semanticSearch: async (q, opts) => { captured = { q, opts }; return { results: [] }; } } }); // A chat answer wants a few tight excerpts because the reader is waiting. A // teaching resource synthesises a whole topic, so it wants many more and // longer ones. The assistant's defaults are 8 and 1400. assert.equal(lib.DEFAULTS.limit, 30); assert.equal(lib.DEFAULTS.contextChars, 2500); await lib.retrieve('bronchiolitis', async () => null); assert.equal(captured.q, 'bronchiolitis'); assert.equal(captured.opts.limit, 30); assert.equal(captured.opts.contextChars, 2500); assert.equal(captured.opts.includeContext, true); }); test('the budget is settable but bounded', async () => { let captured = null; const lib = load({ ...passthrough, './clinicalMcpClient': { semanticSearch: async (q, opts) => { captured = opts; return { results: [] }; } } }); const settings = { 'learning.search_limit': '999', 'learning.context_chars': '999999' }; await lib.retrieve('topic', async key => settings[key]); // "No limit" only moves the ceiling from a setting to the model's context // window, where overflow truncates the middle of the prompt silently. assert.equal(captured.limit, lib.BOUNDS.limit[1]); assert.equal(captured.contextChars, lib.BOUNDS.contextChars[1]); const low = { 'learning.search_limit': '0', 'learning.context_chars': '1' }; await lib.retrieve('topic', async key => low[key]); assert.equal(captured.limit, lib.BOUNDS.limit[0]); assert.equal(captured.contextChars, lib.BOUNDS.contextChars[0]); }); test('retrieval failing never fails the generation', async () => { const lib = load({ ...passthrough, './clinicalMcpClient': { semanticSearch: async () => { throw new Error('MCP unreachable'); } } }); // Writing the resource from the model alone is what happened before this // existed, and is a far better outcome than an error page. const out = await lib.retrieve('bronchiolitis', async () => null); // Length, not deepEqual: the array is created inside the VM realm, so its // prototype is not this realm's Array and a structural compare fails. assert.equal(out.sources.length, 0); assert.equal(out.context, ''); assert.match(out.reason, /MCP unreachable/, 'and the caller is told why'); }); test('an empty corpus result is reported, not silently passed off as grounded', async () => { const lib = load({ ...passthrough, './clinicalMcpClient': { semanticSearch: async () => ({ results: [] }) } }); const out = await lib.retrieve('something nobody indexed', async () => null); assert.equal(out.context, ''); assert.match(out.reason, /nothing indexed matched/); }); test('excerpts are numbered the way the assistant numbers them', async () => { const lib = load({ ...passthrough, './clinicalMcpClient': { semanticSearch: async () => ({ results: [ { number: 1, title: 'Bronchiolitis chapter', page: 12, excerpt: 'Supportive care.' }, { number: 2, title: 'RSV guidance', excerpt: 'Peak 3-6 months.' } ] }) } }); const out = await lib.retrieve('bronchiolitis', async () => null); assert.equal(out.sources.length, 2); assert.match(out.context, /^\[1\] Bronchiolitis chapter, page 12\nSupportive care\./); assert.match(out.context, /\[2\] RSV guidance\nPeak 3-6 months\./); assert.equal(out.reason, null); });