Files
AtreidesAssetsFree/tests/repo/accounts-active.test.mjs
T
2026-10-29 11:57:29 +01:00

216 lines
10 KiB
JavaScript

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