// A person gets a bounded number of paid questions a minute, counted in // Redis; and the same library search twice in a minute is answered from // Redis. Neither refuses anything when Redis is absent. const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const Module = require('node:module'); function fakeRedis() { const store = new Map(); return { store, incr: async k => { const v = (store.get(k) || 0) + 1; store.set(k, v); return v; }, expire: async () => 1 }; } function loadLimiter(redis) { const target = require.resolve('../src/middleware/rateLimit'); delete require.cache[target]; const original = Module._load; Module._load = function(request, parent) { if (/utils\/redis$/.test(request)) return { getRedis: async () => redis }; return original.apply(this, arguments); }; try { return require(target); } finally { Module._load = original; } } function run(mw, userId) { return new Promise(resolve => { const res = { headers: {}, set(k, v) { this.headers[k] = v; }, status(c) { this.code = c; return this; }, json(b) { this.body = b; resolve({ res, next: false }); } }; mw({ user: { id: userId } }, res, () => resolve({ res, next: true })); }); } test('the limit counts per user per window and answers 429 with a retry hint past it', async () => { const { rateLimit } = loadLimiter(fakeRedis()); const mw = rateLimit('t', { limit: 2, windowSeconds: 60 }); assert.equal((await run(mw, 1)).next, true); assert.equal((await run(mw, 1)).next, true); const third = await run(mw, 1); assert.equal(third.next, false); assert.equal(third.res.code, 429); assert.equal(third.res.body.code, 'RATE_LIMITED'); assert.ok(Number(third.res.headers['Retry-After']) >= 1); assert.equal((await run(mw, 2)).next, true, 'another user has their own count'); }); test('without Redis nothing is refused', async () => { const { rateLimit } = loadLimiter(null); const mw = rateLimit('t', { limit: 1, windowSeconds: 60 }); assert.equal((await run(mw, 1)).next, true); assert.equal((await run(mw, 1)).next, true); }); test('the paid assistant routes sit behind the limiter, and the library search is cached briefly', () => { const route = fs.readFileSync(path.join(__dirname, '..', 'src/routes/clinicalAssistant.js'), 'utf8'); assert.match(route, /router\.post\(\['\/clinical-assistant\/chat', '\/clinical-assistant\/chat\/stream', '\/clinical-assistant\/image', '\/clinical-assistant\/image\/jobs'\], askLimit\)/); assert.match(route, /rateLimit\('clinical-ask', \{\s*limit: positiveIntEnv\('CLINICAL_ASSISTANT_ASK_LIMIT_PER_MINUTE', 30\), windowSeconds: 60/); assert.match(route, /await cachedSemanticSearch\(searchQuery, \{/); assert.match(route, /RETRIEVAL_CACHE_TTL_S = positiveIntEnv\('CLINICAL_ASSISTANT_RETRIEVAL_CACHE_TTL_S', 60\)/); assert.match(route, /if \(!RETRIEVAL_CACHE_TTL_S \|\| !cache\) return semanticSearch\(query, opts\)/, '0 turns the cache off, and so does a missing cache'); assert.match(route, /cache\.setJson\(key, response, RETRIEVAL_CACHE_TTL_S\)\.catch/, 'a cache write failure never fails the answer'); const client = fs.readFileSync(path.join(__dirname, '..', 'src/utils/clinicalMcpClient.js'), 'utf8'); assert.match(client, /e\.message\s*=|new Error\('The library is busy right now/, 'the busy answer reads as a sentence'); assert.doesNotMatch(client.slice(client.indexOf('function busyError'), client.indexOf('function acquireSlot')), /MCP/, 'so assistantErrorMessage passes it through unchanged'); });