'use strict'; // Phase 1 — billMerchantRuleService.syncBillPaymentsFromSimplefin is a money- // CREATING path: it turns matching unmatched bank transactions into payments // (payment_source='provider_sync'). Untested until now. Pins: a rule-matched // transaction becomes exactly one payment (amount in cents, tx marked matched), // it's idempotent (re-running creates no duplicate — match-status filter + the // UNIQUE(transaction_id) index), and a non-matching transaction is left alone. const test = require('node:test'); const assert = require('node:assert/strict'); const os = require('node:os'); const path = require('node:path'); const fs = require('node:fs'); const dbPath = path.join(os.tmpdir(), `bill-tracker-merchantrule-${process.pid}.sqlite`); process.env.DB_PATH = dbPath; const { getDb, closeDb } = require('../db/database.cts'); const { addMerchantRule, syncBillPaymentsFromSimplefin, } = require('../services/billMerchantRuleService.cts'); let db, userId, billId; const insertTx = (payee, amountCents, postedDate) => db .prepare( ` INSERT INTO transactions (user_id, source_type, amount, payee, posted_date, match_status, ignored, pending) VALUES (?, 'simplefin', ?, ?, ?, 'unmatched', 0, 0) `, ) .run(userId, amountCents, payee, postedDate).lastInsertRowid; const activePayments = () => db.prepare('SELECT * FROM payments WHERE bill_id = ? AND deleted_at IS NULL').all(billId); test.before(() => { db = getDb(); userId = db .prepare( "INSERT INTO users (username, password_hash, role, active) VALUES ('sync-user','x','user',1)", ) .run().lastInsertRowid; billId = db .prepare( "INSERT INTO bills (user_id, name, due_day, expected_amount, current_balance, interest_rate, active) VALUES (?, 'Netflix', 15, 1500, 50000, 0, 1)", ) .run(userId).lastInsertRowid; addMerchantRule(db, userId, billId, 'netflix'); }); test.after(() => { closeDb(); for (const s of ['', '-wal', '-shm']) { try { fs.rmSync(dbPath + s); } catch {} } }); test('a rule-matched unmatched transaction becomes exactly one provider_sync payment', () => { const txId = insertTx('NETFLIX', -1500, '2026-07-15'); // $15 charge (cents, negative = money out) const result = syncBillPaymentsFromSimplefin(db, userId, billId); assert.equal(result.added, 1, 'one payment created'); const pays = activePayments(); assert.equal(pays.length, 1); assert.equal(pays[0].amount, 1500, 'amount stored in cents'); assert.equal(pays[0].payment_source, 'provider_sync'); assert.equal(pays[0].transaction_id, txId, 'linked to the source transaction'); const tx = db .prepare('SELECT match_status, matched_bill_id FROM transactions WHERE id = ?') .get(txId); assert.equal(tx.match_status, 'matched', 'transaction marked matched'); assert.equal(tx.matched_bill_id, billId); }); test('re-running is idempotent — no duplicate payment', () => { const result = syncBillPaymentsFromSimplefin(db, userId, billId); assert.equal(result.added, 0, 'nothing new to add on a second sync'); assert.equal(activePayments().length, 1, 'still exactly one payment'); }); test('a non-matching transaction is left untouched', () => { const spotifyTx = insertTx('SPOTIFY USA', -999, '2026-07-16'); const result = syncBillPaymentsFromSimplefin(db, userId, billId); assert.equal(result.added, 0, 'Spotify does not match the netflix rule'); assert.equal(activePayments().length, 1); assert.equal( db.prepare('SELECT match_status FROM transactions WHERE id = ?').get(spotifyTx).match_status, 'unmatched', 'left unmatched', ); });