Files
AtreidesAssetsFree/tests/price/lightning-scan.test.mjs
2026-10-29 11:57:29 +01:00

148 lines
7.0 KiB
JavaScript

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