Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+277
View File
@@ -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);
});
});
+29
View File
@@ -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']);
});
});
+207
View File
@@ -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);
});
});