// Generated, do not edit. sha256:a2c33f19cc145348f36ba31f41736c0be16d371654b28747c636f79229822fe6 import { describe, it, beforeEach, mock } from 'node:test'; import assert from 'node:assert/strict'; import { createDbHarness } from '../helpers/db-node.mjs'; const harness = createDbHarness(); mock.module('../../server/db.js', { namedExports: harness.exports }); const { listAccounts, listSelectableAccounts, deactivateCheck, deactivateAccount, reactivateAccount, } = await import('../../server/data/accounts.js'); const { eventsNetByAccount } = await import('../../server/data/events.js'); let btc, kluis, tijdelijk; beforeEach(() => { harness.recreate(); harness.exports.run( `INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit) VALUES ('BTC','Bitcoin','bitcoin','crypto','BTC','sat',8,'subunit')`); btc = harness.exports.lastId(); kluis = account('Kluis'); tijdelijk = account('Tijdelijk'); }); function account(naam, { scanEnabled = 0 } = {}) { harness.exports.run( `INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES (?,?, 'wallet', 1, ?, 1, NULL)`, [naam, naam.toLowerCase(), scanEnabled]); return harness.exports.lastId(); } function koop(accountId, sats, at = '2024-05-01T10:00:00Z') { harness.exports.run( `INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, toAccountId, source, createdAt) VALUES (?, 'buy', ?, ?, 50000, ?, 'manual', ?)`, [btc, at, sats, accountId, at]); } const rij = id => harness.exports.one(`SELECT * FROM Account WHERE id = ?`, [id]); describe('non-actief: uit de keuzelijst, maar niet uit de stand', () => { it('blijft volledig meetellen in het grootboek', async () => { koop(tijdelijk, 100_000); const voor = eventsNetByAccount(tijdelijk, btc, '2030-01-01T00:00:00Z'); await deactivateAccount(tijdelijk, '2024-06-01T00:00:00Z'); assert.equal(eventsNetByAccount(tijdelijk, btc, '2030-01-01T00:00:00Z'), voor, 'deactiveren is géén verwijderen: het saldo verandert niet'); assert.equal(voor, 100_000); }); it('blijft in listAccounts staan, want die voedt de totalen', () => { harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id = ?`, [tijdelijk]); assert.deepEqual(listAccounts().map(a => a.name).sort(), ['Kluis', 'Tijdelijk']); }); it('verdwijnt wél uit de keuzelijst', () => { harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id = ?`, [tijdelijk]); assert.deepEqual(listSelectableAccounts().map(a => a.name), ['Kluis']); }); it('komt met de expliciete opt-in tóch mee, actieve eerst', () => { harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id = ?`, [tijdelijk]); assert.deepEqual(listSelectableAccounts({ metGesloten: true }).map(a => a.name), ['Kluis', 'Tijdelijk']); }); it('houdt de huidige waarde van een bewerkformulier in de lijst', () => { harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id = ?`, [tijdelijk]); assert.deepEqual(listSelectableAccounts({ ookId: tijdelijk }).map(a => a.name), ['Kluis', 'Tijdelijk']); }); it('laat de andere non-actieve accounts wél weg bij ookId', () => { const derde = account('Derde'); harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id IN (?,?)`, [tijdelijk, derde]); assert.deepEqual(listSelectableAccounts({ ookId: tijdelijk }).map(a => a.name), ['Kluis', 'Tijdelijk']); }); it('verandert niets als ookId leeg of onbekend is', () => { harness.exports.run(`UPDATE Account SET isActive = 0, closedAt = '2024-06-01T00:00:00Z' WHERE id = ?`, [tijdelijk]); for (const opt of [{}, { ookId: null }, { ookId: undefined }, { ookId: 0 }, { ookId: 9999 }]) { assert.deepEqual(listSelectableAccounts(opt).map(a => a.name), ['Kluis'], JSON.stringify(opt)); } }); }); describe('deactivateCheck', () => { const soorten = chk => chk.findings.map(f => f.kind).sort(); it('meldt niets bij een leeg, ongebruikt account', () => { const chk = deactivateCheck(kluis); assert.equal(chk.found, true); assert.deepEqual(chk.findings, []); }); it('geeft found:false voor een onbekend account', () => { assert.deepEqual(deactivateCheck(9999), { found: false, findings: [] }); }); it('meldt dat het scannen aanstaat', () => { const a = account('Scanner', { scanEnabled: 1 }); assert.deepEqual(soorten(deactivateCheck(a)), ['scanEnabled']); }); it('meldt wallet-bronnen', () => { harness.exports.run( `INSERT INTO WalletSource (accountId, kind, value, name, createdAt) VALUES (?, 'xpub', 'zpub', 'Ledger', '2024-01-01T00:00:00Z')`, [kluis]); assert.deepEqual(soorten(deactivateCheck(kluis)), ['walletSources']); }); it('meldt een rente-afspraak zonder einddatum', () => { harness.exports.run( `INSERT INTO InterestSchedule (accountId, payoutAssetId, rateKind, startsOn, compounding, createdAt) VALUES (?,?, 'variable', '2024-01-01', 0, '2024-01-01T00:00:00Z')`, [kluis, btc]); assert.deepEqual(soorten(deactivateCheck(kluis)), ['openSchedules']); }); it('meldt een afspraak met een einddatum in de toekomst, maar niet één die al afliep', () => { harness.exports.run( `INSERT INTO InterestSchedule (accountId, payoutAssetId, rateKind, startsOn, endsOn, compounding, createdAt) VALUES (?,?, 'variable', '2020-01-01', '2020-12-31', 0, '2020-01-01T00:00:00Z')`, [kluis, btc]); assert.deepEqual(soorten(deactivateCheck(kluis)), [], 'een afgelopen afspraak is geen gevolg'); harness.exports.run( `INSERT INTO InterestSchedule (accountId, payoutAssetId, rateKind, startsOn, endsOn, compounding, createdAt) VALUES (?,?, 'variable', '2024-01-01', '2099-12-31', 0, '2024-01-01T00:00:00Z')`, [kluis, btc]); assert.deepEqual(soorten(deactivateCheck(kluis)), ['openSchedules']); }); it('meldt openstaande reconcile-voorstellen, maar niet afgehandelde', () => { const voorstel = status => harness.exports.run( `INSERT INTO ReconcileProposal (accountId, assetId, amount, direction, suggestedType, status, detectedAt) VALUES (?,?, 1000, 'in', 'transfer', ?, '2024-05-01T00:00:00Z')`, [kluis, btc, status]); voorstel('ignored'); assert.deepEqual(soorten(deactivateCheck(kluis)), [], 'afgehandeld is geen gevolg'); voorstel('pending'); assert.deepEqual(soorten(deactivateCheck(kluis)), ['pendingProposals']); }); it('meldt een saldo dat niet nul is, met het bedrag per asset', () => { koop(kluis, 250_000); const chk = deactivateCheck(kluis); const saldo = chk.findings.find(f => f.kind === 'balance'); assert.ok(saldo, 'een saldo hoort een bevinding te zijn'); assert.equal(saldo.balances[0].saldo, 250_000); }); it('meldt een saldo van nul juist NIET', () => { koop(kluis, 100_000); harness.exports.run( `INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, fromAccountId, source, createdAt) VALUES (?, 'sell', '2024-06-01T10:00:00Z', 100000, 50000, ?, 'manual', '2024-06-01T10:00:00Z')`, [btc, kluis]); const chk = deactivateCheck(kluis); assert.equal(chk.findings.some(f => f.kind === 'balance'), false, 'leeg account, geen saldo-bevinding'); assert.equal(chk.findings.some(f => f.kind === 'events'), true, 'de historie wordt wél gemeld'); }); it('meldt een saldo dat alléén uit een balans komt', () => { harness.exports.run( `INSERT INTO Valuation (assetId, valuedOn, accountId, balance, fiatRate, rateAt, source) VALUES (?, '2026-06-30', ?, 123456, 1.0, '2026-06-30T00:00:00.000Z', 'manual')`, [btc, kluis]); const chk = deactivateCheck(kluis); const saldo = chk.findings.find(f => f.kind === 'balance'); assert.ok(saldo, 'een gemeten balans is net zo goed een saldo'); assert.deepEqual(saldo.balances, [{ assetId: btc, saldo: 123456 }]); assert.equal(chk.findings.some(f => f.kind === 'events'), false, 'en er is geen historie'); }); it('meldt de fee van een verplaatsing naar dezelfde rekening', () => { harness.exports.run( `INSERT INTO Event (assetId, type, occurredAt, amount, unitFee, fromAccountId, toAccountId, source, createdAt) VALUES (?, 'transfer', '2024-06-01T10:00:00Z', 100000, 250, ?, ?, 'manual', '2024-06-01T10:00:00Z')`, [btc, kluis, kluis]); const saldo = deactivateCheck(kluis).findings.find(f => f.kind === 'balance'); assert.ok(saldo, 'de fee is een saldo'); assert.deepEqual(saldo.balances, [{ assetId: btc, saldo: -250 }]); }); it('merkt de events-bevinding aan als informatief', () => { koop(kluis, 100_000); const ev = deactivateCheck(kluis).findings.find(f => f.kind === 'events'); assert.equal(ev.informational, true, 'de historie blijft en blijft meetellen: dat is geen waarschuwing'); }); it('verandert niets aan de database', () => { koop(kluis, 100_000); const voor = rij(kluis); deactivateCheck(kluis); assert.deepEqual({ ...rij(kluis) }, { ...voor }); }); }); describe('deactiveren en heractiveren', () => { it('zet de vlag, de datum én het scannen uit', async () => { const a = account('Scanner', { scanEnabled: 1 }); await deactivateAccount(a, '2024-06-01T00:00:00Z'); const r = rij(a); assert.equal(r.isActive, 0); assert.equal(r.closedAt, '2024-06-01T00:00:00Z'); assert.equal(r.scanEnabled, 0, 'dat mag stil, want deactivateCheck heeft het gemeld'); }); it('zet bij heractiveren het scannen NIET terug aan', async () => { const a = account('Scanner', { scanEnabled: 1 }); await deactivateAccount(a, '2024-06-01T00:00:00Z'); await reactivateAccount(a); const r = rij(a); assert.equal(r.isActive, 1); assert.equal(r.closedAt, null, 'de CHECK eist dat een actief account geen afsluitdatum heeft'); assert.equal(r.scanEnabled, 0, 'scannen aanzetten is een eigen, expliciete handeling'); }); it('weigert een onbekend account in beide richtingen', async () => { await assert.rejects(() => deactivateAccount(9999), (e) => e.key === 'accounts.unknownAccount'); await assert.rejects(() => reactivateAccount(9999), (e) => e.key === 'accounts.unknownAccount'); }); });