BillTracker/tests/seedDemoData.test.js

321 lines
9.9 KiB
JavaScript
Raw Permalink Normal View History

feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
'use strict';
// Demo seed + surgical removal. seedDemoData() must populate every user-facing
// surface, and clearSeededDemoData() must remove EXACTLY the seeded rows — never a
// user's own bills/categories/planning, and leave no orphans. This is the contract
// the "Seed / Clear Demo Data" buttons depend on.
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-seed-${process.pid}.sqlite`);
process.env.DB_PATH = dbPath;
const { getDb, closeDb } = require('../db/database.cts');
const { seedDemoData } = require('../scripts/seedDemoData.cts');
const { clearSeededDemoData, eraseUserData } = require('../services/userDataService.cts');
const mkUser = (db, name) =>
db
.prepare(
"INSERT INTO users (username, password_hash, role, active) VALUES (?, 'hash', 'user', 1)",
)
.run(name).lastInsertRowid;
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
const num = (db, sql, ...p) => db.prepare(sql).get(...p).n;
let db, userA, userB, userC;
test.before(() => {
db = getDb();
db.pragma('foreign_keys = ON');
userA = mkUser(db, 'seed-a');
userB = mkUser(db, 'seed-b');
userC = mkUser(db, 'seed-c');
seedDemoData(userA);
seedDemoData(userB);
seedDemoData(userC);
});
test.after(() => {
closeDb();
for (const s of ['', '-wal', '-shm']) {
try {
fs.unlinkSync(dbPath + s);
} catch {}
}
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
});
test('seed populates every user-facing surface', () => {
assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userA), 23);
// 8 of 9 bill categories reuse the user's defaults (not flagged); Healthcare + 5 spending are created.
assert.equal(
num(db, 'SELECT COUNT(*) n FROM categories WHERE user_id=? AND is_seeded=1', userA),
6,
);
assert.equal(
num(
db,
'SELECT COUNT(*) n FROM categories WHERE user_id=? AND is_seeded=1 AND spending_enabled=1',
userA,
),
5,
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM payments p JOIN bills b ON b.id=p.bill_id WHERE b.user_id=?',
userA,
) > 40,
'payment history',
);
assert.ok(
num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userA) > 10,
'bank transactions',
);
assert.ok(
num(
db,
"SELECT COUNT(*) n FROM transactions WHERE user_id=? AND match_status='matched'",
userA,
) >= 3,
'matched txns',
);
assert.ok(
num(
db,
"SELECT COUNT(*) n FROM transactions WHERE user_id=? AND match_status='unmatched'",
userA,
) >= 5,
'unmatched txns → suggestions/recommendations',
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND spending_category_id IS NOT NULL',
userA,
) > 5,
'categorised spending',
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM monthly_bill_state m JOIN bills b ON b.id=m.bill_id WHERE b.user_id=?',
userA,
) >= 4,
'skip/override/note/snooze',
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM category_groups WHERE user_id=? AND is_seeded=1', userA),
2,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=? AND is_seeded=1', userA),
1,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM spending_budgets WHERE user_id=? AND is_seeded=1', userA),
5,
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM spending_category_rules WHERE user_id=? AND is_seeded=1',
userA,
) >= 3,
);
assert.ok(
num(db, 'SELECT COUNT(*) n FROM monthly_income WHERE user_id=? AND is_seeded=1', userA) >= 1,
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM monthly_starting_amounts WHERE user_id=? AND is_seeded=1',
userA,
) >= 1,
);
assert.ok(
num(
db,
'SELECT COUNT(*) n FROM bill_merchant_rules r JOIN bills b ON b.id=r.bill_id WHERE b.user_id=?',
userA,
) >= 1,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM data_sources WHERE user_id=? AND is_seeded=1', userA),
1,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM financial_accounts WHERE user_id=? AND is_seeded=1', userA),
1,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM calendar_tokens WHERE user_id=? AND is_seeded=1', userA),
1,
);
assert.ok(
db.prepare('SELECT snowball_extra_payment e FROM users WHERE id=?').get(userA).e > 0,
'snowball extra-payment set',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// Matched payments link a real seeded transaction; money is integer cents.
assert.ok(
num(
db,
"SELECT COUNT(*) n FROM payments WHERE payment_source='transaction_match' AND transaction_id IN (SELECT id FROM transactions)",
) >= 3,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM payments WHERE amount <> CAST(amount AS INT)'),
0,
'cents-clean',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
});
test('seed refuses to double-seed (idempotency guard)', () => {
assert.throws(
() => seedDemoData(userA),
(e) => e.code === 'ALREADY_SEEDED' && e.status === 409,
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
});
test('clear removes ONLY seeded data — user bills/categories & no orphans', () => {
// A user's OWN bill in a seed-created category (Healthcare) + one in a default category.
const healthcareId = db
.prepare("SELECT id FROM categories WHERE user_id=? AND name='Healthcare'")
.get(userA).id;
assert.equal(
db.prepare('SELECT is_seeded s FROM categories WHERE id=?').get(healthcareId).s,
1,
'Healthcare is a seeded category',
);
const utilitiesId = db
.prepare("SELECT id FROM categories WHERE user_id=? AND name='Utilities'")
.get(userA).id;
const ownBill1 = db
.prepare(
"INSERT INTO bills (user_id, name, category_id, due_day, expected_amount, active, is_seeded) VALUES (?, 'My Doctor', ?, 9, 5000, 1, 0)",
)
.run(userA, healthcareId).lastInsertRowid;
const ownBill2 = db
.prepare(
"INSERT INTO bills (user_id, name, category_id, due_day, expected_amount, active, is_seeded) VALUES (?, 'My Power', ?, 9, 6000, 1, 0)",
)
.run(userA, utilitiesId).lastInsertRowid;
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
const result = clearSeededDemoData(db, userA);
assert.ok(result.removed > 0);
// Every seeded surface gone.
assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userA), 0);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM data_sources WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM financial_accounts WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM spending_budgets WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM monthly_income WHERE user_id=? AND is_seeded=1', userA),
0,
);
assert.equal(
num(db, 'SELECT COUNT(*) n FROM category_groups WHERE user_id=? AND is_seeded=1', userA),
0,
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// No orphans: payments/transactions/state for deleted bills are gone.
assert.equal(
num(
db,
'SELECT COUNT(*) n FROM payments p LEFT JOIN bills b ON b.id=p.bill_id WHERE b.id IS NULL',
),
0,
'no orphan payments',
);
assert.equal(
num(
db,
'SELECT COUNT(*) n FROM monthly_bill_state m LEFT JOIN bills b ON b.id=m.bill_id WHERE b.id IS NULL',
),
0,
'no orphan monthly_bill_state',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// User's own data preserved.
assert.ok(db.prepare('SELECT 1 FROM bills WHERE id=?').get(ownBill1), 'user bill 1 survives');
assert.ok(db.prepare('SELECT 1 FROM bills WHERE id=?').get(ownBill2), 'user bill 2 survives');
assert.equal(
db.prepare('SELECT category_id c FROM bills WHERE id=?').get(ownBill1).c,
healthcareId,
'still categorised (not SET NULL)',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// Healthcare seeded category SURVIVES because a user bill still references it.
assert.ok(
db.prepare('SELECT 1 FROM categories WHERE id=?').get(healthcareId),
'referenced seeded category kept',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// A seeded spending category with no user bill IS removed.
assert.equal(
num(db, "SELECT COUNT(*) n FROM categories WHERE user_id=? AND name='Groceries'", userA),
0,
'unreferenced seeded category removed',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// Default categories untouched.
assert.ok(
db.prepare('SELECT 1 FROM categories WHERE id=?').get(utilitiesId),
'default category kept',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
// Snowball extra-payment reset.
assert.equal(db.prepare('SELECT snowball_extra_payment e FROM users WHERE id=?').get(userA).e, 0);
});
test("clear leaves other users' seeded data untouched", () => {
assert.equal(
num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=? AND is_seeded=1', userB),
23,
'user B intact after clearing A',
);
assert.ok(
num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=? AND is_seeded=1', userB) > 0,
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
});
test('clear is audited to import_history', () => {
const row = db
.prepare(
"SELECT file_type, source_filename FROM import_history WHERE user_id=? AND file_type='clear-demo' ORDER BY id DESC LIMIT 1",
)
.get(userA);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
assert.equal(row.file_type, 'clear-demo');
assert.equal(row.source_filename, 'clear-demo-data');
});
test('eraseUserData still wipes seeded demo data wholesale', () => {
assert.ok(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=?', userC) > 0);
eraseUserData(db, userC);
assert.equal(num(db, 'SELECT COUNT(*) n FROM bills WHERE user_id=?', userC), 0);
assert.equal(num(db, 'SELECT COUNT(*) n FROM transactions WHERE user_id=?', userC), 0);
assert.equal(num(db, 'SELECT COUNT(*) n FROM snowball_plans WHERE user_id=?', userC), 0);
assert.equal(
num(
db,
'SELECT COUNT(*) n FROM payments p LEFT JOIN bills b ON b.id=p.bill_id WHERE b.id IS NULL',
),
0,
'no orphans after erase',
);
feat(demo): full "take it for a ride" seed + surgical, correct removal The demo seeder created bills + categories only, so nearly every page rendered empty; clear-demo had a data-loss bug and error-protection gaps. Seed now populates every user-facing surface: bills with autopay states, 3-6 months of payment history (with drift + debt paydown), monthly skips/overrides/snoozes/notes, a demo bank source + accounts + matched/ unmatched/subscription transactions, categorised spending + budgets + rules, planning income/starting-amounts, category groups, an active snowball plan (+ extra-payment), merchant rules, and a calendar feed — so a new user can explore Tracker (incl. overdue/drift), Analytics, Summary, Spending, Snowball, Payoff, Transactions, Matches, Subscriptions, Banking and Calendar with realistic data. Correct removal (the emphasis): - Migration v1.07 adds an is_seeded marker to the 12 user-scoped, non-cascading demo tables (bill-children cascade with their seeded bill). - clearSeededDemoData() (new, in userDataService alongside eraseUserData) removes ONLY seeded rows in one transaction, child->parent, and keeps a seeded category if a user's own bill still references it. - Fixes the category collision: seed marks ONLY categories it creates, so clear no longer deletes the user's default categories / uncategorises their real bills. Error protection: idempotency guard on seeded (not all) bills -> 409 instead of a fake "created 0"; whole seed wrapped in one transaction (atomic); rate-limit + audit + standardizeError on all three routes; seeded-status reports payments/transactions counts; dead --force removed. Client copy/counts corrected. Verified: typecheck/check clean; server suite 232/232 (+6 new); client typecheck/lint/build clean; e2e probe 17/17; on a COPY of the real prod DB the v1.07 migration applies (integrity ok) and a seed->clear round-trip on the data-rich user restores their real data byte-for-byte with no orphans. Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 13:06:47 -05:00
});