Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,277 @@
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Generated, do not edit. sha256:58db111ac78e4a42ffb2a0d70bf951d76a2b49cabbec0d9b026ed5ba61ba33b8
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createRegistry } from '../../shared/registry.js';
|
||||
|
||||
describe('createRegistry', () => {
|
||||
it('refuses an id twice, an entry without id, and what validate rejects', () => {
|
||||
const r = createRegistry('test', { validate: e => { if (!e.run) throw new Error('no run'); } });
|
||||
r.register({ id: 'a', run: () => 1 });
|
||||
assert.throws(() => r.register({ id: 'a', run: () => 2 }), /twice/);
|
||||
assert.throws(() => r.register({ run: () => 3 }), /needs an id/);
|
||||
assert.throws(() => r.register({ id: 'b' }), /no run/);
|
||||
assert.deepEqual(r.list().map(e => e.id), ['a'], 'a refused entry is not half in');
|
||||
});
|
||||
|
||||
it('gives null for what is not registered, and a frozen entry with a feature', () => {
|
||||
const r = createRegistry('test');
|
||||
assert.equal(r.get('x'), null);
|
||||
r.register({ id: 'x' });
|
||||
assert.equal(r.get('x').feature, null);
|
||||
assert.ok(Object.isFrozen(r.get('x')));
|
||||
});
|
||||
|
||||
it('lists in registration order', () => {
|
||||
const r = createRegistry('test');
|
||||
for (const id of ['c', 'a', 'b']) r.register({ id });
|
||||
assert.deepEqual(r.list().map(e => e.id), ['c', 'a', 'b']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
// Generated, do not edit. sha256:1f71cb9dde91962b1038854fb255fb8b67bd26c20dadc041dd312c033c2e7226
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import {
|
||||
isMetal, hasSubunit, toSubunit, fromSubunit, unitToSubunit,
|
||||
mainToSub, subToMain, toBaseByFactor, fromBaseByFactor, metalFactor,
|
||||
priceToBase, priceFromBase, unitPriceToBase, totalFromPrice, priceFromTotal, amountFromTotal,
|
||||
} from '../../shared/units.js';
|
||||
import { CONFIG } from '../../shared/config.js';
|
||||
|
||||
const BTC = { kind: 'crypto', decimals: 8, unit: 'BTC', subunit: 'sat', displayUnit: 'unit' };
|
||||
const SATS = { ...BTC, displayUnit: 'subunit' };
|
||||
const EUR = { kind: 'fiat', decimals: 2, unit: 'EUR', subunit: 'cent', displayUnit: 'unit' };
|
||||
const GOUD = { kind: 'metal', decimals: 7, unit: 'ozt', subunit: 'g', displayUnit: 'unit' };
|
||||
|
||||
describe('unitToSubunit: altijd de hoofdeenheid van het asset', () => {
|
||||
it('rekent een crypto-hoeveelheid om met 10^decimals', () => {
|
||||
assert.equal(unitToSubunit(0.25, BTC), 25_000_000);
|
||||
assert.equal(unitToSubunit(1, BTC), 100_000_000);
|
||||
});
|
||||
|
||||
it('NEGEERT displayUnit, en dat is het hele bestaansrecht van deze functie', () => {
|
||||
assert.equal(toSubunit(0.25, SATS), 0);
|
||||
assert.equal(unitToSubunit(0.25, SATS), 25_000_000);
|
||||
});
|
||||
|
||||
it('rekent geld in centen', () => {
|
||||
assert.equal(unitToSubunit(1300, EUR), 130_000);
|
||||
assert.equal(unitToSubunit(1005.3, EUR), 100_530);
|
||||
});
|
||||
|
||||
it('houdt bij metaal het gram-pad aan, met de UNIT en niet de weergave-eenheid', () => {
|
||||
const factor = CONFIG.METALS.UNITS.ozt;
|
||||
assert.equal(unitToSubunit(1, GOUD), Math.round(factor * Math.pow(10, CONFIG.METALS.BASE_DECIMALS)));
|
||||
assert.equal(unitToSubunit(1, { ...GOUD, displayUnit: 'subunit' }), unitToSubunit(1, GOUD));
|
||||
});
|
||||
|
||||
it('weigert een hoeveelheid die geen getal is in plaats van NaN op te slaan', () => {
|
||||
assert.throws(() => unitToSubunit('geen getal', BTC), (e) => e.key === 'common.invalidAmount');
|
||||
assert.throws(() => unitToSubunit(undefined, BTC), (e) => e.key === 'common.invalidAmount');
|
||||
});
|
||||
});
|
||||
|
||||
describe('unitPriceToBase: de koers per hoofdeenheid naar de opslagbasis', () => {
|
||||
it('laat een crypto-koers ongemoeid, want die staat per hoofdeenheid', () => {
|
||||
assert.equal(unitPriceToBase(38000, BTC), 38000);
|
||||
assert.equal(unitPriceToBase(1.5, EUR), 1.5);
|
||||
});
|
||||
|
||||
it('rekent een metaalkoers van per ozt naar per gram', () => {
|
||||
const factor = CONFIG.METALS.UNITS.ozt;
|
||||
assert.equal(unitPriceToBase(2150, GOUD), 2150 / factor);
|
||||
assert.ok(Math.abs(priceFromBase(unitPriceToBase(2150, GOUD), GOUD) - 2150) < 1e-9);
|
||||
});
|
||||
|
||||
it('NEGEERT displayUnit, en dat is het hele bestaansrecht van deze functie', () => {
|
||||
const GRAMMEN = { ...GOUD, displayUnit: 'subunit' };
|
||||
assert.equal(priceToBase(2150, GRAMMEN), 2150);
|
||||
assert.equal(unitPriceToBase(2150, GRAMMEN), unitPriceToBase(2150, GOUD));
|
||||
});
|
||||
|
||||
it('weigert een koers die geen getal is', () => {
|
||||
assert.throws(() => unitPriceToBase('geen getal', GOUD), /Invalid price/);
|
||||
assert.throws(() => unitPriceToBase(undefined, BTC), /Invalid price/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toSubunit en fromSubunit blijven elkaars omgekeerde', () => {
|
||||
it('doet dat voor crypto in de hoofdeenheid', () => {
|
||||
assert.equal(fromSubunit(toSubunit(0.5, BTC), BTC), 0.5);
|
||||
});
|
||||
|
||||
it('doet dat voor een asset dat in zijn ondereenheid getoond wordt', () => {
|
||||
assert.equal(fromSubunit(toSubunit(42, SATS), SATS), 42);
|
||||
});
|
||||
|
||||
it('doet dat voor metaal', () => {
|
||||
assert.equal(fromSubunit(toSubunit(2.5, GOUD), GOUD), 2.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('de losse rekenstappen', () => {
|
||||
it('rondt hoofd naar sub af op hele eenheden', () => {
|
||||
assert.equal(mainToSub(0.000000005, 8), 1);
|
||||
assert.equal(mainToSub(0.000000004, 8), 0);
|
||||
});
|
||||
|
||||
it('poetst de float-ruis weg bij de terugweg', () => {
|
||||
assert.equal(subToMain(mainToSub(0.1, 8) + mainToSub(0.2, 8), 8), 0.3);
|
||||
});
|
||||
|
||||
it('rekent gram-basis heen en terug', () => {
|
||||
assert.equal(fromBaseByFactor(toBaseByFactor(3, 'g'), 'g'), 3);
|
||||
});
|
||||
|
||||
it('gooit op een eenheid die de config niet kent', () => {
|
||||
assert.throws(() => metalFactor('lood'), /Unknown metal unit/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('de twee vlaggen waarop de rekenpaden kiezen', () => {
|
||||
it('herkent metaal aan de soort en niet aan de eenheid', () => {
|
||||
assert.equal(isMetal(GOUD), true);
|
||||
assert.equal(isMetal(BTC), false);
|
||||
assert.equal(isMetal(null), false);
|
||||
});
|
||||
|
||||
it('ziet een lege subunit als geen subunit', () => {
|
||||
assert.equal(hasSubunit(BTC), true);
|
||||
assert.equal(hasSubunit({ ...BTC, subunit: null }), false);
|
||||
assert.equal(hasSubunit({ ...BTC, subunit: ' ' }), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('totalFromPrice en priceFromTotal', () => {
|
||||
|
||||
it('rekent een bitcoin-aankoop heen en terug', () => {
|
||||
assert.equal(priceFromTotal(500, 25_000_000, 8), 2000);
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 2000), 500);
|
||||
});
|
||||
|
||||
it('rekent een metaalaankoop in de opslagbasis en niet in de getoonde eenheid', () => {
|
||||
const eenOzt = toSubunit(1, GOUD);
|
||||
const perGram = priceFromTotal(3804, eenOzt, GOUD.decimals);
|
||||
assert.ok(Math.abs(perGram - 3804 / metalFactor('ozt')) < 1e-9, `kreeg ${perGram}`);
|
||||
assert.ok(Math.abs(priceFromBase(perGram, GOUD) - 3804) < 1e-6);
|
||||
assert.ok(Math.abs(totalFromPrice(eenOzt, GOUD.decimals, perGram) - 3804) < 1e-6);
|
||||
});
|
||||
|
||||
it('geeft NUL terug als er niets betaald is, want dat is een geldig antwoord', () => {
|
||||
assert.equal(priceFromTotal(0, 25_000_000, 8), 0);
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 0), 0);
|
||||
});
|
||||
|
||||
it('geeft null bij een hoeveelheid van nul in plaats van een deling door nul', () => {
|
||||
assert.equal(priceFromTotal(500, 0, 8), null);
|
||||
assert.equal(priceFromTotal(500, null, 8), null);
|
||||
});
|
||||
|
||||
it('geeft null bij onbruikbare invoer, en niet 0', () => {
|
||||
assert.equal(priceFromTotal('', 25_000_000, 8), null);
|
||||
assert.equal(priceFromTotal(undefined, 25_000_000, 8), null);
|
||||
assert.equal(totalFromPrice(25_000_000, 8, ''), null);
|
||||
assert.equal(totalFromPrice(NaN, 8, 100), null);
|
||||
});
|
||||
|
||||
it('telt de fee bij een AANKOOP op bij het bedrag', () => {
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 2000, 12, 'buy'), 512);
|
||||
assert.equal(priceFromTotal(512, 25_000_000, 8, 12, 'buy'), 2000);
|
||||
});
|
||||
|
||||
it('haalt de fee bij een VERKOOP van het bedrag af', () => {
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 2000, 12, 'sell'), 488);
|
||||
assert.equal(priceFromTotal(488, 25_000_000, 8, 12, 'sell'), 2000);
|
||||
});
|
||||
|
||||
it('laat een ontbrekende fee alles bij het oude', () => {
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 2000, '', 'buy'), 500);
|
||||
assert.equal(totalFromPrice(25_000_000, 8, 2000, null, 'sell'), 500);
|
||||
assert.equal(priceFromTotal(500, 25_000_000, 8, '', 'buy'), 2000);
|
||||
});
|
||||
|
||||
it('geeft null als de fee groter is dan het bedrag, in plaats van een negatieve prijs', () => {
|
||||
assert.equal(priceFromTotal(10, 25_000_000, 8, 12, 'buy'), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('amountFromTotal', () => {
|
||||
it('geeft de hoeveelheid in de opslagbasis', () => {
|
||||
assert.equal(amountFromTotal(1000, 20000, 8), 0.05 * 1e8);
|
||||
});
|
||||
|
||||
it('haalt de kosten van een AANKOOP eraf voor het delen', () => {
|
||||
assert.equal(amountFromTotal(1010, 20000, 8, 10, 'buy'), 0.05 * 1e8);
|
||||
});
|
||||
|
||||
it('telt de kosten van een VERKOOP erbij', () => {
|
||||
assert.equal(amountFromTotal(990, 20000, 8, 10, 'sell'), 0.05 * 1e8);
|
||||
});
|
||||
|
||||
it('rekent bij metaal in de opslagbasis, dus per gram', () => {
|
||||
assert.equal(amountFromTotal(1000, 50, 7), 20 * 1e7);
|
||||
});
|
||||
|
||||
it('geeft null bij een prijs van nul of lager', () => {
|
||||
assert.equal(amountFromTotal(1000, 0, 8), null);
|
||||
assert.equal(amountFromTotal(1000, -5, 8), null);
|
||||
});
|
||||
|
||||
it('geeft null als de kosten groter zijn dan het bedrag', () => {
|
||||
assert.equal(amountFromTotal(5, 20000, 8, 10, 'buy'), null);
|
||||
});
|
||||
|
||||
it('geeft null bij onleesbare invoer in plaats van NaN', () => {
|
||||
assert.equal(amountFromTotal('', 20000, 8), null);
|
||||
assert.equal(amountFromTotal(1000, '', 8), null);
|
||||
assert.equal(amountFromTotal('veel', 20000, 8), null);
|
||||
});
|
||||
|
||||
it('is de omgekeerde van totalFromPrice, kosten en al', () => {
|
||||
const basis = 0.25 * 1e8, prijs = 32000, fee = 12;
|
||||
const totaal = totalFromPrice(basis, 8, prijs, fee, 'buy');
|
||||
assert.equal(amountFromTotal(totaal, prijs, 8, fee, 'buy'), basis);
|
||||
assert.equal(priceFromTotal(totaal, basis, 8, fee, 'buy'), prijs);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user