import test from 'node:test'; import assert from 'node:assert/strict'; import { parsePrompt, extractJson, formatRecall, buildHookOutput, rufloBinCandidates, } from './ruflo-recall-hook.mjs'; test('parsePrompt: extracts the prompt field', () => { assert.equal(parsePrompt('{"prompt":"hello world"}'), 'hello world'); }); test('parsePrompt: empty string when field is missing', () => { assert.equal(parsePrompt('{"other":1}'), ''); }); test('parsePrompt: empty string on invalid JSON (fail-open)', () => { assert.equal(parsePrompt('not json at all'), ''); }); test('parsePrompt: empty string when prompt is not a string', () => { assert.equal(parsePrompt('{"prompt":123}'), ''); }); test('extractJson: pulls the JSON object out of noisy ruflo stdout', () => { const stdout = '[INFO] Searching: "x" (semantic)\n\n' + '✅ Using sql.js\n' + '{\n "query": "x",\n "results": []\n}\n'; const obj = extractJson(stdout); assert.equal(obj.query, 'x'); assert.deepEqual(obj.results, []); }); test('extractJson: null when there is no JSON', () => { assert.equal(extractJson('[INFO] only noise, no braces'), null); }); test('extractJson: null on malformed JSON (fail-open)', () => { assert.equal(extractJson('noise {not: valid'), null); }); test('formatRecall: empty string when there are no results', () => { assert.equal(formatRecall({ results: [] }), ''); assert.equal(formatRecall(null), ''); assert.equal(formatRecall({}), ''); }); test('formatRecall: formats at most 3 results and tags the block', () => { const out = formatRecall({ results: [ { key: 'k1', preview: 'p1' }, { key: 'k2', preview: 'p2' }, { key: 'k3', preview: 'p3' }, { key: 'k4', preview: 'p4' }, ], }); assert.ok(out.includes('k1') && out.includes('k3')); assert.ok(!out.includes('k4')); assert.ok(out.includes('ruflo memory recall')); }); test('buildHookOutput: correct UserPromptSubmit payload shape', () => { const o = buildHookOutput('some context'); assert.equal(o.hookSpecificOutput.hookEventName, 'UserPromptSubmit'); assert.equal(o.hookSpecificOutput.additionalContext, 'some context'); }); test('rufloBinCandidates: RUFLO_RECALL_BIN override wins', () => { assert.deepEqual(rufloBinCandidates({ RUFLO_RECALL_BIN: 'X:/r.js' }), ['X:/r.js']); }); test('rufloBinCandidates: builds an APPDATA-based candidate', () => { const c = rufloBinCandidates({ APPDATA: 'C:/Users/X/AppData/Roaming' }); assert.equal(c.length, 1); assert.ok(c[0].includes('ruflo') && c[0].endsWith('ruflo.js')); }); test('rufloBinCandidates: empty array when no env hints', () => { assert.deepEqual(rufloBinCandidates({}), []); });