BillTracker/tests/eraseUserData.test.js

86 lines
4.2 KiB
JavaScript

'use strict';
// Batch 5: "erase my data" wipes the requesting user's financial data only — never
// another user's, never the account/auth — and re-seeds default categories.
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-erase-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;
const { getDb, closeDb } = require('../db/database');
const { eraseUserData } = require('../services/userDataService');
function makeUser(db, name) {
return db.prepare("INSERT INTO users (username, password_hash, role, active) VALUES (?, 'hash', 'user', 1)").run(name).lastInsertRowid;
}
function seedFinancialData(db, userId, label) {
const catId = db.prepare("INSERT INTO categories (user_id, name) VALUES (?, ?)").run(userId, `${label}-cat`).lastInsertRowid;
const billId = db.prepare("INSERT INTO bills (user_id, name, category_id, due_day, expected_amount, active) VALUES (?, ?, ?, 1, 5000, 1)").run(userId, `${label}-bill`, catId).lastInsertRowid;
db.prepare("INSERT INTO payments (bill_id, amount, paid_date, payment_source) VALUES (?, 5000, '2026-06-01', 'manual')").run(billId);
const dsId = db.prepare("INSERT INTO data_sources (user_id, type, provider, name, status) VALUES (?, 'file_import', 'csv', 'CSV Import', 'active')").run(userId).lastInsertRowid;
db.prepare("INSERT INTO transactions (user_id, data_source_id, source_type, provider_transaction_id, amount, match_status, ignored) VALUES (?, ?, 'file_import', ?, -5000, 'unmatched', 0)").run(userId, dsId, `${label}-tx`);
return { billId, catId, dsId };
}
const countFinancial = (db, userId) => ({
bills: db.prepare('SELECT COUNT(*) n FROM bills WHERE user_id=?').get(userId).n,
payments: db.prepare('SELECT COUNT(*) n FROM payments p JOIN bills b ON b.id=p.bill_id WHERE b.user_id=?').get(userId).n,
transactions: db.prepare('SELECT COUNT(*) n FROM transactions WHERE user_id=?').get(userId).n,
data_sources: db.prepare('SELECT COUNT(*) n FROM data_sources WHERE user_id=?').get(userId).n,
categories: db.prepare('SELECT COUNT(*) n FROM categories WHERE user_id=?').get(userId).n,
});
let db, userA, userB;
test.before(() => {
db = getDb();
db.pragma('foreign_keys = ON');
userA = makeUser(db, 'erase-a');
userB = makeUser(db, 'erase-b');
seedFinancialData(db, userA, 'a');
seedFinancialData(db, userB, 'b');
// a session + a webauthn credential for user A to prove auth is preserved
db.prepare("INSERT INTO sessions (id, user_id, expires_at) VALUES ('sess-a', ?, datetime('now','+1 day'))").run(userA);
});
test.after(() => {
closeDb();
for (const s of ['', '-wal', '-shm']) { try { fs.unlinkSync(dbPath + s); } catch {} }
});
test('erase wipes the requesting user\'s financial data', () => {
const before = countFinancial(db, userA);
assert.ok(before.bills > 0 && before.payments > 0 && before.transactions > 0 && before.data_sources > 0);
const result = eraseUserData(db, userA);
assert.ok(result.erased > 0);
const after = countFinancial(db, userA);
assert.equal(after.bills, 0);
assert.equal(after.payments, 0, 'bill-child payments cascade/removed');
assert.equal(after.transactions, 0);
assert.equal(after.data_sources, 0);
});
test('erase re-seeds default categories (app stays usable)', () => {
const cats = db.prepare('SELECT COUNT(*) n FROM categories WHERE user_id=?').get(userA).n;
assert.ok(cats > 0, 'default categories re-seeded after wipe');
});
test('erase never touches another user\'s data', () => {
const b = countFinancial(db, userB);
assert.ok(b.bills > 0 && b.payments > 0 && b.transactions > 0 && b.data_sources > 0, 'user B untouched');
});
test('erase preserves the account and auth (session, user row)', () => {
assert.ok(db.prepare('SELECT 1 FROM users WHERE id=?').get(userA), 'account preserved');
assert.ok(db.prepare("SELECT 1 FROM sessions WHERE id='sess-a'").get(), 'session preserved');
});
test('erase is audited to import_history', () => {
const row = db.prepare("SELECT file_type, source_filename FROM import_history WHERE user_id=? ORDER BY id DESC LIMIT 1").get(userA);
assert.equal(row.file_type, 'erase-data');
assert.equal(row.source_filename, 'erase-my-data');
});