← back to AbramsOS

tests/reminder-engine.test.js

69 lines

const test = require('node:test');
const assert = require('node:assert');

require('dotenv').config();
const db = require('../lib/db');
const engine = require('../lib/reminder-engine');

const TEST_USER_ID = 'user_steve';

test.before(async () => {
  // Wipe any reminders the engine might recreate, but keep purchases.
  await db.query(`DELETE FROM calendar_reminder WHERE user_id = $1`, [TEST_USER_ID]);
});

test.after(async () => {
  await db.query(`DELETE FROM calendar_reminder WHERE user_id = $1`, [TEST_USER_ID]);
  // Drop the synthetic purchase we insert below
  await db.query(`DELETE FROM purchase WHERE merchant_name = 'TEST_REMINDER_FIXTURE'`);
  await db.pool.end();
});

test('warranty_expiry reminder fires for purchase ~340 days old (≈25d before 1y mark)', async () => {
  const purchaseDate = new Date(Date.now() - 340 * 86400e3); // 340 days ago → warranty expires ~25d from now
  await db.query(
    `INSERT INTO purchase (id, user_id, merchant_name, purchase_date, total_amount, currency, confidence)
     VALUES ('test_pur_warranty', $1, 'TEST_REMINDER_FIXTURE', $2, 100, 'USD', 0.9)
     ON CONFLICT (id) DO UPDATE SET purchase_date = EXCLUDED.purchase_date`,
    [TEST_USER_ID, purchaseDate]
  );
  const inserted = await engine.generateForUser(TEST_USER_ID);
  assert.ok(inserted >= 1, `should insert at least one reminder (got ${inserted})`);
  const r = await db.query(
    `SELECT reason_code FROM calendar_reminder WHERE owner_id = 'test_pur_warranty'`
  );
  const codes = r.rows.map(x => x.reason_code);
  assert.ok(codes.includes('warranty_expiry'), `expected warranty_expiry in [${codes.join(',')}]`);
});

test('returns_window_closing fires for purchase ~25d old (≈5d before 30d mark)', async () => {
  const purchaseDate = new Date(Date.now() - 25 * 86400e3);
  await db.query(
    `INSERT INTO purchase (id, user_id, merchant_name, purchase_date, total_amount, currency, confidence)
     VALUES ('test_pur_returns', $1, 'TEST_REMINDER_FIXTURE', $2, 100, 'USD', 0.9)
     ON CONFLICT (id) DO UPDATE SET purchase_date = EXCLUDED.purchase_date`,
    [TEST_USER_ID, purchaseDate]
  );
  await engine.generateForUser(TEST_USER_ID);
  const r = await db.query(
    `SELECT reason_code FROM calendar_reminder WHERE owner_id = 'test_pur_returns'`
  );
  assert.ok(r.rows.some(x => x.reason_code === 'returns_window_closing'));
});

test('engine is idempotent (re-runs do not duplicate)', async () => {
  const before = await db.query(`SELECT count(*)::int AS n FROM calendar_reminder WHERE user_id = $1`, [TEST_USER_ID]);
  await engine.generateForUser(TEST_USER_ID);
  await engine.generateForUser(TEST_USER_ID);
  const after = await db.query(`SELECT count(*)::int AS n FROM calendar_reminder WHERE user_id = $1`, [TEST_USER_ID]);
  assert.strictEqual(after.rows[0].n, before.rows[0].n, 'duplicate runs should be no-ops');
});

test('upcomingForUser returns pending reminders ordered by due_at', async () => {
  const rows = await engine.upcomingForUser(TEST_USER_ID, { days: 400 });
  assert.ok(rows.length >= 2, `expected ≥2 pending (got ${rows.length})`);
  for (let i = 1; i < rows.length; i++) {
    assert.ok(new Date(rows[i].due_at) >= new Date(rows[i-1].due_at), 'order ASC');
  }
});