70 lines
3.0 KiB
JavaScript
70 lines
3.0 KiB
JavaScript
|
|
'use strict';
|
||
|
|
|
||
|
|
// QA-B5-02 regression: the SimpleFIN bank-tracking `unpaid_this_month` metric must
|
||
|
|
// only count bills that actually occur in the requested month — annual / off-month
|
||
|
|
// quarterly bills should not inflate it (same occurrence gate as Tracker/Summary).
|
||
|
|
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-summary-bt-test-${process.pid}.sqlite`);
|
||
|
|
process.env.DB_PATH = dbPath;
|
||
|
|
|
||
|
|
const { getDb, closeDb } = require('../db/database');
|
||
|
|
|
||
|
|
function callSummary(userId, year, month) {
|
||
|
|
const router = require('../routes/summary');
|
||
|
|
const layer = router.stack.find(item => item.route?.path === '/' && item.route.methods.get);
|
||
|
|
assert.ok(layer, 'GET /api/summary route should exist');
|
||
|
|
const handler = layer.route.stack[0].handle;
|
||
|
|
return new Promise((resolve) => {
|
||
|
|
const req = { query: { year: String(year), month: String(month) }, user: { id: userId, role: 'user' } };
|
||
|
|
const res = {
|
||
|
|
statusCode: 200,
|
||
|
|
status(code) { this.statusCode = code; return this; },
|
||
|
|
json(data) { resolve({ status: this.statusCode, data }); },
|
||
|
|
};
|
||
|
|
handler(req, res);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
test('bank-tracking unpaid_this_month excludes bills not due this month (QA-B5-02)', async () => {
|
||
|
|
const db = getDb();
|
||
|
|
const userId = db.prepare(
|
||
|
|
"INSERT INTO users (username, password_hash, role, active) VALUES ('bt-user', 'x', 'user', 1)",
|
||
|
|
).run().lastInsertRowid;
|
||
|
|
|
||
|
|
const insertBill = db.prepare(`
|
||
|
|
INSERT INTO bills (user_id, name, due_day, billing_cycle, cycle_type, cycle_day, expected_amount, active)
|
||
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, 1)
|
||
|
|
`);
|
||
|
|
// Due every June (query month): counts.
|
||
|
|
insertBill.run(userId, 'Monthly Bill', 15, 'monthly', 'monthly', null, 10000); // $100
|
||
|
|
// Annual, anchored to January: NOT due in June — must be excluded.
|
||
|
|
insertBill.run(userId, 'Annual Bill', 1, 'annually', 'annual', '1', 50000); // $500
|
||
|
|
|
||
|
|
// Enable bank tracking pointed at a financial account with a balance.
|
||
|
|
const acctId = db.prepare(`
|
||
|
|
INSERT INTO financial_accounts (user_id, name, org_name, account_type, balance, available_balance, monitored)
|
||
|
|
VALUES (?, 'Checking', 'Test Bank', 'checking', 200000, 200000, 1)
|
||
|
|
`).run(userId).lastInsertRowid;
|
||
|
|
const setSetting = db.prepare('INSERT INTO user_settings (user_id, key, value) VALUES (?, ?, ?)');
|
||
|
|
setSetting.run(userId, 'bank_tracking_enabled', 'true');
|
||
|
|
setSetting.run(userId, 'bank_tracking_account_id', String(acctId));
|
||
|
|
|
||
|
|
const { data } = await callSummary(userId, 2026, 6); // June 2026
|
||
|
|
|
||
|
|
assert.ok(data.bank_tracking, 'bank_tracking summary should be present when enabled');
|
||
|
|
// Only the $100 monthly bill is due in June; the $500 annual bill (Jan) is excluded.
|
||
|
|
assert.equal(data.bank_tracking.unpaid_this_month, 100);
|
||
|
|
});
|
||
|
|
|
||
|
|
test.after(() => {
|
||
|
|
closeDb();
|
||
|
|
for (const suffix of ['', '-wal', '-shm']) {
|
||
|
|
try { fs.rmSync(dbPath + suffix); } catch { /* ignore */ }
|
||
|
|
}
|
||
|
|
});
|