277 lines
13 KiB
JavaScript
277 lines
13 KiB
JavaScript
// Generated, do not edit. sha256:60e26a3323cff9f6656d123b1222be3bdc643c67ea3cef033160914cd9a62cad
|
|||
|
|
import { describe, it } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import {
|
||
|
|
dailyProvenance, provenanceCounts, provenanceRuns, provenanceOn, provenanceOfRow, originFrom, liveOrigin, PROVENANCE_KINDS,
|
||
|
|
} from '../../shared/price-provenance.js';
|
||
|
|
|
||
|
|
const MANUAL = 99;
|
||
|
|
let seq = 0;
|
||
|
|
const dag = (d, fiatPrice, extra = {}) => ({
|
||
|
|
id: ++seq, observedAt: `${d}T00:00:00Z`, resolution: 'day', fiatPrice, providerId: 1,
|
||
|
|
createdAt: `${d}T00:00:00Z`.replace(d, volgendeDag(d)), updatedAt: null, ...extra,
|
||
|
|
});
|
||
|
|
const spot = (d, tijd, fiatPrice, extra = {}) => ({
|
||
|
|
id: ++seq, observedAt: `${d}T${tijd}:00.000Z`, resolution: 'timestamp', fiatPrice, providerId: 1,
|
||
|
|
createdAt: `${d}T${tijd}:00.000Z`, updatedAt: null, ...extra,
|
||
|
|
});
|
||
|
|
function volgendeDag(d) { return new Date(Date.parse(`${d}T00:00:00Z`) + 86400000).toISOString().slice(0, 10); }
|
||
|
|
const opts = extra => ({ manualProviderId: MANUAL, today: '2026-09-25', ...extra });
|
||
|
|
const op = (uit, d) => uit.find(x => x.day === d);
|
||
|
|
|
||
|
|
describe('dailyProvenance: de soort per dag', () => {
|
||
|
|
it('een dagrij van een bron is een slotkoers, een rij op manual is met de hand', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), dag('2026-09-02', 101, { providerId: MANUAL })],
|
||
|
|
opts({ until: '2026-09-02' }));
|
||
|
|
assert.deepEqual(uit.map(d => d.kind), ['close', 'manual']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een spot van een voorbije dag is afgeleid, met de afwijking naar de slotkoersen eromheen', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), spot('2026-09-02', '09:00', 110), dag('2026-09-03', 120)],
|
||
|
|
opts({ until: '2026-09-03' }));
|
||
|
|
const d = op(uit, '2026-09-02');
|
||
|
|
assert.equal(d.kind, 'spot');
|
||
|
|
assert.equal(d.fiatPrice, 110);
|
||
|
|
assert.deepEqual(d.deviation, { before: 10, after: -8.33 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een afwijking zonder slotkoers aan een kant is null aan die kant, en een spot meet niet tegen een spot', () => {
|
||
|
|
const uit = dailyProvenance([spot('2026-09-01', '09:00', 100), spot('2026-09-02', '09:00', 110), dag('2026-09-03', 100)],
|
||
|
|
opts({ until: '2026-09-03' }));
|
||
|
|
assert.deepEqual(op(uit, '2026-09-02').deviation, { before: null, after: 10 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('vandaag is live, ook als er een dagrij staat', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-24', 100), dag('2026-09-25', 101)], opts());
|
||
|
|
assert.equal(op(uit, '2026-09-25').kind, 'live');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een dagrij die op zijn eigen dag geschreven is, is een voorlopige koers en dus afgeleid', () => {
|
||
|
|
const voorlopig = dag('2026-09-02', 105, { createdAt: '2026-09-02T14:00:00.000Z' });
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), voorlopig], opts({ until: '2026-09-02' }));
|
||
|
|
assert.equal(op(uit, '2026-09-02').kind, 'spot');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een dagrij van vóór migratie 86 (geen tijdstempels) telt als slotkoers', () => {
|
||
|
|
const oud = dag('2026-09-01', 100, { createdAt: null, updatedAt: null });
|
||
|
|
assert.equal(dailyProvenance([oud], opts({ until: '2026-09-01' }))[0].kind, 'close');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('dailyProvenance: dezelfde keuzes als de leeskant', () => {
|
||
|
|
it('een dagrij wint van een spot op dezelfde dag, in welke volgorde ze ook binnenkomen', () => {
|
||
|
|
const rijen = [spot('2026-09-01', '15:00', 200), dag('2026-09-01', 100)];
|
||
|
|
for (const volgorde of [rijen, [...rijen].reverse()]) {
|
||
|
|
const d = dailyProvenance(volgorde, opts({ until: '2026-09-01' }))[0];
|
||
|
|
assert.equal(d.kind, 'close');
|
||
|
|
assert.equal(d.fiatPrice, 100);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('van twee dagrijen wint de nieuwste (hoogste id), zoals getPricePoint', () => {
|
||
|
|
const oud = dag('2026-09-01', 100);
|
||
|
|
const nieuw = dag('2026-09-01', 102);
|
||
|
|
assert.equal(dailyProvenance([nieuw, oud], opts({ until: '2026-09-01' }))[0].fiatPrice, 102);
|
||
|
|
assert.equal(dailyProvenance([oud, nieuw], opts({ until: '2026-09-01' }))[0].fiatPrice, 102);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('van twee spots op één dag wint de laatste', () => {
|
||
|
|
const d = dailyProvenance([spot('2026-09-01', '18:00', 120), spot('2026-09-01', '09:00', 100)],
|
||
|
|
opts({ until: '2026-09-01' }))[0];
|
||
|
|
assert.equal(d.fiatPrice, 120);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een koers van nul of lager telt niet, zoals in toDailyGrid', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), dag('2026-09-02', 0), dag('2026-09-03', -5)],
|
||
|
|
opts({ until: '2026-09-03' }));
|
||
|
|
assert.deepEqual(uit.map(d => d.kind), ['close', 'carried', 'carried']);
|
||
|
|
assert.equal(op(uit, '2026-09-03').fiatPrice, 100);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('dailyProvenance: vandaag volgt de laatst geziene koers', () => {
|
||
|
|
const VANDAAG = '2026-09-25';
|
||
|
|
const voorlopig = (fiatPrice, tijd) => dag(VANDAAG, fiatPrice, { createdAt: `${VANDAAG}T06:00:00.000Z`, updatedAt: `${VANDAAG}T${tijd}:00.000Z` });
|
||
|
|
|
||
|
|
it('een spot van later vandaag wint van een eerder geschreven dagrij, in welke volgorde ook', () => {
|
||
|
|
const rijen = [voorlopig(100, '08:00'), spot(VANDAAG, '14:00', 110, { providerId: 2 })];
|
||
|
|
for (const volgorde of [rijen, [...rijen].reverse()]) {
|
||
|
|
const d = op(dailyProvenance(volgorde, opts()), VANDAAG);
|
||
|
|
assert.equal(d.fiatPrice, 110);
|
||
|
|
assert.equal(d.kind, 'live');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een dagrij die later vandaag is bijgewerkt, wint van een eerdere spot', () => {
|
||
|
|
const rijen = [spot(VANDAAG, '07:00', 110, { providerId: 2 }), voorlopig(100, '09:00')];
|
||
|
|
for (const volgorde of [rijen, [...rijen].reverse()]) {
|
||
|
|
assert.equal(op(dailyProvenance(volgorde, opts()), VANDAAG).fiatPrice, 100);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een voorbije dag houdt de regel van de leeskant: de dagrij wint, ook van een latere spot', () => {
|
||
|
|
const gisteren = '2026-09-24';
|
||
|
|
const rijen = [dag(gisteren, 100, { createdAt: `${gisteren}T08:00:00.000Z` }), spot(gisteren, '14:00', 120, { providerId: 2 })];
|
||
|
|
for (const volgorde of [rijen, [...rijen].reverse()]) {
|
||
|
|
assert.equal(op(dailyProvenance(volgorde, opts()), gisteren).fiatPrice, 100);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('dailyProvenance: doortrekken en de grens', () => {
|
||
|
|
it('crypto trekt zeven dagen door en heeft daarna geen koers', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100)], opts({ until: '2026-09-09' }));
|
||
|
|
assert.equal(op(uit, '2026-09-08').kind, 'carried');
|
||
|
|
assert.equal(op(uit, '2026-09-08').ageDays, 7);
|
||
|
|
assert.equal(op(uit, '2026-09-09').kind, 'none');
|
||
|
|
assert.equal(op(uit, '2026-09-09').fiatPrice, null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('metaal trekt veertien dagen door, fiat zonder grens', () => {
|
||
|
|
const metaal = dailyProvenance([dag('2026-08-01', 50)], opts({ assetKind: 'metal', until: '2026-08-16' }));
|
||
|
|
assert.equal(op(metaal, '2026-08-15').kind, 'carried');
|
||
|
|
assert.equal(op(metaal, '2026-08-16').kind, 'none');
|
||
|
|
const fiat = dailyProvenance([dag('2025-01-01', 0.9)], opts({ assetKind: 'fiat', until: '2026-09-01' }));
|
||
|
|
assert.equal(fiat.at(-1).kind, 'carried');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een bereik dat vóór de eerste koers begint, heeft daar geen koers', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-03', 100)], opts({ from: '2026-09-01', until: '2026-09-03' }));
|
||
|
|
assert.deepEqual(uit.map(d => d.kind), ['none', 'none', 'close']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een bereik dat ná de eerste koers begint, trekt de koers van daarvoor door', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100)], opts({ from: '2026-09-03', until: '2026-09-03' }));
|
||
|
|
assert.equal(uit[0].kind, 'carried');
|
||
|
|
assert.equal(uit[0].ageDays, 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('lege invoer geeft een lege lijst en geen fout', () => {
|
||
|
|
assert.deepEqual(dailyProvenance([], opts()), []);
|
||
|
|
assert.deepEqual(dailyProvenance(null, opts()), []);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('provenanceOn: de herkomst van één dag, voor één bedrag', () => {
|
||
|
|
it('geeft hetzelfde als het dagoverzicht voor die dag', () => {
|
||
|
|
const rijen = [dag('2026-09-01', 100), dag('2026-09-03', 102)];
|
||
|
|
assert.deepEqual(provenanceOn(rijen, '2026-09-02', opts()),
|
||
|
|
dailyProvenance(rijen, opts({ until: '2026-09-03' })).find(d => d.day === '2026-09-02'));
|
||
|
|
});
|
||
|
|
|
||
|
|
it('meet de afwijking van een afgeleide koers tegen slotkoersen BUITEN die ene dag', () => {
|
||
|
|
const rijen = [dag('2026-09-01', 100), spot('2026-09-02', '09:00', 110), dag('2026-09-03', 100)];
|
||
|
|
assert.deepEqual(provenanceOn(rijen, '2026-09-02', opts()).deviation, { before: 10, after: 10 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('beschrijft de rij met de gebruikte koers als twee bronnen dezelfde dag hebben', () => {
|
||
|
|
const eerste = dag('2026-09-01', 100, { providerId: 1 });
|
||
|
|
const tweede = dag('2026-09-01', 105, { providerId: 2 });
|
||
|
|
const d = provenanceOn([eerste, tweede], '2026-09-01', opts({ usedPrice: 100 }));
|
||
|
|
assert.equal(d.fiatPrice, 100);
|
||
|
|
assert.equal(d.row.providerId, 1);
|
||
|
|
assert.equal(d.kind, 'close');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('doet dat ook bij een doorgetrokken koers, op de dag waar hij vandaan komt', () => {
|
||
|
|
const rijen = [dag('2026-09-01', 100, { providerId: 1 }), dag('2026-09-01', 105, { providerId: 2 })];
|
||
|
|
const d = provenanceOn(rijen, '2026-09-03', opts({ usedPrice: 100 }));
|
||
|
|
assert.equal(d.kind, 'carried');
|
||
|
|
assert.equal(d.ageDays, 2);
|
||
|
|
assert.equal(d.row.providerId, 1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('noemt een gebruikte koers voorbij de ouderdomsgrens doorgetrokken, met zijn echte leeftijd', () => {
|
||
|
|
const d = provenanceOn([dag('2026-08-01', 100)], '2026-09-01', opts({ usedPrice: 100 }));
|
||
|
|
assert.equal(d.kind, 'carried');
|
||
|
|
assert.equal(d.ageDays, 31);
|
||
|
|
assert.equal(provenanceOn([dag('2026-08-01', 100)], '2026-09-01', opts()).kind, 'none', 'zonder gebruikte koers blijft het geen koers');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('houdt zijn eigen keuze als geen rij de gebruikte koers heeft, en geeft null zonder koers', () => {
|
||
|
|
const rijen = [dag('2026-09-01', 100)];
|
||
|
|
assert.equal(provenanceOn(rijen, '2026-09-01', opts({ usedPrice: 999 })).fiatPrice, 100);
|
||
|
|
assert.equal(provenanceOn([], '2026-09-01', opts()), null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('provenanceOfRow: de laatste opgeslagen koers als koers van vandaag', () => {
|
||
|
|
it('een rij van een eerdere dag is doorgetrokken, met zijn leeftijd, ook voorbij de grens', () => {
|
||
|
|
const d = provenanceOfRow(dag('2026-08-01', 100), opts());
|
||
|
|
assert.equal(d.kind, 'carried');
|
||
|
|
assert.equal(d.ageDays, 55);
|
||
|
|
assert.equal(d.day, '2026-09-25');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('een rij van vandaag is live, een met de hand blijft met de hand', () => {
|
||
|
|
assert.equal(provenanceOfRow(spot('2026-09-25', '09:00', 100), opts()).kind, 'live');
|
||
|
|
assert.equal(provenanceOfRow(dag('2026-09-25', 100, { providerId: MANUAL }), opts()).kind, 'manual');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('beschrijft een rij als koers van een dag in het verleden, voor een verplaatsing op die dag', () => {
|
||
|
|
assert.equal(provenanceOfRow(dag('2026-03-03', 100), opts({ day: '2026-03-03' })).kind, 'close');
|
||
|
|
const afgeleid = provenanceOfRow({ ...spot('2026-03-03', '09:00', 100), derivedFrom: 'spot' }, opts({ day: '2026-03-03' }));
|
||
|
|
assert.equal(afgeleid.kind, 'spot', 'de laatste spot van die dag is afgeleid en niet live');
|
||
|
|
assert.equal(provenanceOfRow(dag('2026-03-01', 100), opts({ day: '2026-03-03' })).ageDays, 2);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft null zonder rij of zonder koers', () => {
|
||
|
|
assert.equal(provenanceOfRow(null, opts()), null);
|
||
|
|
assert.equal(provenanceOfRow(dag('2026-09-25', 0), opts()), null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('originFrom en liveOrigin: de platte vorm voor de hover', () => {
|
||
|
|
it('noemt de bron bij naam, de brondag en bij een afgeleide koers het moment', () => {
|
||
|
|
const rij = spot('2026-09-02', '09:12', 110, { providerId: 7 });
|
||
|
|
const d = provenanceOn([dag('2026-09-01', 100), rij], '2026-09-02', opts());
|
||
|
|
const o = originFrom(d, new Map([[7, 'Bitvavo']]));
|
||
|
|
assert.equal(o.kind, 'spot');
|
||
|
|
assert.equal(o.source, 'Bitvavo');
|
||
|
|
assert.equal(o.sourceDay, '2026-09-02');
|
||
|
|
assert.equal(o.moment, rij.observedAt);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft bij een slotkoers geen moment, want die is van de hele dag', () => {
|
||
|
|
const o = originFrom(provenanceOn([dag('2026-09-01', 100)], '2026-09-01', opts()), new Map());
|
||
|
|
assert.equal(o.moment, null);
|
||
|
|
assert.equal(o.source, null, 'een onbekende bron is null en geen verzonnen naam');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft null zonder koers, en een live spot zegt alleen dat hij live is', () => {
|
||
|
|
assert.equal(originFrom(null), null);
|
||
|
|
assert.equal(originFrom({ day: '2026-09-01', fiatPrice:null, kind: 'none', row: null }), null);
|
||
|
|
assert.deepEqual(liveOrigin(100, null, new Date('2026-09-25T10:00:00Z')),
|
||
|
|
{ day: '2026-09-25', kind: 'live', fiatPrice:100, ageDays: 0, deviation: null, source: null, sourceDay: null, moment: '2026-09-25T10:00:00.000Z' });
|
||
|
|
assert.equal(liveOrigin(null), null);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('provenanceRuns', () => {
|
||
|
|
it('voegt opeenvolgende dagen van dezelfde soort samen en breekt bij elke wissel', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), dag('2026-09-02', 100), spot('2026-09-03', '09:00', 101)],
|
||
|
|
opts({ until: '2026-09-05' }));
|
||
|
|
assert.deepEqual(provenanceRuns(uit), [
|
||
|
|
{ kind: 'close', from: '2026-09-01', until: '2026-09-02', days: 2 },
|
||
|
|
{ kind: 'spot', from: '2026-09-03', until: '2026-09-03', days: 1 },
|
||
|
|
{ kind: 'carried', from: '2026-09-04', until: '2026-09-05', days: 2 },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft een lege lijst bij lege invoer', () => {
|
||
|
|
assert.deepEqual(provenanceRuns([]), []);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('provenanceCounts', () => {
|
||
|
|
it('telt per soort en noemt elke soort, ook met nul', () => {
|
||
|
|
const uit = dailyProvenance([dag('2026-09-01', 100), spot('2026-09-02', '09:00', 101)], opts({ until: '2026-09-03' }));
|
||
|
|
const n = provenanceCounts(uit);
|
||
|
|
assert.deepEqual(Object.keys(n), [...PROVENANCE_KINDS]);
|
||
|
|
assert.equal(n.close, 1);
|
||
|
|
assert.equal(n.spot, 1);
|
||
|
|
assert.equal(n.carried, 1);
|
||
|
|
assert.equal(n.none, 0);
|
||
|
|
});
|
||
|
|
});
|