← 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));
});