test(qa): summary skip-exclusion + per-month override regression (B2/B5)
- tests/summarySkipOverride.test.js: verifies the Summary excludes skipped bills from the monthly total and applies per-month amount overrides, alongside the QA-B5-01 occurrence gate (guards both from regressing together) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ad474f1ac1
commit
ccf89e6df1
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
// B2/B5: the Summary must honour per-month monthly_bill_state modifiers — skipped
|
||||
// bills excluded from the total, per-month amount overrides applied — alongside the
|
||||
// occurrence gate added for QA-B5-01. Guards against regressions in either.
|
||||
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-skip-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);
|
||||
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(c) { this.statusCode = c; return this; },
|
||||
json(data) { resolve({ status: this.statusCode, data }); },
|
||||
};
|
||||
handler(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
test('summary honours skip (exclude) and per-month amount override', async () => {
|
||||
const db = getDb();
|
||||
const userId = db.prepare(
|
||||
"INSERT INTO users (username, password_hash, role, active) VALUES ('skip-user', 'x', 'user', 1)",
|
||||
).run().lastInsertRowid;
|
||||
|
||||
const insertBill = db.prepare(`
|
||||
INSERT INTO bills (user_id, name, due_day, billing_cycle, cycle_type, expected_amount, active)
|
||||
VALUES (?, ?, ?, 'monthly', 'monthly', ?, 1)
|
||||
`);
|
||||
const a = insertBill.run(userId, 'Bill A', 5, 10000).lastInsertRowid; // $100
|
||||
const b = insertBill.run(userId, 'Bill B', 10, 20000).lastInsertRowid; // $200, skipped
|
||||
const c = insertBill.run(userId, 'Bill C', 15, 30000).lastInsertRowid; // $300, overridden to $50
|
||||
|
||||
const setState = db.prepare(
|
||||
'INSERT INTO monthly_bill_state (bill_id, year, month, actual_amount, is_skipped) VALUES (?, 2026, 6, ?, ?)',
|
||||
);
|
||||
setState.run(b, null, 1); // skip B
|
||||
setState.run(c, 5000, 0); // override C → $50
|
||||
|
||||
const { data } = await callSummary(userId, 2026, 6);
|
||||
const find = (id) => data.expenses.find((e) => e.bill_id === id);
|
||||
|
||||
assert.equal(find(a).display_amount, 100, 'Bill A shows base amount');
|
||||
assert.equal(find(b).is_skipped, true, 'Bill B is flagged skipped');
|
||||
assert.equal(find(c).display_amount, 50, 'Bill C shows the per-month override');
|
||||
assert.equal(find(c).actual_amount, 50, 'Bill C override surfaced as actual_amount');
|
||||
|
||||
// Counted total excludes the skipped bill and uses the override: 100 + 50 = 150.
|
||||
const countedTotal = data.expenses
|
||||
.filter((e) => !e.is_skipped)
|
||||
.reduce((sum, e) => sum + e.display_amount, 0);
|
||||
assert.equal(countedTotal, 150, 'monthly obligation excludes skipped, applies override');
|
||||
});
|
||||
|
||||
test.after(() => {
|
||||
closeDb();
|
||||
for (const suffix of ['', '-wal', '-shm']) {
|
||||
try { fs.rmSync(dbPath + suffix); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
Loading…
Reference in New Issue