// Generated, do not edit. sha256:1e50493a927118d3db6e1a4a85d1b998ed506b6b294e4b4ef8436e2e3deb698d import { describe, it, beforeEach, mock } from 'node:test'; import assert from 'node:assert/strict'; const geschreven = { rijen: [], saldo: [], labels: [] }; mock.module('../../client/data/wallet-txs.js', { namedExports: { upsertWalletTxs: async (rows) => { geschreven.rijen.push(...rows); }, labelChannelTxs: async (accountId, txids) => { geschreven.labels.push([accountId, txids]); return txids.length; }, } }); mock.module('../../client/data/wallet-sources.js', { namedExports: { recordSourceBalance: async (id, sats) => { geschreven.saldo.push([id, sats]); } } }); let KANALEN = { open: [], closed: [] }; mock.module('../../client/lightning/lnd.js', { namedExports: { listChannels: async () => ({ channels: KANALEN.open }), listClosedChannels: async () => ({ channels: KANALEN.closed }), } }); const { walletTxRows, scanLightningSource, lightningAccountSats, isLightningSource, countMovements } = await import('../../client/lightning/lightning-scan.js'); const { channelTxids } = await import('../../client/lightning/lightning-tracker.js'); beforeEach(() => { geschreven.rijen = []; geschreven.saldo = []; geschreven.labels = []; }); const BRON = { id: 4, accountId: 2, kind: 'lnd', connectionId: 9 }; const AT = '2026-10-19T10:00:00.000Z'; describe('walletTxRows', () => { it('maakt van een betaling, een factuur en een forward elk een lightning-rij met een sleutel', () => { const { rows, skipped } = walletTxRows([ { hash: 'aa', direction: 'out', sats: 21, fee_sats: 1, at: AT }, { hash: 'bb', direction: 'in', sats: 10_000, fee_sats: 0, at: AT }, { hash: null, ref: 'fwd:1:2:3', direction: 'route', sats: 1, fee_sats: 0, at: AT }, ], BRON); assert.equal(skipped, 0); assert.deepEqual(rows.map(r => [r.txid, r.direction, r.valueSats, r.feeSats]), [['aa', 'out', 21, 1], ['bb', 'in', 10_000, null], ['fwd:1:2:3', 'in', 1, null]]); for (const r of rows) { assert.deepEqual([r.layer, r.accountId, r.sourceId, r.blockTime, r.blockHeight], ['lightning', 2, 4, AT, null]); } }); it('een betaling aan uzelf krijgt de soort self, een gewone geen', () => { const { rows } = walletTxRows([ { hash: 'aa', direction: 'out', sats: 5_000, fee_sats: 3, at: AT, self: true }, { hash: 'bb', direction: 'out', sats: 21, fee_sats: 1, at: AT, self: false }, ], BRON); assert.deepEqual(rows.map(r => [r.txid, r.kind]), [['aa', 'self'], ['bb', null]]); }); it('laat een beweging zonder sleutel of zonder moment weg, en telt hem', () => { const { rows, skipped } = walletTxRows([ { hash: null, direction: 'in', sats: 1, at: AT }, { hash: 'cc', direction: 'in', sats: 1, at: null }, ], BRON); assert.deepEqual([rows.length, skipped], [0, 2]); }); }); describe('scanLightningSource', () => { const tracker = { normalizeMovements: async () => [{ hash: 'aa', direction: 'out', sats: 21, fee_sats: 1, at: AT }], normalizeNwcMovements: async () => [{ hash: 'aa', direction: 'out', sats: 21, fee_sats: 1, at: AT }], nodeBalance: async () => ({ channelLocal: 250_000, anchor: 900_000 }), channelTxids: async () => new Set(['f'.repeat(64)]), }; it('LND: de bewegingen in de cache, de kanaal-outbound als saldo, en het kanaallabel op het account', async () => { const r = await scanLightningSource(BRON, { tracker }); assert.deepEqual(r, { count: 1, skipped: 0, balanceSats: 250_000, channelLabels: 1, channelTxids: ['f'.repeat(64)] }); assert.equal(geschreven.rijen[0].txid, 'aa'); assert.deepEqual(geschreven.saldo, [[4, 250_000]]); assert.deepEqual(geschreven.labels, [[2, ['f'.repeat(64)]]]); }); it('NWC: het eigen saldo van de wallet, van msat naar hele sats', async () => { const r = await scanLightningSource({ id: 7, accountId: 2, kind: 'nwc' }, { tracker, nwcClient: { getBalance: async () => ({ balance: 21_999 }) } }); assert.equal(r.balanceSats, 21); assert.deepEqual(geschreven.saldo, [[7, 21]]); }); it('een node die niet antwoordt: een fout, en er wordt niets geschreven', async () => { const kapot = { ...tracker, normalizeMovements: async () => { throw new Error('node weg'); } }; await assert.rejects(scanLightningSource(BRON, { tracker: kapot }), /node weg/); assert.deepEqual([geschreven.rijen.length, geschreven.saldo.length], [0, 0]); }); }); describe('channelTxids', () => { const A = 'a'.repeat(64), B = 'b'.repeat(64), C = 'c'.repeat(64); it('neemt de funding-tx van open en gesloten kanalen, en de closing-tx van een gesloten', async () => { KANALEN = { open: [{ channel_point: `${A}:0` }], closed: [{ channel_point: `${B.toUpperCase()}:1`, closing_tx_hash: C }] }; assert.deepEqual([...await channelTxids(9)].sort(), [A, B, C]); }); it('een kanaal dat nog sluit (een closing-hash van nullen) heeft geen closing-tx', async () => { KANALEN = { open: [], closed: [{ channel_point: `${B}:0`, closing_tx_hash: '0'.repeat(64) }] }; assert.deepEqual([...await channelTxids(9)], [B]); }); }); describe('lightningAccountSats', () => { it('telt LND, en een NWC-wallet niet ernaast want die is een deel van de outbound', () => { assert.equal(lightningAccountSats([ { kind: 'lnd', balanceSats: 250_000 }, { kind: 'nwc', balanceSats: 21 }, { kind: 'xpub', balanceSats: null }]), 250_000); }); it('telt een NWC-wallet wel op een account zonder LND, en een nooit bijgewerkte bron als nul', () => { assert.equal(lightningAccountSats([{ kind: 'nwc', balanceSats: 21 }, { kind: 'nwc', balanceSats: null }]), 21); }); it('herkent alleen LND en NWC als lightning', () => { assert.deepEqual(['lnd', 'nwc', 'xpub', 'address'].map(kind => isLightningSource({ kind })), [true, true, false, false]); }); }); describe('countMovements', () => { const gekoppeld = new Set([1, 4]); const isLinked = tx => gekoppeld.has(tx.id); it('een betaling op twee bronnen van één account is één beweging, gekoppeld als één rij dat is', () => { const r = countMovements([ { id: 1, layer: 'lightning', accountId: 2, txid: 'zap' }, { id: 2, layer: 'lightning', accountId: 2, txid: 'zap' }, { id: 3, layer: 'lightning', accountId: 2, txid: 'factuur' }, ], isLinked); assert.deepEqual(r, { total: 2, linked: 1 }); }); it('dezelfde hash op een ander account, of als on-chain txid, telt apart', () => { const r = countMovements([ { id: 1, layer: 'lightning', accountId: 2, txid: 'zap' }, { id: 5, layer: 'lightning', accountId: 3, txid: 'zap' }, { id: 6, layer: 'onchain', accountId: 2, txid: 'zap' }, ], isLinked); assert.deepEqual(r, { total: 3, linked: 1 }); }); it('een on-chain rij in twee walletlijsten telt één keer; twee on-chain rijen blijven twee', () => { const r = countMovements([ { id: 4, layer: 'onchain', accountId: 2, txid: 'a' }, { id: 4, layer: 'onchain', accountId: 2, txid: 'a' }, { id: 7, layer: 'onchain', accountId: 3, txid: 'a' }, ], isLinked); assert.deepEqual(r, { total: 2, linked: 1 }); }); });