← back to AbramsOS
tests/claims.test.js
89 lines
// Claims engine: reminder → claim_case + queued action_queue row.
// Mocks ollama with template fallback (so no network).
const test = require('node:test');
const assert = require('node:assert');
require('dotenv').config();
// Inject ollama mock that always throws → strategist falls through to template
const ollamaPath = require.resolve('../lib/ollama');
const claimPath = require.resolve('../lib/claim-strategist');
require.cache[ollamaPath] = { id: ollamaPath, filename: ollamaPath, loaded: true, exports: {
generateJson: async () => { throw new Error('mock-ollama-down'); },
generate: async () => '',
reachable: async () => false,
DEFAULT_BASE: 'http://stub', DEFAULT_MODEL: 'stub',
}};
delete require.cache[claimPath];
const db = require('../lib/db');
const strategist = require('../lib/claim-strategist');
const TEST_USER_ID = 'user_steve';
const REMINDER_ID = 'test_reminder_claim';
const PURCHASE_ID = 'test_pur_claim';
test.before(async () => {
// Clean any prior test data
await db.query(`DELETE FROM action_queue WHERE case_id IN (SELECT id FROM claim_case WHERE user_id = $1 AND metadata_jsonb->>'from_reminder' = $2)`, [TEST_USER_ID, REMINDER_ID]);
await db.query(`DELETE FROM claim_case WHERE user_id = $1 AND metadata_jsonb->>'from_reminder' = $2`, [TEST_USER_ID, REMINDER_ID]);
await db.query(`DELETE FROM calendar_reminder WHERE id = $1`, [REMINDER_ID]);
await db.query(`DELETE FROM purchase WHERE id = $1`, [PURCHASE_ID]);
// Seed: a purchase + a returns_window_closing reminder pointing at it
await db.query(
`INSERT INTO purchase (id, user_id, merchant_name, order_number, purchase_date, total_amount, currency, confidence)
VALUES ($1, $2, 'TestMart', 'TM-99999', $3, 49.99, 'USD', 0.92)`,
[PURCHASE_ID, TEST_USER_ID, new Date(Date.now() - 25 * 86400e3)]
);
await db.query(
`INSERT INTO calendar_reminder (id, user_id, owner_table, owner_id, due_at, reason_code, title, body)
VALUES ($1, $2, 'purchase', $3, $4, 'returns_window_closing', 'TestMart return window closes in 5d', 'Body')`,
[REMINDER_ID, TEST_USER_ID, PURCHASE_ID, new Date(Date.now() + 5 * 86400e3)]
);
});
test.after(async () => {
await db.query(`DELETE FROM action_queue WHERE case_id IN (SELECT id FROM claim_case WHERE metadata_jsonb->>'from_reminder' = $1)`, [REMINDER_ID]);
await db.query(`DELETE FROM claim_case WHERE metadata_jsonb->>'from_reminder' = $1`, [REMINDER_ID]);
await db.query(`DELETE FROM calendar_reminder WHERE id = $1`, [REMINDER_ID]);
await db.query(`DELETE FROM purchase WHERE id = $1`, [PURCHASE_ID]);
await db.pool.end();
});
test('fromReminder builds a draft claim_case + queues a user_required action', async () => {
const r = await strategist.fromReminder(REMINDER_ID, TEST_USER_ID);
assert.ok(r.caseId, 'case id returned');
assert.strictEqual(r.claim.routing, 'merchant');
assert.strictEqual(r.claim.claim_type, 'refund');
assert.match(r.claim.body, /TestMart/);
assert.match(r.claim.body, /TM-99999/);
// claim_case row exists
const c = await db.query(`SELECT * FROM claim_case WHERE id = $1`, [r.caseId]);
assert.strictEqual(c.rows.length, 1);
assert.strictEqual(c.rows[0].state, 'draft');
assert.strictEqual(c.rows[0].asset_table, 'purchase');
assert.strictEqual(c.rows[0].asset_id, PURCHASE_ID);
// action_queue row exists, gated user_required, NOT executed
const a = await db.query(`SELECT * FROM action_queue WHERE case_id = $1`, [r.caseId]);
assert.strictEqual(a.rows.length, 1);
assert.strictEqual(a.rows[0].action_type, 'send_email');
assert.strictEqual(a.rows[0].approval_level, 'user_required');
assert.strictEqual(a.rows[0].state, 'pending');
assert.strictEqual(a.rows[0].executed_at, null);
});
test('letter body uses template fallback when LLM is down', async () => {
// Find the case we just created
const c = await db.query(
`SELECT draft_body, metadata_jsonb FROM claim_case WHERE metadata_jsonb->>'from_reminder' = $1`,
[REMINDER_ID]
);
assert.ok(c.rows.length);
assert.match(c.rows[0].draft_body, /To Whom It May Concern/);
assert.ok(['template_fallback', 'template'].includes(c.rows[0].metadata_jsonb.draft_source));
});