412 lines
17 KiB
JavaScript
412 lines
17 KiB
JavaScript
// Generated, do not edit. sha256:a513fd9262407d2ff66521427f976635538e249ad2fcc5c2180b78d3c1d79c7f
|
|||
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
||
|
|
import { toSearchName } from '../../shared/search-name.js';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
||
|
|
import { ENUMS } from '../../shared/config.js';
|
||
|
|
|
||
|
|
const harness = createDbHarness();
|
||
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||
|
|
|
||
|
|
const {
|
||
|
|
eventCandidatesForTx, eventLinkMap, linkTxEvent, linksForEvent, linksForTx, onchainAsset,
|
||
|
|
syncAddressLinks, txCandidatesForEvent, unlinkTxEvent, walletTxEventMap
|
||
|
|
} = await import('../../server/data/tx-links.js');
|
||
|
|
|
||
|
|
const ADRES = 'bc1qvoorbeeldadresvoortests';
|
||
|
|
let ctx;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
harness.recreate();
|
||
|
|
ctx = {
|
||
|
|
btc: asset('BTC', 'Bitcoin', 'crypto'),
|
||
|
|
account: account('Hardware wallet'),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
function asset(symbol, name, kind) {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit) VALUES (?,?,?,?,?,?,?,?)`, [symbol, name, toSearchName(name), kind, symbol, null, 8, 'unit']);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
function account(naam) {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt) VALUES (?,?,?,?,?,1,NULL)`, [naam, toSearchName(naam), 'wallet', 1, 1]);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
function beweging({ direction = 'in', address = ADRES, valueSats = 100_000, txid = 'tx-1', blockTime = '2024-05-01T12:00:00' } = {}) {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO WalletTransaction (accountId, layer, txid, direction, valueSats, address, blockHeight, blockTime, createdAt)
|
||
|
|
VALUES (?,'onchain',?,?,?,?,?,?,?)`,
|
||
|
|
[ctx.account, txid, direction, valueSats, address,
|
||
|
|
blockTime ? 840_000 : null, blockTime, '2024-05-01T12:00:00']);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
function gebeurtenis({ type = 'transfer', extAddress = ADRES, amount = 100_000, occurredAt = '2024-05-01T12:00:00', assetId = null } = {}) {
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, toAccountId, source, externalAddress, createdAt)
|
||
|
|
VALUES (?,?,?,?,?,?,?,?,?)`,
|
||
|
|
[assetId ?? ctx.btc, type, occurredAt, amount, type === 'transfer' ? null : 50000,
|
||
|
|
ctx.account, 'manual', extAddress, '2024-05-01T12:00:00']);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
const links = () => harness.exports.all(
|
||
|
|
`SELECT walletTransactionId, eventId, method FROM WalletTransactionEventLink ORDER BY walletTransactionId, eventId`);
|
||
|
|
|
||
|
|
describe('syncAddressLinks: dezelfde payment hash op twee lightning-wallets', () => {
|
||
|
|
function bron(accountId, kind) {
|
||
|
|
harness.exports.run(`INSERT INTO WalletSource (accountId, kind, value, connectionId, createdAt)
|
||
|
|
VALUES (?, ?, ?, NULL, '2026-01-01')`, [accountId, kind, `${kind}-${accountId}-${Math.random()}`]);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
function betaling(accountId, sourceId, hash = 'hash-zap') {
|
||
|
|
harness.exports.run(`INSERT INTO WalletTransaction (accountId, sourceId, layer, txid, direction, valueSats, blockTime, createdAt)
|
||
|
|
VALUES (?, ?, 'lightning', ?, 'out', 21, '2026-10-19T10:00:00Z', '2026-10-19')`, [accountId, sourceId, hash]);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
const koppel = (tx, ev) => harness.exports.run(`INSERT INTO WalletTransactionEventLink (walletTransactionId, eventId, method, createdAt)
|
||
|
|
VALUES (?, ?, 'manual', '2026-10-19')`, [tx, ev]);
|
||
|
|
|
||
|
|
it('neemt de gebeurtenis van de tweeling over, in beide richtingen', async () => {
|
||
|
|
const ev = gebeurtenis({ type: 'transfer', extAddress: null });
|
||
|
|
const lnd = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
const nwc = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
koppel(lnd, ev);
|
||
|
|
assert.equal(await syncAddressLinks(), 1);
|
||
|
|
assert.deepEqual(links().find(l => l.walletTransactionId === nwc), { walletTransactionId: nwc, eventId: ev, method: 'exact' });
|
||
|
|
assert.equal(await syncAddressLinks(), 0, 'de tweede keer is er niets nieuw');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('koppelt niets over naar een ander account met dezelfde hash', async () => {
|
||
|
|
const ander = account('Iemand anders');
|
||
|
|
const ev = gebeurtenis({ type: 'transfer', extAddress: null });
|
||
|
|
const hier = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
betaling(ander, bron(ander, 'nwc'));
|
||
|
|
koppel(hier, ev);
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een verschillende hash of een on-chain rij met dezelfde txid is geen tweeling', async () => {
|
||
|
|
const ev = gebeurtenis({ type: 'transfer', extAddress: null });
|
||
|
|
const een = betaling(ctx.account, bron(ctx.account, 'nwc'), 'hash-a');
|
||
|
|
betaling(ctx.account, bron(ctx.account, 'nwc'), 'hash-b');
|
||
|
|
beweging({ txid: 'hash-a', address: null });
|
||
|
|
koppel(een, ev);
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een gekoppelde on-chain rij geeft zijn gebeurtenis niet door aan een lightning-rij met dezelfde txid', async () => {
|
||
|
|
const ev = gebeurtenis({ type: 'transfer', extAddress: null });
|
||
|
|
const keten = beweging({ txid: 'hash-x', address: null });
|
||
|
|
betaling(ctx.account, bron(ctx.account, 'nwc'), 'hash-x');
|
||
|
|
koppel(keten, ev);
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een koppeling die de gebruiker brak, komt niet terug', async () => {
|
||
|
|
const ev = gebeurtenis({ type: 'transfer', extAddress: null });
|
||
|
|
const een = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
const twee = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
koppel(een, ev);
|
||
|
|
harness.exports.run(`INSERT INTO ReconcileProposal (accountId, assetId, walletTransactionId, transactionHash, amount, direction, suggestedType, status, resolvedEventId, detectedAt)
|
||
|
|
VALUES (?, ?, ?, 'hash-zap', 21, 'out', 'transfer', 'ignored', ?, '2026-10-19')`, [ctx.account, ctx.btc, twee, ev]);
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('wat de oude LND-booker al boekte', () => {
|
||
|
|
function oudeBoeking(ref, acc = ctx.account) {
|
||
|
|
harness.exports.run(`INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, fromAccountId, source, importReference, createdAt)
|
||
|
|
VALUES (?, 'sell', '2026-10-19T10:00:00Z', 21, 50000, ?, 'lightning', ?, '2026-10-19')`, [ctx.btc, acc, ref]);
|
||
|
|
return harness.exports.lastId();
|
||
|
|
}
|
||
|
|
|
||
|
|
it('koppelt een betaling op zijn payment hash, en de NWC-tweeling neemt het over', async () => {
|
||
|
|
const ev = oudeBoeking('ln:hash-zap');
|
||
|
|
const lnd = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
const nwc = betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
assert.equal(await syncAddressLinks(), 2);
|
||
|
|
assert.deepEqual(links().map(l => [l.walletTransactionId, l.eventId, l.method]),
|
||
|
|
[[lnd, ev, 'exact'], [nwc, ev, 'exact']]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('koppelt een forward op zijn moment', async () => {
|
||
|
|
const ev = oudeBoeking('lnfwd:2026-10-19T10:00:00Z');
|
||
|
|
const fwd = betaling(ctx.account, bron(ctx.account, 'nwc'), 'fwd:1:2:3');
|
||
|
|
await syncAddressLinks();
|
||
|
|
assert.deepEqual(links().map(l => [l.walletTransactionId, l.eventId]), [[fwd, ev]]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('koppelt niet naar een boeking van een ander account, niet met een andere bron, en niet aan een betaling op hetzelfde moment', async () => {
|
||
|
|
oudeBoeking('ln:hash-zap', account('Iemand anders'));
|
||
|
|
harness.exports.run(`INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, fromAccountId, source, importReference, createdAt)
|
||
|
|
VALUES (?, 'sell', '2026-10-19T10:00:00Z', 21, 50000, ?, 'import', 'ln:hash-zap', '2026-10-19')`, [ctx.btc, ctx.account]);
|
||
|
|
oudeBoeking('lnfwd:2026-10-19T10:00:00Z');
|
||
|
|
betaling(ctx.account, bron(ctx.account, 'nwc'));
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('syncAddressLinks: exacte match op adres', () => {
|
||
|
|
it('koppelt een inkomende beweging aan een event met hetzelfde externe adres', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
const n = await syncAddressLinks();
|
||
|
|
assert.equal(n, 1, 'moet één nieuwe koppeling melden');
|
||
|
|
assert.deepEqual(links(), [{ walletTransactionId: tx, eventId: ev, method: 'exact' }]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('koppelt niets bij een afwijkend adres', async () => {
|
||
|
|
beweging({ address: ADRES });
|
||
|
|
gebeurtenis({ extAddress: 'bc1qeenheelanderadres' });
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
assert.deepEqual(links(), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('negeert uitgaande bewegingen', async () => {
|
||
|
|
beweging({ direction: 'out' });
|
||
|
|
gebeurtenis();
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('negeert bewegingen zonder adres', async () => {
|
||
|
|
beweging({ address: null });
|
||
|
|
gebeurtenis({ extAddress: null });
|
||
|
|
assert.equal(await syncAddressLinks(), 0,
|
||
|
|
'twee NULL-adressen zijn in SQL niet gelijk, en horen ook niet te matchen');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('levert bij adres-hergebruik een kruisproduct, niet een 1-op-1-match', async () => {
|
||
|
|
const tx1 = beweging({ txid: 'tx-1' });
|
||
|
|
const tx2 = beweging({ txid: 'tx-2' });
|
||
|
|
const ev1 = gebeurtenis();
|
||
|
|
const ev2 = gebeurtenis();
|
||
|
|
assert.equal(await syncAddressLinks(), 4);
|
||
|
|
assert.deepEqual(links().map(l => [l.walletTransactionId, l.eventId]),
|
||
|
|
[[tx1, ev1], [tx1, ev2], [tx2, ev1], [tx2, ev2]]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('is idempotent: tweemaal draaien levert geen extra koppelingen', async () => {
|
||
|
|
beweging();
|
||
|
|
gebeurtenis();
|
||
|
|
assert.equal(await syncAddressLinks(), 1);
|
||
|
|
assert.equal(await syncAddressLinks(), 0, 'de tweede keer is er niets nieuw');
|
||
|
|
assert.equal(links().length, 1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat een handmatig verbroken koppeling niet terugkomen', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
harness.exports.run(
|
||
|
|
`INSERT INTO ReconcileProposal
|
||
|
|
(accountId, assetId, walletTransactionId, transactionHash, amount, direction, suggestedType, status, resolvedEventId, detectedAt)
|
||
|
|
VALUES (?,?,?,?,?,?,?, 'ignored', ?,?)`,
|
||
|
|
[ctx.account, ctx.btc, tx, 'tx-1', 100_000, 'in', 'transfer', ev, '2024-05-02T00:00:00']);
|
||
|
|
|
||
|
|
assert.equal(await syncAddressLinks(), 0);
|
||
|
|
assert.deepEqual(links(), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('legt elke koppeling ook vast als afgehandelde proposal (auto_linked)', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis({ type: 'transfer' });
|
||
|
|
await syncAddressLinks();
|
||
|
|
const p = harness.exports.all(`SELECT * FROM ReconcileProposal`);
|
||
|
|
assert.equal(p.length, 1);
|
||
|
|
assert.equal(p[0].status, 'auto_linked');
|
||
|
|
assert.equal(p[0].walletTransactionId, tx);
|
||
|
|
assert.equal(p[0].resolvedEventId, ev);
|
||
|
|
assert.equal(p[0].suggestedType, 'transfer');
|
||
|
|
assert.equal(p[0].amount, 100_000);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stelt trade voor als het event geen transfer is', async () => {
|
||
|
|
beweging();
|
||
|
|
gebeurtenis({ type: 'buy' });
|
||
|
|
await syncAddressLinks();
|
||
|
|
assert.equal(harness.exports.one(`SELECT suggestedType FROM ReconcileProposal`).suggestedType, 'trade');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('vraagt alleen om persist als er iets veranderd is', async () => {
|
||
|
|
beweging();
|
||
|
|
gebeurtenis();
|
||
|
|
const voor = harness.calls.persist;
|
||
|
|
await syncAddressLinks();
|
||
|
|
assert.ok(harness.calls.persist > voor, 'een nieuwe koppeling hoort te persisteren');
|
||
|
|
|
||
|
|
const tussen = harness.calls.persist;
|
||
|
|
await syncAddressLinks();
|
||
|
|
assert.equal(harness.calls.persist, tussen, 'zonder wijziging geen schrijfactie');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('onchainAsset', () => {
|
||
|
|
it('kiest BTC als dat bestaat', () => {
|
||
|
|
asset('ETH', 'Ethereum', 'crypto');
|
||
|
|
assert.equal(onchainAsset().symbol, 'BTC');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('valt terug op het eerste crypto-asset op alfabet', () => {
|
||
|
|
harness.recreate();
|
||
|
|
ctx = { btc: null, account: account('Wallet') };
|
||
|
|
asset('SOL', 'Solana', 'crypto');
|
||
|
|
asset('ETH', 'Ethereum', 'crypto');
|
||
|
|
assert.equal(onchainAsset().symbol, 'ETH');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft null als er geen crypto-asset is', () => {
|
||
|
|
harness.recreate();
|
||
|
|
ctx = { btc: null, account: account('Wallet') };
|
||
|
|
asset('XAU', 'Goud', 'metal');
|
||
|
|
assert.equal(onchainAsset(), null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('linkTxEvent / unlinkTxEvent', () => {
|
||
|
|
it('legt een handmatige koppeling met de opgegeven methode', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
await linkTxEvent(tx, ev, 'manual');
|
||
|
|
assert.deepEqual(links(), [{ walletTransactionId: tx, eventId: ev, method: 'manual' }]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('weigert een methode buiten de SSOT-enum', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
assert.ok(!ENUMS.LINK_METHOD.includes('gokje'));
|
||
|
|
await assert.rejects(() => linkTxEvent(tx, ev, 'gokje'), /method/);
|
||
|
|
assert.deepEqual(links(), []);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('negeert een dubbele koppeling in plaats van te falen', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
await linkTxEvent(tx, ev, 'manual');
|
||
|
|
await linkTxEvent(tx, ev, 'soft');
|
||
|
|
const l = links();
|
||
|
|
assert.equal(l.length, 1, 'UNIQUE(paar) + OR IGNORE');
|
||
|
|
assert.equal(l[0].method, 'manual', 'de eerste methode blijft staan');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('verbreekt precies één paar', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev1 = gebeurtenis();
|
||
|
|
const ev2 = gebeurtenis();
|
||
|
|
await linkTxEvent(tx, ev1, 'manual');
|
||
|
|
await linkTxEvent(tx, ev2, 'manual');
|
||
|
|
await unlinkTxEvent(tx, ev1);
|
||
|
|
assert.deepEqual(links().map(l => l.eventId), [ev2]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('eventLinkMap / walletTxEventMap', () => {
|
||
|
|
it('geeft per event de gebruikte methode', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
await linkTxEvent(tx, ev, 'soft');
|
||
|
|
assert.equal(eventLinkMap().get(ev), 'soft');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('groepeert meerdere events onder één beweging', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev1 = gebeurtenis();
|
||
|
|
const ev2 = gebeurtenis();
|
||
|
|
await linkTxEvent(tx, ev1, 'manual');
|
||
|
|
await linkTxEvent(tx, ev2, 'manual');
|
||
|
|
const rij = walletTxEventMap().get(tx);
|
||
|
|
assert.equal(rij.length, 2, 'één beweging kan aan meerdere events hangen');
|
||
|
|
assert.deepEqual(rij.map(r => r.eventId).sort((a, b) => a - b), [ev1, ev2]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('is leeg zonder koppelingen', () => {
|
||
|
|
beweging();
|
||
|
|
gebeurtenis();
|
||
|
|
assert.equal(eventLinkMap().size, 0);
|
||
|
|
assert.equal(walletTxEventMap().size, 0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('eventCandidatesForTx', () => {
|
||
|
|
it('sorteert op datum-nabijheid tot de beweging', () => {
|
||
|
|
const tx = beweging({ blockTime: '2024-05-01T12:00:00' });
|
||
|
|
const ver = gebeurtenis({ occurredAt: '2024-05-20T12:00:00' });
|
||
|
|
const dicht = gebeurtenis({ occurredAt: '2024-05-02T12:00:00' });
|
||
|
|
const ids = eventCandidatesForTx(tx, ctx.btc).map(e => e.id);
|
||
|
|
assert.deepEqual(ids, [dicht, ver]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat events weg die al aan deze beweging hangen', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const gekoppeld = gebeurtenis();
|
||
|
|
const vrij = gebeurtenis({ occurredAt: '2024-05-03T12:00:00' });
|
||
|
|
await linkTxEvent(tx, gekoppeld, 'manual');
|
||
|
|
assert.deepEqual(eventCandidatesForTx(tx, ctx.btc).map(e => e.id), [vrij]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('beperkt tot het gevraagde asset', () => {
|
||
|
|
const eth = asset('ETH', 'Ethereum', 'crypto');
|
||
|
|
const tx = beweging();
|
||
|
|
const btcEvent = gebeurtenis();
|
||
|
|
gebeurtenis({ assetId: eth });
|
||
|
|
assert.deepEqual(eventCandidatesForTx(tx, ctx.btc).map(e => e.id), [btcEvent]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('valt terug op createdAt als de beweging geen blockTime heeft', () => {
|
||
|
|
const tx = beweging({ blockTime: null });
|
||
|
|
const ver = gebeurtenis({ occurredAt: '2025-01-01T12:00:00' });
|
||
|
|
const dicht = gebeurtenis({ occurredAt: '2024-05-01T13:00:00' });
|
||
|
|
assert.deepEqual(eventCandidatesForTx(tx, ctx.btc).map(e => e.id), [dicht, ver]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('txCandidatesForEvent', () => {
|
||
|
|
it('sorteert op datum-nabijheid tot het event', () => {
|
||
|
|
const ev = gebeurtenis({ occurredAt: '2024-05-10T12:00:00' });
|
||
|
|
const ver = beweging({ txid: 'ver', blockTime: '2024-01-01T12:00:00' });
|
||
|
|
const dicht = beweging({ txid: 'dicht', blockTime: '2024-05-09T12:00:00' });
|
||
|
|
assert.deepEqual(txCandidatesForEvent(ev).map(t => t.id), [dicht, ver]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat bewegingen weg die al aan dit event hangen', async () => {
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
const gekoppeld = beweging({ txid: 'a' });
|
||
|
|
const vrij = beweging({ txid: 'b', blockTime: '2024-05-05T12:00:00' });
|
||
|
|
await linkTxEvent(gekoppeld, ev, 'manual');
|
||
|
|
assert.deepEqual(txCandidatesForEvent(ev).map(t => t.id), [vrij]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('toont ook uitgaande bewegingen als kandidaat', () => {
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
const uit = beweging({ direction: 'out', txid: 'uit' });
|
||
|
|
assert.ok(txCandidatesForEvent(ev).some(t => t.id === uit));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('linksForTx / linksForEvent', () => {
|
||
|
|
it('geeft de tegenhangers met methode terug', async () => {
|
||
|
|
const tx = beweging();
|
||
|
|
const ev = gebeurtenis({ type: 'transfer' });
|
||
|
|
await linkTxEvent(tx, ev, 'soft');
|
||
|
|
|
||
|
|
const perTx = linksForTx(tx);
|
||
|
|
assert.equal(perTx.length, 1);
|
||
|
|
assert.equal(perTx[0].id, ev);
|
||
|
|
assert.equal(perTx[0].method, 'soft');
|
||
|
|
assert.equal(perTx[0].asset_symbol, 'BTC');
|
||
|
|
|
||
|
|
const perEvent = linksForEvent(ev);
|
||
|
|
assert.equal(perEvent.length, 1);
|
||
|
|
assert.equal(perEvent[0].id, tx);
|
||
|
|
assert.equal(perEvent[0].address, ADRES);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('zet bewegingen zonder blockTime bovenaan bij linksForEvent', async () => {
|
||
|
|
const ev = gebeurtenis();
|
||
|
|
const bevestigd = beweging({ txid: 'a', blockTime: '2024-05-01T12:00:00' });
|
||
|
|
const onbevestigd = beweging({ txid: 'b', blockTime: null });
|
||
|
|
await linkTxEvent(bevestigd, ev, 'manual');
|
||
|
|
await linkTxEvent(onbevestigd, ev, 'manual');
|
||
|
|
assert.deepEqual(linksForEvent(ev).map(t => t.id), [onbevestigd, bevestigd]);
|
||
|
|
});
|
||
|
|
});
|