'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'); });