147 lines
7.1 KiB
JavaScript
147 lines
7.1 KiB
JavaScript
// Generated, do not edit. sha256:c62ff88846496db91f21b929a9f3c90f1478ad07262b244968b0d786ea63f4b8
|
|||
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
||
|
|
import { rekenvalutaUitDatabase } from '../helpers/rekenvaluta.mjs';
|
||
|
|
|
||
|
|
const harness = createDbHarness();
|
||
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||
|
|
|
||
|
|
for (const mod of ['providers', 'price-source', 'price-history', 'api-metrics', 'assets']) {
|
||
|
|
mock.module(`../../client/data/${mod}.js`, { namedExports: await import(`../../server/data/${mod}.js`) });
|
||
|
|
}
|
||
|
|
await rekenvalutaUitDatabase(mock);
|
||
|
|
|
||
|
|
const facade = await import('../../client/price/providers.js');
|
||
|
|
|
||
|
|
const tijd = dag => Math.floor(Date.parse(`${dag}T00:00:00Z`) / 1000);
|
||
|
|
let antwoord;
|
||
|
|
globalThis.fetch = async () => ({ ok: true, status: 200, json: async () => antwoord });
|
||
|
|
|
||
|
|
const { run, lastId, one } = harness.exports;
|
||
|
|
let goud, usd;
|
||
|
|
|
||
|
|
function asset(symbol, kind) {
|
||
|
|
run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
|
||
|
|
VALUES (?,?,?,?,?,'cent',2,'unit')`, [symbol, symbol, symbol.toLowerCase(), kind, symbol]);
|
||
|
|
return lastId();
|
||
|
|
}
|
||
|
|
function dollarbron() {
|
||
|
|
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired,
|
||
|
|
isActive, isSeed, isSystem, createdAt)
|
||
|
|
VALUES ('dollar','dollar','dollar','https://voorbeeld.test','none',0,1,1,0,'2026-09-01T10:00:00.000Z')`);
|
||
|
|
const p = lastId();
|
||
|
|
run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?, 'XAU')`, [p]);
|
||
|
|
for (const c of ['spot', 'history_point', 'daily_history']) {
|
||
|
|
run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate, quoteCurrency)
|
||
|
|
VALUES (?, ?, 'GET', '/p', 'USD')`, [p, c]);
|
||
|
|
}
|
||
|
|
for (const keuze of ['spot', 'history']) {
|
||
|
|
run(`INSERT INTO PriceSource (assetId, capability, providerId, rank, status)
|
||
|
|
VALUES (?, ?, ?, 1, 'confirmed')`, [goud, keuze, p]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function koers(dag, eur) {
|
||
|
|
run(`INSERT INTO PriceHistory (assetId, observedAt, resolution, fiatPrice, providerId, createdAt)
|
||
|
|
VALUES (?, ?, 'day', ?, (SELECT id FROM Provider WHERE key = 'manual'), '2026-09-24T00:00:00Z')`,
|
||
|
|
[usd, `${dag}T00:00:00Z`, eur]);
|
||
|
|
}
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
harness.recreate();
|
||
|
|
facade.clearSpotCache();
|
||
|
|
facade.forgetSeriesFetches();
|
||
|
|
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired,
|
||
|
|
isActive, isSeed, isSystem, createdAt)
|
||
|
|
VALUES ('manual','manual','manual','','none',0,1,1,1,'2026-09-01T10:00:00.000Z')`);
|
||
|
|
goud = asset('XAU', 'metal');
|
||
|
|
usd = asset('USD', 'fiat');
|
||
|
|
dollarbron();
|
||
|
|
});
|
||
|
|
const rij = id => one(`SELECT * FROM Asset WHERE id = ?`, [id]);
|
||
|
|
|
||
|
|
describe('omrekenen naar euro', () => {
|
||
|
|
it('rekent een punt om met de koers van die dag, en laat het ruwe bedrag niet achter', async () => {
|
||
|
|
koers('2026-09-18', 0.85);
|
||
|
|
antwoord = { time: tijd('2026-09-18'), price: 2000 };
|
||
|
|
const p = await facade.historicalPointForVia(rij(goud), '2026-09-18');
|
||
|
|
assert.deepEqual(p, { time: tijd('2026-09-18'), price: 1700 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('neemt in het weekend de koers van vrijdag', async () => {
|
||
|
|
koers('2026-09-18', 0.85);
|
||
|
|
antwoord = { time: tijd('2026-09-20'), price: 2000 };
|
||
|
|
assert.equal((await facade.historicalPointForVia(rij(goud), '2026-09-20')).price, 1700);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft GEEN prijs als de koers ouder is dan de grens, en zeker geen dollars als euro', async () => {
|
||
|
|
koers('2026-09-01', 0.85);
|
||
|
|
antwoord = { time: tijd('2026-09-18'), price: 2000 };
|
||
|
|
assert.equal(await facade.historicalPointForVia(rij(goud), '2026-09-18'), null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('geeft geen prijs zonder asset voor die munt', async () => {
|
||
|
|
run(`DELETE FROM Asset WHERE id = ?`, [usd]);
|
||
|
|
antwoord = { time: tijd('2026-09-18'), price: 2000 };
|
||
|
|
assert.equal(await facade.historicalPointForVia(rij(goud), '2026-09-18'), null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat in een reeks de dagen zonder koers vallen en rekent de rest om', async () => {
|
||
|
|
koers('2026-09-17', 0.8);
|
||
|
|
antwoord = [
|
||
|
|
{ time: tijd('2026-08-01'), price: 1900 },
|
||
|
|
{ time: tijd('2026-09-17'), price: 2000 },
|
||
|
|
{ time: tijd('2026-09-18'), price: 2100 },
|
||
|
|
];
|
||
|
|
const reeks = await facade.fetchPriceVia('dollar', rij(goud), 'daily_history');
|
||
|
|
assert.deepEqual(reeks, [
|
||
|
|
{ time: tijd('2026-09-17'), price: 1600 },
|
||
|
|
{ time: tijd('2026-09-18'), price: 1680 },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rekent een spot om met de nieuwste koers', async () => {
|
||
|
|
const gisteren = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
|
||
|
|
koers(gisteren, 0.9);
|
||
|
|
antwoord = { price: 2000 };
|
||
|
|
assert.deepEqual(await facade.liveSpotFor(rij(goud)), { price: 1800 });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rateSourceFor: geen bron zonder keten, wel met een actieve bron, niet met een uitgezette', async () => {
|
||
|
|
assert.equal(await facade.rateSourceFor('USD'), null, 'geen keten');
|
||
|
|
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired,
|
||
|
|
isActive, isSeed, isSystem, createdAt)
|
||
|
|
VALUES ('ecb','ECB','ecb','https://voorbeeld.test','none',0,1,1,0,'2026-09-01T10:00:00.000Z')`);
|
||
|
|
const ecb = lastId();
|
||
|
|
run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?, 'USD')`, [ecb]);
|
||
|
|
run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate) VALUES (?, 'daily_history', 'GET', '/r')`, [ecb]);
|
||
|
|
run(`INSERT INTO PriceSource (assetId, capability, providerId, rank, status)
|
||
|
|
VALUES (?, 'history', ?, 1, 'confirmed')`, [usd, ecb]);
|
||
|
|
assert.equal((await facade.rateSourceFor('USD')).key, 'ecb');
|
||
|
|
run(`UPDATE Provider SET isActive = 0 WHERE id = ?`, [ecb]);
|
||
|
|
assert.equal(await facade.rateSourceFor('USD'), null, 'uitgezet');
|
||
|
|
assert.equal(await facade.rateSourceFor('EUR'), null, 'de rekenvaluta wordt niet omgerekend');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('currentRate: de koers van vandaag uit de reeks, 1 voor de rekenvaluta, en niets zonder koers', async () => {
|
||
|
|
const dag = n => new Date(Date.now() - n * 86400000).toISOString().slice(0, 10);
|
||
|
|
assert.equal(await facade.currentRate('EUR'), 1);
|
||
|
|
assert.equal(await facade.currentRate('USD'), null, 'nog geen koers');
|
||
|
|
koers(dag(30), 0.8);
|
||
|
|
assert.equal(await facade.currentRate('USD'), null, 'een koers van een maand geleden is geen koers van vandaag');
|
||
|
|
koers(dag(1), 0.86);
|
||
|
|
assert.equal(await facade.currentRate('USD'), 0.86);
|
||
|
|
assert.equal(await facade.currentRate('GBP'), null, 'geen asset voor die munt');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rekent een andere munt dan de dollar om met de koers van dát fiat-asset', async () => {
|
||
|
|
run(`UPDATE ProviderEndpoint SET quoteCurrency = 'GBP'`);
|
||
|
|
const gbp = asset('GBP', 'fiat');
|
||
|
|
run(`INSERT INTO PriceHistory (assetId, observedAt, resolution, fiatPrice, providerId, createdAt)
|
||
|
|
VALUES (?, '2026-09-18T00:00:00Z', 'day', 1.2, (SELECT id FROM Provider WHERE key = 'manual'), '2026-09-24T00:00:00Z')`, [gbp]);
|
||
|
|
antwoord = { time: tijd('2026-09-18'), price: 1000 };
|
||
|
|
assert.deepEqual(await facade.historicalPointForVia(rij(goud), '2026-09-18'),
|
||
|
|
{ time: tijd('2026-09-18'), price: 1200 });
|
||
|
|
});
|
||
|
|
});
|