106 lines
4.8 KiB
JavaScript
106 lines
4.8 KiB
JavaScript
// Generated, do not edit. sha256:9871fb04293a9538a99372a76f6c50a51c7cfcd7eb2971b99eaf20efe539553d
|
|||
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
||
|
|
|
||
|
|
const harness = createDbHarness();
|
||
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||
|
|
|
||
|
|
for (const mod of ['providers', 'price-source', 'price-history', 'api-metrics']) {
|
||
|
|
mock.module(`../../client/data/${mod}.js`, { namedExports: await import(`../../server/data/${mod}.js`) });
|
||
|
|
}
|
||
|
|
|
||
|
|
const facade = await import('../../client/price/providers.js');
|
||
|
|
|
||
|
|
let fetchCalls = [];
|
||
|
|
globalThis.fetch = async (url, opties) => {
|
||
|
|
const body = JSON.parse(opties.body);
|
||
|
|
fetchCalls.push(body.provider.key);
|
||
|
|
const antwoord = body.date ? { time: Math.floor(Date.parse(`${body.date}T00:00:00Z`) / 1000), price: 60 } : { price: 60 };
|
||
|
|
return { ok: true, status: 200, json: async () => antwoord };
|
||
|
|
};
|
||
|
|
|
||
|
|
const { run, lastId, one } = harness.exports;
|
||
|
|
let goud;
|
||
|
|
const maand = new Date().toISOString().slice(0, 7);
|
||
|
|
|
||
|
|
function provider(key, { limiet = null, gebruikt = 0, rang = 1, caps = ['spot', 'history_point'] } = {}) {
|
||
|
|
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired,
|
||
|
|
isActive, isSeed, isSystem, createdAt)
|
||
|
|
VALUES (?,?,?, 'https://voorbeeld.test','none',0,1,1,0,'2026-09-01T10:00:00.000Z')`, [key, key, key]);
|
||
|
|
const id = lastId();
|
||
|
|
run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?, 'XAU')`, [id]);
|
||
|
|
for (const c of caps) {
|
||
|
|
run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate) VALUES (?, ?, 'GET', '/p')`, [id, c]);
|
||
|
|
run(`INSERT OR IGNORE INTO PriceSource (assetId, capability, providerId, rank, status)
|
||
|
|
VALUES (?, ?, ?, ?, 'confirmed')`, [goud, c === 'spot' ? 'spot' : 'history', id, rang]);
|
||
|
|
}
|
||
|
|
if (limiet) run(`INSERT INTO ProviderCallLimit (providerId, callsPerMonth, createdAt) VALUES (?, ?, '2026-09-01T00:00:00Z')`, [id, limiet]);
|
||
|
|
if (gebruikt) {
|
||
|
|
run(`INSERT INTO ApiCallMetric (providerId, capability, bucketMinute, okCount, failCount, sumLatencyMs, maxLatencyMs)
|
||
|
|
VALUES (?, 'spot', ?, ?, 0, 0, 0)`, [id, `${maand}-01T00:01`, gebruikt]);
|
||
|
|
}
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
const asset = () => one(`SELECT * FROM Asset WHERE id = ?`, [goud]);
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
harness.recreate();
|
||
|
|
facade.clearSpotCache();
|
||
|
|
facade.forgetSeriesFetches();
|
||
|
|
facade.forgetUsage();
|
||
|
|
fetchCalls = [];
|
||
|
|
run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
|
||
|
|
VALUES ('XAU','Goud','goud','metal','g','mg',3,'unit')`);
|
||
|
|
goud = lastId();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('de maandlimiet', () => {
|
||
|
|
it('stuurt geen verzoek meer naar een bron op zijn limiet', async () => {
|
||
|
|
provider('goldapi', { limiet: 100, gebruikt: 100 });
|
||
|
|
assert.equal(await facade.liveSpotFor(asset()), null);
|
||
|
|
assert.deepEqual(fetchCalls, [], 'niets naar buiten');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('telt de verzoeken van deze sessie mee, en weigert het eerste boven de limiet', async () => {
|
||
|
|
provider('goldapi', { limiet: 3, gebruikt: 1 });
|
||
|
|
for (let i = 0; i < 3; i++) { facade.clearSpotCache(); await facade.liveSpotFor(asset()); }
|
||
|
|
assert.equal(fetchCalls.length, 2, 'één stond er al, dus nog twee mogen');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('schakelt voor de spot over op de volgende rang', async () => {
|
||
|
|
provider('goldapi', { limiet: 100, gebruikt: 100, rang: 1 });
|
||
|
|
provider('reserve', { rang: 2 });
|
||
|
|
assert.deepEqual(await facade.liveSpotFor(asset()), { price: 60 });
|
||
|
|
assert.deepEqual(fetchCalls, ['reserve']);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('fillClosesFor met een limiet', () => {
|
||
|
|
it('gaat na de limiet van rang 1 verder bij rang 2', async () => {
|
||
|
|
provider('goldapi', { limiet: 1, rang: 1 });
|
||
|
|
provider('reserve', { rang: 2 });
|
||
|
|
const uit = await facade.fillClosesFor(asset(), '2026-09-01', '2026-09-03', { today: '2026-09-24' });
|
||
|
|
assert.deepEqual(fetchCalls, ['goldapi', 'reserve', 'reserve']);
|
||
|
|
assert.equal(uit.written, 3);
|
||
|
|
assert.equal(uit.limitReached, false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stopt de ronde als geen enkele bron nog tegoed heeft, in plaats van alle dagen af te lopen', async () => {
|
||
|
|
provider('goldapi', { limiet: 1 });
|
||
|
|
const uit = await facade.fillClosesFor(asset(), '2026-01-01', '2026-09-01', { today: '2026-09-24' });
|
||
|
|
assert.deepEqual(fetchCalls, ['goldapi']);
|
||
|
|
assert.equal(uit.limitReached, true);
|
||
|
|
assert.ok(uit.requested <= 2, `hij liep ${uit.requested} dagen af`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('stopt tussen twee dagen als het signaal is afgebroken', async () => {
|
||
|
|
provider('goldapi');
|
||
|
|
const stop = new AbortController();
|
||
|
|
stop.abort();
|
||
|
|
const uit = await facade.fillClosesFor(asset(), '2026-09-01', '2026-09-10', { today: '2026-09-24', signal: stop.signal });
|
||
|
|
assert.deepEqual(fetchCalls, []);
|
||
|
|
assert.equal(uit.stopped, true);
|
||
|
|
});
|
||
|
|
});
|