import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { normalizeNeed, classify, NeedLog } from '../src/needlog.js';
import { VERSION_STR } from '../src/version.js';
import { ENGINE_VERSION } from '../src/engine.js';
import { makeApp, BASE } from './helpers.js';
function fakeCounter() {
const rows = new Map(); const calls = { add: 0 };
const stub = {
async addNeeds(p) { calls.add++; for (const [d, qs] of Object.entries(p)) for (const [q, c] of Object.entries(qs)) { const k = `${d}|${q}`; const e = rows.get(k) || { n: 0, no_match: 0, low_match: 0, no_ours: 0 }; for (const f of Object.keys(e)) e[f] += c[f]; rows.set(k, e); } },
async topNeeds(since) { const m = new Map(); for (const [k, e] of rows) { const [d, q] = k.split('|'); if (d < since) continue; const x = m.get(q) || { q, n: 0, no_match: 0, low_match: 0, no_ours: 0 }; for (const f of ['n', 'no_match', 'low_match', 'no_ours']) x[f] += e[f]; m.set(q, x); } return [...m.values()]; },
};
return { idFromName: () => 'id', get: () => stub, rows, calls };
}
test('single version string: 0.1.2 in version.js, engine label and package.json', () => {
assert.equal(VERSION_STR, '0.1.2');
assert.equal(ENGINE_VERSION, 'toolpick-0.1.2');
assert.equal(JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version, VERSION_STR);
});
test('normalizeNeed: lowercase, strips emails, URLs, addresses, keys and long numbers, caps at 120', () => {
assert.equal(normalizeNeed(' Web SEARCH '), 'web search');
assert.equal(normalizeNeed('email alice@example.com about https://evil.example/x?a=1 now'), 'email about now');
assert.equal(normalizeNeed('check 0x3c1A1E01e790Cd326495631B3b38Bd7068F08d6F balance'), 'check balance');
assert.equal(normalizeNeed('sol FH73WbbUe2UyNzfy72MJs5nT3NxyDf9GrqE7kzjL2Jte risk'), 'sol risk');
assert.equal(normalizeNeed('call +1 555 123 4567 or id 123456789'), 'call + or id');
assert.equal(normalizeNeed('lookup vitalik.eth and www.foo.io/page'), 'lookup and');
assert.equal(normalizeNeed('key sk_live_ABCDEFGHIJKLMNOPQRSTUVWX12 leak'), 'key leak');
assert.equal(normalizeNeed('weather in 2026'), 'weather in 2026');
const long = normalizeNeed('word '.repeat(60));
assert.ok(long.length <= 120 && !long.endsWith(' '));
assert.equal(normalizeNeed('a'), '');
});
test('classify: no match, weak match, none of ours', () => {
assert.deepEqual(classify(null, { code: 'no_matches' }), { no_match: 1, low_match: 0, no_ours: 1 });
assert.equal(classify(null, { code: 'index_not_ready' }), null);
assert.deepEqual(classify({ results: [{ relevance: 0.3 }] }), { no_match: 0, low_match: 1, no_ours: 1 });
assert.deepEqual(classify({ results: [{ relevance: 0.9, ours: true }] }), { no_match: 0, low_match: 0, no_ours: 0 });
});
test('NeedLog batches aggregate counts only and top() applies the min-count floor', async () => {
const counter = fakeCounter(); let t = Date.parse('2026-10-23T12:00:00Z');
const log = new NeedLog(counter, { flushMs: 0, now: () => t });
for (let i = 0; i < 3; i++) log.record('lite', 'Satellite imagery for 0x3c1A1E01e790Cd326495631B3b38Bd7068F08d6F', null, { code: 'no_matches' });
log.record('pick', 'web search', { results: [{ relevance: 0.9, ours: false }] });
log.record('market', 'ignored', null, { code: 'no_matches' });
await new Promise((r) => setTimeout(r, 5));
const stored = JSON.stringify([...counter.rows]);
assert.doesNotMatch(stored, /0x3c1a|ip|wallet/i);
const top = await log.top({});
assert.deepEqual(top.needs.map((x) => x.need), ['satellite imagery for']);
assert.equal(top.needs[0].no_match, 3); assert.equal(top.needs[0].unmet, 3);
assert.equal((await log.top({ minCount: 1 })).needs.length, 2);
});
test('app: paid/preview/MCP queries are logged (smoke excluded); 400 still before the paywall; bare probe 402; /v1/needs free', async () => {
const counter = fakeCounter(); const needLog = new NeedLog(counter, { flushMs: 0 });
const { fetchApp } = await makeApp({ appOpts: { needLog } });
assert.equal((await fetchApp(`${BASE}/v1/lite?need=`)).status, 400);
assert.equal((await fetchApp(`${BASE}/v1/pick`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{"need":""}' })).status, 400);
assert.equal((await fetchApp(`${BASE}/v1/lite`)).status, 402);
assert.equal((await fetchApp(`${BASE}/v1/lite?need=web%20search`)).status, 402); // unpaid: never reaches the engine, not logged
for (let i = 0; i < 3; i++) await fetchApp(`${BASE}/v1/preview?need=quantum%20weather%20oracle%20zzz`);
await fetchApp(`${BASE}/v1/preview?need=quantum%20weather%20oracle%20zzz`, { headers: { 'x-toolpick-smoke': '1' } });
await fetchApp(`${BASE}/mcp`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'find_paid_api', arguments: { need: 'web search' } } }) });
await new Promise((r) => setTimeout(r, 10));
const top = await needLog.top({ minCount: 1 });
const q = top.needs.find((x) => x.need === 'quantum weather oracle zzz');
assert.ok(q, JSON.stringify(top)); assert.equal(q.n, 3); assert.equal(q.no_match, 3);
const r = await fetchApp(`${BASE}/v1/needs`); assert.equal(r.status, 200);
const j = await r.json(); assert.equal(j.min_count, 3); assert.equal(j.needs[0].need, 'quantum weather oracle zzz');
const info = await (await fetchApp(`${BASE}/`, { headers: { accept: 'application/json' } })).json();
assert.match(info.policy.storage, /anonymized daily aggregate counts/); assert.ok(info.free.includes('/v1/needs'));
});
agentgate-source: code mirror on Cloudflare Pages. All repos