165 lines
6.8 KiB
JavaScript
165 lines
6.8 KiB
JavaScript
// Generated, do not edit. sha256:dddfdfd366ef82253787978dcf8368fdcb9f0cf259774eee0d63e666ef6c597c
|
|||
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
||
|
|
import { CONFIG, ENUMS } from '../../shared/config.js';
|
||
|
|
|
||
|
|
const harness = createDbHarness();
|
||
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||
|
|
|
||
|
|
const { counts, tableCounts } = await import('../../server/data/counts.js');
|
||
|
|
const { createScanLog, latestScanLog, listScanLogs } = await import('../../server/data/scan-logs.js');
|
||
|
|
|
||
|
|
let ctx;
|
||
|
|
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')`);
|
||
|
|
const btc = harness.exports.lastId();
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES ('Kluis','kluis','wallet',1,1,1,NULL)`);
|
||
|
|
ctx = { btc, account: harness.exports.lastId() };
|
||
|
|
});
|
||
|
|
|
||
|
|
function gebeurtenis() {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, toAccountId, source, createdAt)
|
||
|
|
VALUES (?, 'buy', '2024-01-01T00:00:00', 100000, 50000, ?, 'manual', '2024-01-01T00:00:00')`,
|
||
|
|
[ctx.btc, ctx.account]);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('counts', () => {
|
||
|
|
it('telt de vier hoofd-entiteiten', () => {
|
||
|
|
gebeurtenis();
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Valuation (assetId, valuedOn, accountId, balance, fiatRate, rateAt, source)
|
||
|
|
VALUES (?, '2024-12-31', ?, 1, 1, '2024-12-31T23:00:00', 'manual')`, [ctx.btc, ctx.account]);
|
||
|
|
assert.deepEqual(counts(), { assets: 1, accounts: 1, events: 1, valuations: 1 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft nullen op een lege database in plaats van niets', () => {
|
||
|
|
harness.exports.run(`DELETE FROM Asset`);
|
||
|
|
harness.exports.run(`DELETE FROM Account`);
|
||
|
|
assert.deepEqual(counts(), { assets: 0, accounts: 0, events: 0, valuations: 0 });
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('tableCounts', () => {
|
||
|
|
it('geeft een rij per tabel uit de whitelist, in dezelfde volgorde', () => {
|
||
|
|
const rijen = tableCounts();
|
||
|
|
assert.deepEqual(rijen.map(r => r.name), [...CONFIG.DB_TABLES],
|
||
|
|
'de widget toont de lijst in configuratie-volgorde, dus die mag niet gesorteerd raken');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('draait elke naam uit de whitelist echt af zonder te werpen', () => {
|
||
|
|
const rijen = tableCounts();
|
||
|
|
assert.ok(rijen.length >= 10, `verwacht een flinke whitelist, kreeg ${rijen.length}`);
|
||
|
|
for (const r of rijen) {
|
||
|
|
assert.equal(typeof r.n, 'number', `${r.name} gaf geen getal terug`);
|
||
|
|
assert.ok(r.n >= 0);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('telt echt en geeft niet overal nul', () => {
|
||
|
|
gebeurtenis();
|
||
|
|
const perTabel = Object.fromEntries(tableCounts().map(r => [r.name, r.n]));
|
||
|
|
assert.equal(perTabel.Event, 1);
|
||
|
|
assert.equal(perTabel.Asset, 1);
|
||
|
|
assert.equal(perTabel.Valuation, 0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('createScanLog', () => {
|
||
|
|
const scan = (extra = {}) => ({ accountId: ctx.account, status: 'success', ...extra });
|
||
|
|
const rij = (id) => harness.exports.one(`SELECT * FROM ScanLog WHERE id = ?`, [id]);
|
||
|
|
|
||
|
|
it('bewaart de velden en geeft het id terug', async () => {
|
||
|
|
const id = await createScanLog(scan({
|
||
|
|
scannedAt: '2024-05-01T12:00:00', addressesFound: 12, balanceFound: 750_000, proposalsCreated: 2,
|
||
|
|
}));
|
||
|
|
const s = rij(id);
|
||
|
|
assert.equal(s.accountId, ctx.account);
|
||
|
|
assert.equal(s.scannedAt, '2024-05-01T12:00:00');
|
||
|
|
assert.equal(s.status, 'success');
|
||
|
|
assert.equal(s.addressesFound, 12);
|
||
|
|
assert.equal(s.balanceFound, 750_000);
|
||
|
|
assert.equal(s.proposalsCreated, 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('zet het tijdstip zelf als de aanroeper er geen geeft', async () => {
|
||
|
|
const id = await createScanLog(scan());
|
||
|
|
assert.ok(rij(id).scannedAt, 'een scan zonder tijdstip is niet te ordenen');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('bewaart rawData als JSON-tekst', async () => {
|
||
|
|
const id = await createScanLog(scan({ rawData: { adressen: ['bc1q…'], saldo: 750_000 } }));
|
||
|
|
const ruw = rij(id).rawData;
|
||
|
|
assert.equal(typeof ruw, 'string');
|
||
|
|
assert.deepEqual(JSON.parse(ruw), { adressen: ['bc1q…'], saldo: 750_000 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat rawData leeg als er niets is', async () => {
|
||
|
|
const id = await createScanLog(scan());
|
||
|
|
assert.equal(rij(id).rawData, null, 'geen "null" als tekst, maar echt leeg');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('accepteert elke status uit de registry en weigert de rest', async () => {
|
||
|
|
for (const status of ENUMS.SCAN_STATUS) {
|
||
|
|
const id = await createScanLog(scan({ status }));
|
||
|
|
assert.equal(rij(id).status, status);
|
||
|
|
}
|
||
|
|
await assert.rejects(() => createScanLog(scan({ status: 'bijna-goed' })), /status/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('persisteert', async () => {
|
||
|
|
const voor = harness.calls.persist;
|
||
|
|
await createScanLog(scan());
|
||
|
|
assert.equal(harness.calls.persist, voor + 1);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('latestScanLog en listScanLogs', () => {
|
||
|
|
const scan = (extra = {}) => createScanLog({ accountId: ctx.account, status: 'success', ...extra });
|
||
|
|
|
||
|
|
it('geeft de laatste scan van dat account', async () => {
|
||
|
|
await scan({ scannedAt: '2024-01-01T00:00:00' });
|
||
|
|
const laatste = await scan({ scannedAt: '2024-06-01T00:00:00' });
|
||
|
|
assert.equal(latestScanLog(ctx.account).id, laatste);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('kiest bij een gelijk tijdstip de hoogste id', async () => {
|
||
|
|
await scan({ scannedAt: '2024-06-01T00:00:00', addressesFound: 1 });
|
||
|
|
const tweede = await scan({ scannedAt: '2024-06-01T00:00:00', addressesFound: 2 });
|
||
|
|
assert.equal(latestScanLog(ctx.account).id, tweede);
|
||
|
|
assert.equal(latestScanLog(ctx.account).addressesFound, 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft null als er nog nooit gescand is', () => {
|
||
|
|
assert.equal(latestScanLog(ctx.account), null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('kijkt alleen naar het gevraagde account', async () => {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES ('Ander','ander','wallet',1,1,1,NULL)`);
|
||
|
|
const ander = harness.exports.lastId();
|
||
|
|
await scan({ scannedAt: '2024-06-01T00:00:00' });
|
||
|
|
assert.equal(latestScanLog(ander), null);
|
||
|
|
assert.deepEqual(listScanLogs(ander), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft de volledige historie, nieuwste eerst', async () => {
|
||
|
|
await scan({ scannedAt: '2024-01-01T00:00:00' });
|
||
|
|
await scan({ scannedAt: '2024-06-01T00:00:00' });
|
||
|
|
await scan({ scannedAt: '2024-03-01T00:00:00' });
|
||
|
|
assert.deepEqual(listScanLogs(ctx.account).map(s => s.scannedAt),
|
||
|
|
['2024-06-01T00:00:00', '2024-03-01T00:00:00', '2024-01-01T00:00:00']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('is append-only: een tweede scan overschrijft de eerste niet', async () => {
|
||
|
|
await scan({ scannedAt: '2024-01-01T00:00:00', status: 'failed' });
|
||
|
|
await scan({ scannedAt: '2024-06-01T00:00:00', status: 'success' });
|
||
|
|
assert.equal(listScanLogs(ctx.account).length, 2);
|
||
|
|
assert.deepEqual(listScanLogs(ctx.account).map(s => s.status), ['success', 'failed']);
|
||
|
|
});
|
||
|
|
});
|