411 lines
21 KiB
JavaScript
411 lines
21 KiB
JavaScript
// Generated, do not edit. sha256:ac2e17a56171cfd2df7cbf0535002e994a9b104a37a2b866dda8f2d218d16c98
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
|
import { AppError } from '../../shared/app-error.js';
|
|
import { CONFIG } from '../../shared/config.js';
|
|
import { isRetryLater } from '../../server/market-service.mjs';
|
|
|
|
const harness = createDbHarness({ seedPreferences: true });
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
|
|
|
const nep = { configured: true, register: [], series: null, spots: null, history: null, calls: [], spotCalls: [], historyCalls: [] };
|
|
mock.module('../../server/market-service.mjs', {
|
|
namedExports: {
|
|
isRetryLater,
|
|
marketConfigured: async () => nep.configured,
|
|
marketRegister: async () => (typeof nep.register === 'function' ? nep.register() : nep.register),
|
|
marketSeries: async (opts) => { nep.calls.push(opts); return nep.series(opts); },
|
|
marketHistory: async (opts) => { nep.historyCalls.push(opts); return nep.history(opts); },
|
|
marketSpots: async (keys, quote) => { nep.spotCalls.push({ keys, quote }); return nep.spots(keys, quote); },
|
|
},
|
|
});
|
|
|
|
const { intradaySeries, serviceSpot, serviceDayHistory, priceAtMoment } = await import('../../server/data/market.js');
|
|
|
|
const BTC_REG = { key: 'crypto:BTC', kind: 'crypto', symbol: 'BTC', unit: null, kinds: ['spot', 'history'], resolutions: ['minute', 'hour', 'day'] };
|
|
const XAU_REG = { key: 'metal:XAU', kind: 'metal', symbol: 'XAU', unit: 'ozt', kinds: ['spot', 'history'], resolutions: ['minute', 'day'] };
|
|
const ETH_DAG = { key: 'crypto:ETH', kind: 'crypto', symbol: 'ETH', unit: null, kinds: ['spot'], resolutions: ['day'] };
|
|
|
|
function asset(symbol, kind) {
|
|
harness.exports.run(
|
|
`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
|
|
VALUES (?, ?, ?, ?, ?, NULL, 8, 'unit')`, [symbol, symbol, symbol.toLowerCase(), kind, symbol]);
|
|
return harness.exports.one(`SELECT id FROM Asset WHERE symbol = ?`, [symbol]).id;
|
|
}
|
|
|
|
const sinds = () => Date.now() - 3600_000;
|
|
|
|
beforeEach(() => {
|
|
harness.recreate();
|
|
nep.configured = true;
|
|
nep.register = [BTC_REG, XAU_REG, ETH_DAG];
|
|
nep.calls = [];
|
|
nep.spotCalls = [];
|
|
nep.historyCalls = [];
|
|
nep.history = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'eulerpool', points: [] });
|
|
nep.spots = async () => ({
|
|
spot: [{ instrument: 'crypto:BTC', price: 61000, currency: 'EUR', observedAt: 1, source: 'bitvavo', sourceLabel: 'Bitvavo' }],
|
|
missing: ['crypto:ETH'], unreachable: [],
|
|
});
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'bitvavo', resolution: 'minute', points: [[o.from + 60_000, 61000]] });
|
|
});
|
|
|
|
describe('intradaySeries: wat er naar de dienst gaat', () => {
|
|
it('vraagt het instrument op soort en symbool, in de rekenvaluta, en niets over het asset', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
const from = sinds();
|
|
const uit = await intradaySeries(id, from);
|
|
|
|
assert.equal(uit.status, 'ok');
|
|
assert.equal(nep.calls.length, 1);
|
|
const vraag = nep.calls[0];
|
|
assert.deepEqual(Object.keys(vraag).sort(), ['from', 'instrument', 'quote', 'resolution', 'to']);
|
|
assert.equal(vraag.instrument, 'crypto:BTC');
|
|
assert.equal(vraag.quote, CONFIG.PORTFOLIO.DEFAULT_BASE_CURRENCY);
|
|
assert.equal(vraag.resolution, 'minute');
|
|
assert.equal(vraag.from, from);
|
|
assert.equal(Object.values(vraag).includes(id), false);
|
|
});
|
|
|
|
it('geeft de munt van de BRON door en niet de gevraagde', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'x', resolution: 'minute', points: [[o.from + 1, 66000]] });
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.currency, 'USD');
|
|
});
|
|
|
|
it('geeft de bron per punt door, ook na het omrekenen naar gram', async () => {
|
|
const id = asset('XAU', 'metal');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'goldapi', resolution: 'minute',
|
|
points: [[o.from + 1, 2488.278144, 'goldapi'], [o.from + 2, 2488.278144]] });
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.points[0][2], 'goldapi');
|
|
assert.equal(uit.points[1].length, 2);
|
|
assert.ok(Math.abs(uit.points[0][1] - 80) < 1e-9);
|
|
});
|
|
|
|
it('noemt een punt bij de naam die de dienst geeft, en zonder naam bij de sleutel', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'bitvavo', sourceLabel: 'Een Nederlandse aanbieder',
|
|
resolution: 'minute', sourceLabels: { bitvavo: 'Een Nederlandse aanbieder' },
|
|
points: [[o.from + 1, 61000, 'bitvavo'], [o.from + 2, 61010, 'mempool']] });
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.deepEqual(uit.points.map(p => p[2]), ['Een Nederlandse aanbieder', 'mempool']);
|
|
assert.equal(uit.sourceLabel, 'Een Nederlandse aanbieder');
|
|
});
|
|
});
|
|
|
|
describe('priceAtMoment', () => {
|
|
const AT = Date.parse('2026-10-19T10:00:00Z');
|
|
const W = CONFIG.MARKET_SERVICE.MOMENT_WINDOW_MS;
|
|
|
|
it('vraagt de minuten rond het moment, zonder asset-id, en kiest het dichtstbijzijnde punt', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'bitvavo', resolution: 'minute',
|
|
points: [[AT - 240_000, 60_000], [AT + 60_000, 61_000], [AT + 180_000, 62_000]] });
|
|
const uit = await priceAtMoment(id, new Date(AT).toISOString());
|
|
assert.deepEqual(uit, { status: 'ok', price: 61_000, currency: 'EUR', at: AT + 60_000, source: 'bitvavo' });
|
|
const vraag = nep.calls[0];
|
|
assert.deepEqual([vraag.instrument, vraag.resolution, vraag.from, vraag.to], ['crypto:BTC', 'minute', AT - W, AT + W]);
|
|
assert.equal(Object.values(vraag).includes(id), false);
|
|
});
|
|
|
|
it('een punt buiten het venster is geen koers van dat moment', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'bitvavo', resolution: 'minute',
|
|
points: [[AT - W - 1, 60_000]] });
|
|
assert.deepEqual(await priceAtMoment(id, new Date(AT).toISOString()), { status: 'missing' });
|
|
});
|
|
|
|
it('een metaal per gram, en de bron van het punt als de dienst die noemt', async () => {
|
|
const id = asset('XAU', 'metal');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'x', resolution: 'minute',
|
|
points: [[AT, 2488.278144, 'goldapi']] });
|
|
const uit = await priceAtMoment(id, new Date(AT).toISOString());
|
|
assert.ok(Math.abs(uit.price - 80) < 1e-9);
|
|
assert.equal(uit.source, 'goldapi');
|
|
});
|
|
|
|
it('zonder token, buiten het register of zonder minuutreeks: niets gevraagd', async () => {
|
|
const btc = asset('BTC', 'crypto');
|
|
const eth = asset('ETH', 'crypto');
|
|
nep.configured = false;
|
|
assert.deepEqual(await priceAtMoment(btc, new Date(AT).toISOString()), { status: 'off' });
|
|
nep.configured = true;
|
|
assert.deepEqual(await priceAtMoment(eth, new Date(AT).toISOString()), { status: 'notCovered' });
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
|
|
it('onbereikbaar is een antwoord, en een moment in de toekomst een contractfout', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.series = async () => { throw new AppError('marketService.unreachable', null, 'down'); };
|
|
const uit = await priceAtMoment(id, new Date(AT).toISOString());
|
|
assert.equal(uit.status, 'unavailable');
|
|
await assert.rejects(priceAtMoment(id, new Date(Date.now() + 60_000).toISOString()), /moment in the past/);
|
|
await assert.rejects(priceAtMoment(id, 'gisteren'), /moment in the past/);
|
|
});
|
|
});
|
|
|
|
describe('intradaySeries: wanneer er niets gevraagd wordt', () => {
|
|
it('zonder token: uit, zonder melding en zonder vraag aan de dienst', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.configured = false;
|
|
assert.deepEqual(await intradaySeries(id, sinds()), { status: 'off' });
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
|
|
it('een asset dat het register niet kent: niet gedekt', async () => {
|
|
const id = asset('DOGE', 'crypto');
|
|
assert.deepEqual(await intradaySeries(id, sinds()), { status: 'notCovered' });
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
|
|
it('een metaal in troy ounce: gedekt, en per gram, want zo bewaart de app metaal', async () => {
|
|
const id = asset('XAU', 'metal');
|
|
nep.series = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'goldapi', resolution: 'minute', points: [[o.from + 60_000, 2488.278144]] });
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.status, 'ok');
|
|
assert.equal(nep.calls[0].instrument, 'metal:XAU');
|
|
assert.ok(Math.abs(uit.points[0][1] - 80) < 1e-9, `${uit.points[0][1]} per gram`);
|
|
});
|
|
|
|
it('een eenheid die de app niet kent: niet gedekt, want een verkeerde factor is een stil verkeerde koers', async () => {
|
|
nep.register = [{ key: 'metal:XPT', kind: 'metal', symbol: 'XPT', unit: 'lb', resolutions: ['minute'] }];
|
|
assert.deepEqual(await intradaySeries(asset('XPT', 'metal'), sinds()), { status: 'notCovered' });
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
|
|
it('een instrument zonder minuutreeks: niet gedekt', async () => {
|
|
const id = asset('ETH', 'crypto');
|
|
assert.deepEqual(await intradaySeries(id, sinds()), { status: 'notCovered' });
|
|
});
|
|
|
|
it('dezelfde code bij een andere soort is een ander instrument', async () => {
|
|
const id = asset('BTC', 'fiat');
|
|
assert.deepEqual(await intradaySeries(id, sinds()), { status: 'notCovered' });
|
|
});
|
|
});
|
|
|
|
describe('intradaySeries: een onbereikbare dienst is een antwoord, geen fout', () => {
|
|
const metFout = (e) => { nep.series = async () => { throw e; }; };
|
|
|
|
it('api.monthUnreachable: onbeschikbaar en later opnieuw, met de maand', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
metFout(new AppError('marketService.monthUnreachable', { month: '2026-09' }, 'x'));
|
|
assert.deepEqual(await intradaySeries(id, sinds()),
|
|
{ status: 'unavailable', retryLater: true, key: 'marketService.monthUnreachable', params: { month: '2026-09' } });
|
|
});
|
|
|
|
it('dienst weg: onbeschikbaar en later opnieuw', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
metFout(new AppError('marketService.unreachable', { url: 'u', reason: 'ECONNREFUSED' }, 'x'));
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.status, 'unavailable');
|
|
assert.equal(uit.retryLater, true);
|
|
});
|
|
|
|
it('401: onbeschikbaar maar NIET later opnieuw, zodat de melding om een nieuw token vraagt', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
metFout(new AppError('marketService.unauthorized', null, 'x'));
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.status, 'unavailable');
|
|
assert.equal(uit.retryLater, false);
|
|
assert.equal(uit.key, 'marketService.unauthorized');
|
|
});
|
|
|
|
it('geeft de request-id van de mislukte aanroep mee, zodat de fout in beide logs te vinden is', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
const e = new AppError('marketService.rejected', { status: 404, key: 'api.noSeries', reason: 'x' }, 'x');
|
|
e.requestId = 'a1b2c3d4-0000-4000-8000-000000000001';
|
|
metFout(e);
|
|
assert.equal((await intradaySeries(id, sinds())).requestId, 'a1b2c3d4-0000-4000-8000-000000000001');
|
|
});
|
|
|
|
it('een fout uit het register telt net zo', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.register = () => { throw new AppError('marketService.timeout', { s: 10 }, 'x'); };
|
|
const uit = await intradaySeries(id, sinds());
|
|
assert.equal(uit.status, 'unavailable');
|
|
assert.equal(uit.retryLater, true);
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
|
|
it('een andere fout gaat gewoon door, want dat is een fout in de app', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
metFout(new TypeError('stuk'));
|
|
await assert.rejects(() => intradaySeries(id, sinds()), /stuk/);
|
|
});
|
|
});
|
|
|
|
describe('intradaySeries: contractfouten van de app zelf', () => {
|
|
it('weigert een onbekend asset', async () => {
|
|
await assert.rejects(() => intradaySeries(9999, sinds()), /Unknown asset/);
|
|
});
|
|
|
|
it('weigert een begin in de toekomst, geen getal, of verder terug dan de grens', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
const grens = CONFIG.MARKET_SERVICE.INTRADAY_MAX_DAYS * 86_400_000;
|
|
for (const slecht of [Date.now() + 60_000, 'gisteren', NaN, Date.now() - grens - 60_000]) {
|
|
await assert.rejects(() => intradaySeries(id, slecht), /sinceMs/, `sinceMs ${slecht}`);
|
|
}
|
|
assert.equal(nep.calls.length, 0);
|
|
});
|
|
});
|
|
|
|
describe('serviceSpot: de actuele koers uit de dienst (fase 3)', () => {
|
|
it('vraagt het HELE bruikbare register als pakket, in de rekenvaluta, en geeft de koers in de munt van de bron', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
const uit = await serviceSpot(id);
|
|
assert.deepEqual(uit, { status: 'ok', instrument: 'crypto:BTC', price: 61000, currency: 'EUR', observedAt: 1, source: 'bitvavo', sourceLabel: 'Bitvavo' });
|
|
assert.equal(nep.spotCalls.length, 1);
|
|
assert.deepEqual([...nep.spotCalls[0].keys].sort(), ['crypto:BTC', 'crypto:ETH', 'metal:XAU']);
|
|
assert.equal(nep.spotCalls[0].quote, CONFIG.PORTFOLIO.DEFAULT_BASE_CURRENCY);
|
|
});
|
|
|
|
it('zonder token: uit, en geen vraag', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.configured = false;
|
|
assert.deepEqual(await serviceSpot(id), { status: 'off' });
|
|
assert.equal(nep.spotCalls.length, 0);
|
|
});
|
|
|
|
it('een handmatige koers van vandaag gaat voor: de dienst wordt niet gevraagd', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
harness.exports.run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired, isActive, isSeed, isSystem, createdAt)
|
|
VALUES ('manual','Handmatig','handmatig','','none',0,1,1,1,'2026-01-01')`);
|
|
const manual = harness.exports.one(`SELECT id FROM Provider WHERE key = 'manual'`).id;
|
|
const gisteren = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10);
|
|
harness.exports.run(`INSERT INTO PriceHistory (assetId, providerId, fiatPrice, resolution, observedAt) VALUES (?, ?, 50000, 'day', ?)`,
|
|
[id, manual, `${gisteren}T00:00:00Z`]);
|
|
assert.equal((await serviceSpot(id)).status, 'ok');
|
|
|
|
const vandaag = new Date().toISOString().slice(0, 10);
|
|
harness.exports.run(`INSERT INTO PriceHistory (assetId, providerId, fiatPrice, resolution, observedAt) VALUES (?, ?, 50000, 'day', ?)`,
|
|
[id, manual, `${vandaag}T00:00:00Z`]);
|
|
const voor = nep.spotCalls.length;
|
|
assert.deepEqual(await serviceSpot(id), { status: 'local' });
|
|
assert.equal(nep.spotCalls.length, voor);
|
|
});
|
|
|
|
it('niet in het register: niet gedekt, zonder vraag om koersen', async () => {
|
|
assert.deepEqual(await serviceSpot(asset('DOGE', 'crypto')), { status: 'notCovered' });
|
|
assert.equal(nep.spotCalls.length, 0);
|
|
});
|
|
|
|
it('een metaal per troy ounce komt per gram terug, in de munt van de bron', async () => {
|
|
nep.spots = async () => ({ spot: [{ instrument: 'metal:XAU', price: 2488.278144, currency: 'USD', observedAt: 1, source: 'goldapi', sourceLabel: 'GoldAPI' }], missing: [], unreachable: [] });
|
|
const uit = await serviceSpot(asset('XAU', 'metal'));
|
|
assert.equal(uit.status, 'ok');
|
|
assert.equal(uit.currency, 'USD');
|
|
assert.ok(Math.abs(uit.price - 80) < 1e-9, `${uit.price} per gram`);
|
|
});
|
|
|
|
it('`missing` is een gat, `unreachable` is later opnieuw', async () => {
|
|
assert.deepEqual(await serviceSpot(asset('ETH', 'crypto')), { status: 'missing' });
|
|
nep.spots = async () => ({ spot: [], missing: [], unreachable: ['crypto:BTC'] });
|
|
assert.deepEqual(await serviceSpot(asset('BTC', 'crypto')),
|
|
{ status: 'unavailable', retryLater: true, key: 'marketService.spotUnreachable', params: null });
|
|
});
|
|
|
|
it('dienst weg of token geweigerd: een antwoord, met het oordeel over later opnieuw', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.spots = async () => { throw new AppError('marketService.unreachable', { url: 'u', reason: 'ECONNREFUSED' }, 'x'); };
|
|
assert.equal((await serviceSpot(id)).retryLater, true);
|
|
nep.spots = async () => { throw new AppError('marketService.unauthorized', null, 'x'); };
|
|
const uit = await serviceSpot(id);
|
|
assert.equal(uit.status, 'unavailable');
|
|
assert.equal(uit.retryLater, false);
|
|
});
|
|
|
|
it('weigert een onbekend asset', async () => {
|
|
await assert.rejects(() => serviceSpot(9999), /Unknown asset/);
|
|
});
|
|
});
|
|
|
|
describe('serviceCoverage: welke assets de dienst dekt', () => {
|
|
it('geeft de assets waarvan het register het instrument kent, metaal in troy ounce inbegrepen', async () => {
|
|
const { serviceCoverage } = await import('../../server/data/market.js');
|
|
const b = asset('BTC', 'crypto'), e = asset('ETH', 'crypto');
|
|
const x = asset('XAU', 'metal');
|
|
asset('DOGE', 'crypto');
|
|
assert.deepEqual(await serviceCoverage(), { status: 'ok', assetIds: [b, e, x] });
|
|
});
|
|
|
|
it('zonder token: uit, en het register wordt niet gevraagd', async () => {
|
|
const { serviceCoverage } = await import('../../server/data/market.js');
|
|
nep.configured = false;
|
|
let gevraagd = false;
|
|
nep.register = () => { gevraagd = true; return []; };
|
|
assert.deepEqual(await serviceCoverage(), { status: 'off' });
|
|
assert.equal(gevraagd, false);
|
|
});
|
|
|
|
it('register onbereikbaar: onbeschikbaar, geen fout', async () => {
|
|
const { serviceCoverage } = await import('../../server/data/market.js');
|
|
nep.register = () => { throw new AppError('marketService.unreachable', { url: 'u', reason: 'x' }, 'x'); };
|
|
assert.equal((await serviceCoverage()).status, 'unavailable');
|
|
});
|
|
});
|
|
|
|
describe('serviceDayHistory: dagkoersen uit de dienst (10-10-2026)', () => {
|
|
const DAG = 86_400_000;
|
|
const vandaag = () => Math.floor(Date.now() / DAG) * DAG;
|
|
const sinds = () => vandaag() - 10 * DAG;
|
|
|
|
it('geeft alleen afgesloten dagen, geen momenten en niet vandaag, in de munt van de bron', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.history = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'eulerpool', points: [
|
|
[vandaag() - 2 * DAG, 60000, 'day'], [vandaag() - 2 * DAG + 3600_000, 60500, 'moment'],
|
|
[vandaag() - DAG, 61000, 'day'], [vandaag(), 62000, 'day'],
|
|
] });
|
|
const uit = await serviceDayHistory(id, sinds());
|
|
assert.deepEqual(uit, { status: 'ok', instrument: 'crypto:BTC', currency: 'USD', source: 'eulerpool',
|
|
days: [[vandaag() - 2 * DAG, 60000], [vandaag() - DAG, 61000]] });
|
|
assert.deepEqual(Object.keys(nep.historyCalls[0]).sort(), ['from', 'instrument', 'quote', 'to']);
|
|
assert.equal(Object.values(nep.historyCalls[0]).includes(id), false, 'een asset-id ging de deur uit');
|
|
});
|
|
|
|
it('houdt per dag de bron die de dienst noemde, bij zijn naam (de alias)', async () => {
|
|
nep.history = async (o) => ({ instrument: o.instrument, currency: 'EUR', source: 'bitvavo',
|
|
sourceLabels: { bitvavo: 'Bitvavo (beurs)' }, points: [
|
|
[vandaag() - 2 * DAG, 60000, 'day', 'bitvavo'],
|
|
[vandaag() - DAG, 61000, 'day', 'onbekend'],
|
|
[vandaag() - DAG + 1, 61500, 'day'],
|
|
] });
|
|
const id = asset('BTC', 'crypto');
|
|
const uit = await serviceDayHistory(id, sinds());
|
|
assert.equal(uit.days[0][2], 'Bitvavo (beurs)', 'de alias uit sourceLabels');
|
|
assert.equal(uit.days[1][2], 'onbekend', 'zonder naam de sleutel');
|
|
assert.equal(uit.days[2].length, 2, 'een rij zonder bron krijgt er geen');
|
|
});
|
|
|
|
it('metaal per gram', async () => {
|
|
nep.history = async (o) => ({ instrument: o.instrument, currency: 'USD', source: 'eulerpool', points: [[vandaag() - DAG, 2488.278144, 'day']] });
|
|
const uit = await serviceDayHistory(asset('XAU', 'metal'), sinds());
|
|
assert.ok(Math.abs(uit.days[0][1] - 80) < 1e-9);
|
|
});
|
|
|
|
it('een instrument zonder historie in het register wordt niet gevraagd', async () => {
|
|
assert.deepEqual(await serviceDayHistory(asset('ETH', 'crypto'), sinds()), { status: 'notCovered' });
|
|
assert.equal(nep.historyCalls.length, 0);
|
|
});
|
|
|
|
it('zonder token uit, en een onbereikbare dienst is een antwoord', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
nep.configured = false;
|
|
assert.deepEqual(await serviceDayHistory(id, sinds()), { status: 'off' });
|
|
nep.configured = true;
|
|
nep.history = async () => { throw new AppError('marketService.unreachable', { url: 'u', reason: 'x' }, 'x'); };
|
|
assert.equal((await serviceDayHistory(id, sinds())).retryLater, true);
|
|
});
|
|
|
|
it('weigert een begin in de toekomst of verder terug dan de grens', async () => {
|
|
const id = asset('BTC', 'crypto');
|
|
for (const slecht of [Date.now() + DAG, 'gisteren', Date.now() - (CONFIG.MARKET_SERVICE.HISTORY_MAX_DAYS + 2) * DAG]) {
|
|
await assert.rejects(() => serviceDayHistory(id, slecht), /sinceMs/);
|
|
}
|
|
});
|
|
});
|