'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.cts'); const { eraseUserData } = require('../services/userDataService.cts'); 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'); });