Files
2026-10-29 11:57:29 +01:00

262 lines
12 KiB
JavaScript

// Generated, do not edit. sha256:3e601bf7d829d14e2bd02a09636bf79c397a27d8499910e51c05e4aa817aa93b
import { describe, it, beforeEach, mock } from 'node:test';
import { toSearchName } from '../../shared/search-name.js';
import assert from 'node:assert/strict';
import { createDbHarness } from '../helpers/db-node.mjs';
const harness = createDbHarness();
mock.module('../../server/db.js', { namedExports: harness.exports });
const {
apiCallsByProvider, apiMetricsByCapability, apiMetricsGlobal, apiMetricsSeries, apiMetricsTotals, recordApiCall,
} = await import('../../server/data/api-metrics.js');
const VANAF = '2020-01-01T00:00';
let ctx;
beforeEach(() => {
harness.recreate();
ctx = { mempool: provider('mempool'), coingecko: provider('coingecko') };
});
function provider(key) {
harness.exports.run(
`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired, isActive, isSeed, isSystem, createdAt) VALUES (?,?,?,?,'none',0,1,0,0,'2024-01-01T00:00:00')`, [key, key, toSearchName(key), `https://${key}.test`]);
return harness.exports.lastId();
}
function bucket({ providerId = ctx.mempool, capability = 'spot', minute, ok = 1, fail = 0, sum = 100, max = 100 }) {
harness.exports.run(
`INSERT INTO ApiCallMetric (providerId, capability, bucketMinute, okCount, failCount, sumLatencyMs, maxLatencyMs)
VALUES (?,?,?,?,?,?,?)`, [providerId, capability, minute, ok, fail, sum, max]);
}
const rijen = () => harness.exports.all(
`SELECT * FROM ApiCallMetric ORDER BY providerId, capability, bucketMinute`);
describe('recordApiCall', () => {
it('registreert een gelukte call in de bucket van deze minuut', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: 120 });
const r = rijen();
assert.equal(r.length, 1);
assert.equal(r[0].okCount, 1);
assert.equal(r[0].failCount, 0);
assert.equal(r[0].sumLatencyMs, 120);
assert.equal(r[0].maxLatencyMs, 120);
assert.match(r[0].bucketMinute, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/,
'de bucket is een minuut-prefix, want daarop wordt later per uur en dag gegroepeerd');
});
it('registreert een mislukte call apart', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: false, latency_ms: 80 });
assert.equal(rijen()[0].okCount, 0);
assert.equal(rijen()[0].failCount, 1);
assert.equal(rijen()[0].sumLatencyMs, 80, 'ook een mislukte call heeft een latency');
});
it('telt op binnen dezelfde minuut in plaats van te overschrijven', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: 100 });
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: 200 });
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: false, latency_ms: 50 });
const r = rijen();
assert.equal(r.length, 1, 'één rij per (provider, capability, minuut)');
assert.equal(r[0].okCount, 2);
assert.equal(r[0].failCount, 1);
assert.equal(r[0].sumLatencyMs, 350, 'de som telt op');
assert.equal(r[0].maxLatencyMs, 200, 'het maximum telt NIET op maar neemt het hoogste');
});
it('houdt capabilities en providers gescheiden', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: 10 });
await recordApiCall({ providerId: ctx.mempool, capability: 'daily_history', ok: true, latency_ms: 10 });
await recordApiCall({ providerId: ctx.coingecko, capability: 'spot', ok: true, latency_ms: 10 });
assert.equal(rijen().length, 3, 'de sleutel is (provider, capability, minuut)');
});
it('negeert een call zonder geldige provider', async () => {
await recordApiCall({ providerId: null, capability: 'spot', ok: true, latency_ms: 10 });
await recordApiCall({ providerId: undefined, capability: 'spot', ok: true, latency_ms: 10 });
await recordApiCall({ providerId: 'mempool', capability: 'spot', ok: true, latency_ms: 10 });
await recordApiCall({ providerId: 1.5, capability: 'spot', ok: true, latency_ms: 10 });
assert.deepEqual(rijen(), [], 'geen enkele rij, en ook geen fout');
});
it('maakt van een ontbrekende of onzinnige latency een 0', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true });
assert.equal(rijen()[0].sumLatencyMs, 0, 'de kolom is NOT NULL, dus dit moet een getal zijn');
harness.recreate();
ctx.mempool = provider('mempool');
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: NaN });
assert.equal(rijen()[0].sumLatencyMs, 0);
});
it('rondt af en klemt een negatieve latency op 0', async () => {
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: 12.6 });
assert.equal(rijen()[0].sumLatencyMs, 13);
harness.recreate();
ctx.mempool = provider('mempool');
await recordApiCall({ providerId: ctx.mempool, capability: 'spot', ok: true, latency_ms: -5 });
assert.equal(rijen()[0].sumLatencyMs, 0, 'een negatieve duur is nooit een meting');
});
});
describe('apiMetricsTotals', () => {
it('telt over de buckets en leidt gemiddelde en maximum af', () => {
bucket({ minute: '2024-05-01T10:00', ok: 2, fail: 0, sum: 300, max: 200 });
bucket({ minute: '2024-05-01T10:01', ok: 1, fail: 1, sum: 100, max: 60 });
assert.deepEqual(apiMetricsTotals(ctx.mempool, VANAF), {
ok: 3, fail: 1, calls: 4, avg_latency_ms: 100, maxLatencyMs: 200,
});
});
it('geeft nullen als er niets is, zonder deling door nul', () => {
assert.deepEqual(apiMetricsTotals(ctx.mempool, VANAF), {
ok: 0, fail: 0, calls: 0, avg_latency_ms: 0, maxLatencyMs: 0,
});
});
it('negeert buckets vóór de grens', () => {
bucket({ minute: '2024-04-30T23:59', ok: 5, sum: 500, max: 500 });
bucket({ minute: '2024-05-01T00:00', ok: 1, sum: 100, max: 100 });
const t = apiMetricsTotals(ctx.mempool, '2024-05-01T00:00');
assert.equal(t.ok, 1, 'de grens is inclusief: >= sinceBucket');
assert.equal(t.maxLatencyMs, 100, 'ook het maximum kijkt alleen binnen het venster');
});
it('kijkt alleen naar de gevraagde provider', () => {
bucket({ providerId: ctx.mempool, minute: '2024-05-01T10:00', ok: 1 });
bucket({ providerId: ctx.coingecko, minute: '2024-05-01T10:00', ok: 7 });
assert.equal(apiMetricsTotals(ctx.mempool, VANAF).ok, 1);
assert.equal(apiMetricsTotals(ctx.coingecko, VANAF).ok, 7);
});
it('rondt het gemiddelde af op een heel getal', () => {
bucket({ minute: '2024-05-01T10:00', ok: 3, fail: 0, sum: 100, max: 50 });
assert.equal(apiMetricsTotals(ctx.mempool, VANAF).avg_latency_ms, 33);
});
});
describe('apiMetricsGlobal', () => {
it('telt over alle providers heen', () => {
bucket({ providerId: ctx.mempool, minute: '2024-05-01T10:00', ok: 2, fail: 1, sum: 300, max: 150 });
bucket({ providerId: ctx.coingecko, minute: '2024-05-01T10:00', ok: 1, fail: 0, sum: 100, max: 400 });
assert.deepEqual(apiMetricsGlobal(VANAF), {
ok: 3, fail: 1, calls: 4, avg_latency_ms: 100, maxLatencyMs: 400,
});
});
it('spiegelt de vorm van apiMetricsTotals', () => {
bucket({ minute: '2024-05-01T10:00', ok: 1, sum: 10, max: 10 });
assert.deepEqual(Object.keys(apiMetricsGlobal(VANAF)).sort(),
Object.keys(apiMetricsTotals(ctx.mempool, VANAF)).sort());
});
it('geeft nullen op een lege tabel', () => {
assert.deepEqual(apiMetricsGlobal(VANAF), {
ok: 0, fail: 0, calls: 0, avg_latency_ms: 0, maxLatencyMs: 0,
});
});
});
describe('apiCallsByProvider', () => {
it('telt gelukte en mislukte aanroepen samen, per provider, over alle capabilities', () => {
bucket({ providerId: ctx.mempool, capability: 'spot', minute: '2024-05-01T10:00', ok: 2, fail: 1 });
bucket({ providerId: ctx.mempool, capability: 'daily_history', minute: '2024-05-02T10:00', ok: 1, fail: 0 });
bucket({ providerId: ctx.coingecko, minute: '2024-05-01T10:00', ok: 0, fail: 4 });
const uit = new Map(apiCallsByProvider(VANAF).map(r => [r.providerId, r.calls]));
assert.equal(uit.get(ctx.mempool), 4);
assert.equal(uit.get(ctx.coingecko), 4, 'vier mislukte aanroepen kosten ook vier van het tegoed');
});
it('telt vorige maand niet mee', () => {
bucket({ minute: '2024-04-30T23:59', ok: 50 });
bucket({ minute: '2024-05-01T00:00', ok: 1 });
assert.deepEqual(apiCallsByProvider('2024-05-01T00:00').map(r => r.calls), [1]);
});
});
describe('apiMetricsByCapability', () => {
it('groepeert per capability, alfabetisch', () => {
bucket({ capability: 'spot', minute: '2024-05-01T10:00', ok: 1, sum: 100, max: 100 });
bucket({ capability: 'spot', minute: '2024-05-01T10:01', ok: 2, sum: 200, max: 150 });
bucket({ capability: 'daily_history', minute: '2024-05-01T10:00', ok: 1, fail: 2, sum: 90, max: 90 });
const r = apiMetricsByCapability(ctx.mempool, VANAF);
assert.deepEqual(r.map(x => x.capability), ['daily_history', 'spot'],
'vaste volgorde, zodat het uitklap-paneel niet danst tussen twee aanroepen');
assert.equal(r[0].fail, 2);
assert.equal(r[1].ok, 3);
assert.equal(r[1].max_lat, 150);
});
it('geeft een lege lijst zonder metingen', () => {
assert.deepEqual(apiMetricsByCapability(ctx.mempool, VANAF), []);
});
});
describe('apiMetricsSeries', () => {
beforeEach(() => {
bucket({ minute: '2024-05-01T10:00', ok: 1, sum: 10, max: 10 });
bucket({ minute: '2024-05-01T10:30', ok: 2, sum: 20, max: 40 });
bucket({ minute: '2024-05-01T11:00', ok: 1, fail: 1, sum: 30, max: 90 });
bucket({ minute: '2024-05-02T09:00', ok: 5, sum: 50, max: 20 });
});
it('groepeert per minuut bij prefixlengte 16', () => {
assert.equal(apiMetricsSeries(ctx.mempool, VANAF, 16).length, 4, 'elke bucket apart');
});
it('groepeert per uur bij prefixlengte 13', () => {
const r = apiMetricsSeries(ctx.mempool, VANAF, 13);
assert.deepEqual(r.map(x => x.b), ['2024-05-01T10', '2024-05-01T11', '2024-05-02T09']);
assert.equal(r[0].ok, 3, 'de twee buckets van 10:xx samen');
assert.equal(r[0].max_lat, 40, 'het maximum van dat uur, niet de som');
});
it('groepeert per dag bij prefixlengte 10', () => {
const r = apiMetricsSeries(ctx.mempool, VANAF, 10);
assert.deepEqual(r.map(x => x.b), ['2024-05-01', '2024-05-02']);
assert.equal(r[0].ok, 4);
assert.equal(r[0].fail, 1);
assert.equal(r[1].ok, 5);
});
it('loopt in chronologische volgorde', () => {
const b = apiMetricsSeries(ctx.mempool, VANAF, 13).map(x => x.b);
assert.deepEqual(b, [...b].sort(), 'een sparkline moet van links naar rechts vooruit lopen');
});
it('is sparse: buckets zonder calls komen er niet in', () => {
const uren = apiMetricsSeries(ctx.mempool, VANAF, 13).map(x => x.b);
assert.equal(uren.includes('2024-05-01T12'), false);
});
it('respecteert de ondergrens', () => {
assert.deepEqual(apiMetricsSeries(ctx.mempool, '2024-05-02T00:00', 10).map(x => x.b), ['2024-05-02']);
});
});
describe('randwaardes: apiMetricsSeries op de randen', () => {
beforeEach(() => {
bucket({ minute: '2024-05-01T10:00', ok: 1, sum: 10, max: 10 });
bucket({ minute: '2024-05-05T10:00', ok: 1, sum: 10, max: 10 });
});
it('geeft een lege lijst als er ná de grens niets meer is', () => {
assert.deepEqual(apiMetricsSeries(ctx.mempool, '2030-01-01T00:00', 10), []);
});
it('geeft exact één punt terug als er één bucket binnen de grens valt', () => {
const reeks = apiMetricsSeries(ctx.mempool, '2024-05-05T10:00', 10);
assert.equal(reeks.length, 1, 'één bucket blijft één punt; er wordt niets aangevuld');
assert.equal(reeks[0].b, '2024-05-05');
});
it('is sparse: de dagen tússen twee metingen komen niet in de reeks', () => {
const reeks = apiMetricsSeries(ctx.mempool, VANAF, 10);
assert.deepEqual(reeks.map(r => r.b), ['2024-05-01', '2024-05-05']);
});
});