← back to AbramsOS
tests/extractor-llm.test.js
83 lines
// Tier-2 LLM enrichment unit tests. Mocks lib/ollama with require-cache override.
const test = require('node:test');
const assert = require('node:assert');
// Mock ollama with fresh module-cache injection. Wipe any pre-loaded extractor
// so it picks up our mock via require().
const fakeOllama = {
generate: async () => '',
generateJson: async (_prompt) => ({
merchant: 'Best Buy',
order_number: 'BBY01-1234567',
total: 199.99,
currency: 'USD',
items: [{ description: 'Wireless headphones', quantity: 1, unit_price: 199.99 }],
}),
reachable: async () => true,
DEFAULT_BASE: 'http://stub',
DEFAULT_MODEL: 'stub:tiny',
};
const ollamaPath = require.resolve('../lib/ollama');
const extractorPath = require.resolve('../lib/receipt-extractor');
function installMock(mock) {
delete require.cache[extractorPath];
require.cache[ollamaPath] = { id: ollamaPath, filename: ollamaPath, loaded: true, exports: mock };
}
installMock(fakeOllama);
const extractor = require('../lib/receipt-extractor');
test('enrichWithLlm fills missing fields', async () => {
const summary = {
headers: { subject: 'Thank you for your order', from: '"Some Store" <orders@example.com>', date: new Date().toISOString() },
body: { text: 'Thanks for your order. Your shipment will arrive Tuesday.', html: '' },
};
const t1 = extractor.extract(summary);
// Heuristic should find SOMETHING low-confidence (subject hints + body lacks total/order number)
assert.ok(t1, 'tier-1 should produce a draft');
assert.ok(t1.confidence < extractor.LLM_THRESHOLD, `tier-1 confidence (${t1?.confidence}) should be below threshold (${extractor.LLM_THRESHOLD})`);
const t2 = await extractor.enrichWithLlm(summary, t1);
assert.strictEqual(t2.source, 'heuristic+llm');
// Heuristic-trust rule: merchant from From: header is kept even though LLM offers 'Best Buy'
assert.strictEqual(t2.merchant, t1.merchant, 'heuristic merchant should be preserved');
// LLM fills the fields heuristic missed
assert.strictEqual(t2.orderNumber, 'BBY01-1234567');
assert.strictEqual(t2.total, 199.99);
assert.strictEqual(t2.currency, 'USD');
assert.strictEqual(t2.items.length, 1);
assert.ok(t2.confidence > t1.confidence, `confidence should rise (was ${t1.confidence}, now ${t2.confidence})`);
});
test('enrichWithLlm respects high-confidence heuristic (skips LLM)', async () => {
const summary = {
headers: { subject: 'Your Amazon.com order #112-7654321-7654321', from: '"Amazon.com" <auto-confirm@amazon.com>', date: new Date().toISOString() },
body: { text: 'Order Total: $99.99. Thanks for your order.', html: '' },
};
const t1 = extractor.extract(summary);
assert.ok(t1.confidence >= extractor.LLM_THRESHOLD, 'tier-1 should already be high-conf');
const t2 = await extractor.enrichWithLlm(summary, t1);
assert.strictEqual(t2.source, 'heuristic', 'LLM should be skipped');
assert.strictEqual(t2, t1, 'result is the same object');
});
test('enrichWithLlm tolerates Ollama failure gracefully', async () => {
const failOllama = { ...fakeOllama, generateJson: async () => { throw new Error('mac1 unreachable'); } };
installMock(failOllama);
const extractor2 = require('../lib/receipt-extractor');
const summary = {
headers: { subject: 'Thank you for your order', from: '"Store" <ship@example.com>', date: new Date().toISOString() },
body: { text: 'Thanks for your order. Your shipment will arrive tomorrow.', html: '' },
};
const t1 = extractor2.extract(summary);
assert.ok(t1, 'tier-1 should produce a draft for this fixture');
const t2 = await extractor2.enrichWithLlm(summary, t1);
assert.ok(t2.llmError, 'should surface the LLM error');
assert.strictEqual(t2.confidence, t1.confidence, 'confidence unchanged on failure');
// Restore for any later tests
installMock(fakeOllama);
});