'use strict'; // B9: export → import round-trip must preserve money exactly. The export writes // dollars (fromCents) into REAL columns and has no schema_migrations table, so the // import treats it as dollars and applies toCents — a symmetric conversion. This // guards against that symmetry breaking (which would 100x amounts on import). 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-roundtrip-test-${process.pid}.sqlite`); process.env.DB_PATH = dbPath; const { getDb, closeDb } = require('../db/database.cts'); const { buildUserDbExportFile } = require('../routes/export.cts'); const { previewUserDbImport, applyUserDbImport } = require('../services/userDbImportService.cts'); const mkUser = (db, name) => db .prepare("INSERT INTO users (username, password_hash, role, active) VALUES (?, 'x', 'user', 1)") .run(name).lastInsertRowid; test('export → import round-trip preserves money in cents (no 100x drift)', async () => { const db = getDb(); const userA = mkUser(db, 'rt-a'); const userB = mkUser(db, 'rt-b'); const catA = db .prepare("INSERT INTO categories (user_id, name) VALUES (?, 'Utilities')") .run(userA).lastInsertRowid; const billA = db .prepare( ` INSERT INTO bills (user_id, name, category_id, due_day, billing_cycle, cycle_type, expected_amount, active) VALUES (?, 'Electric Company', ?, 15, 'monthly', 'monthly', 8500, 1) `, ) .run(userA, catA).lastInsertRowid; // $85.00 db.prepare( "INSERT INTO payments (bill_id, amount, paid_date, payment_source) VALUES (?, 8500, '2026-06-15', 'manual')", ).run(billA); db.prepare( 'INSERT INTO monthly_bill_state (bill_id, year, month, actual_amount) VALUES (?, 2026, 7, 5000)', ).run(billA); // $50 override // Export user A, import into fresh user B. const file = buildUserDbExportFile(userA); try { const buffer = fs.readFileSync(file); const preview = await previewUserDbImport(userB, buffer, {}); assert.ok(preview.import_session_id, 'preview returns an import session id'); await applyUserDbImport(userB, preview.import_session_id, {}); } finally { try { fs.unlinkSync(file); } catch { /* ignore */ } } const billB = db .prepare("SELECT expected_amount FROM bills WHERE user_id = ? AND name = 'Electric Company'") .get(userB); assert.ok(billB, 'bill imported for user B'); assert.equal(billB.expected_amount, 8500, 'bill expected_amount preserved as cents'); const payB = db .prepare('SELECT p.amount FROM payments p JOIN bills b ON b.id = p.bill_id WHERE b.user_id = ?') .get(userB); assert.equal(payB.amount, 8500, 'payment amount preserved as cents'); const stateB = db .prepare( 'SELECT m.actual_amount FROM monthly_bill_state m JOIN bills b ON b.id = m.bill_id WHERE b.user_id = ?', ) .get(userB); assert.equal(stateB.actual_amount, 5000, 'per-month override preserved as cents'); }); test.after(() => { closeDb(); for (const suffix of ['', '-wal', '-shm']) { try { fs.rmSync(dbPath + suffix); } catch { /* ignore */ } } });