95 lines
5.8 KiB
JavaScript
95 lines
5.8 KiB
JavaScript
// Generated, do not edit. sha256:6f5b8502746b6ae69cfa949fb746f614f73c7a3a69761281686d9078e5f7285a
|
|
import { describe, it, beforeEach, mock } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
|
|
|
const harness = createDbHarness({ seedPreferences: true });
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
|
|
|
const { listProviders, getProvider } = await import('../../server/data/providers.js');
|
|
const { listAssets } = await import('../../server/data/assets.js');
|
|
const { integrationsJson, INTEGRATIONS_FORMAT } = await import('../../server/integrations-export.mjs');
|
|
|
|
const { run, lastId } = harness.exports;
|
|
|
|
function provider(key, { system = false, auth = 'none', required = 0, header = null, param = null } = {}) {
|
|
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired, authenticationHeaderName,
|
|
authenticationParameterName, isActive, isSeed, isSystem, createdAt)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 1, ?, '2026-10-10')`,
|
|
[key, key, key, system ? '' : `https://${key}.example`, auth, required, header, param, system ? 1 : 0]);
|
|
return lastId();
|
|
}
|
|
const endpoint = (id, capability, path, query = null, quote = null, interval = null) =>
|
|
run(`INSERT INTO ProviderEndpoint (providerId, pathTemplate, queryTemplate, capability, method, quoteCurrency, minIntervalSeconds)
|
|
VALUES (?, ?, ?, ?, 'GET', ?, ?)`, [id, path, query, capability, quote, interval]);
|
|
const output = (id, capability, field, path, transform) =>
|
|
run(`INSERT INTO ProviderOutputMap (providerId, jsonPath, capability, internalField, transform) VALUES (?, ?, ?, ?, ?)`, [id, path, capability, field, transform]);
|
|
const symbol = (id, sym, externalId, { catchAll = false, unit = null } = {}) =>
|
|
run(`INSERT INTO ProviderInputMap (providerId, symbol, externalId, quoteUnit, isCatchAll) VALUES (?, ?, ?, ?, ?)`,
|
|
[id, catchAll ? null : sym, externalId, unit, catchAll ? 1 : 0]);
|
|
const covers = (id, ...syms) => { for (const s of syms) run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?, ?)`, [id, s]); };
|
|
const asset = (sym, kind) => run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
|
|
VALUES (?, ?, ?, ?, ?, NULL, 8, 'unit')`, [sym, sym, sym.toLowerCase(), kind, sym]);
|
|
|
|
const exported = (base = 'EUR') => integrationsJson(listProviders().map(p => getProvider(p.key)), listAssets(), base);
|
|
|
|
beforeEach(() => {
|
|
harness.recreate();
|
|
for (const k of ['manual', 'atreides_services', 'fixed']) provider(k, { system: true });
|
|
asset('XAU', 'metal');
|
|
asset('LINK', 'crypto');
|
|
const gold = provider('goldapi', { auth: 'header_key', required: 1, header: 'x-access-token' });
|
|
endpoint(gold, 'spot', '/api/{symbol}/EUR', null, 'EUR', 3600);
|
|
endpoint(gold, 'history_point', '/api/{symbol}/EUR/{date}', null, 'EUR');
|
|
output(gold, 'spot', 'price', 'price', 'troy_ounce_to_gram');
|
|
covers(gold, 'XAU');
|
|
run(`INSERT INTO ProviderCallLimit (providerId, callsPerMonth) VALUES (?, 100)`, [gold]);
|
|
const euler = provider('eulerpool', { auth: 'query_key', required: 1, param: 'token' });
|
|
endpoint(euler, 'daily_history', '/api/1/{external_id}/quotes/{symbol}', null, 'USD');
|
|
symbol(euler, null, 'crypto', { catchAll: true });
|
|
symbol(euler, 'XAU', 'commodity', { unit: 'ozt' });
|
|
covers(euler, 'LINK', 'XAU', 'NOASSET');
|
|
const bitvavo = provider('bitvavo', { auth: 'hmac' });
|
|
run(`INSERT INTO ProviderAuthenticationHmac (providerId, messageParts, keyHeader, signatureHeader, timestampHeader, windowHeader,
|
|
windowMs, hashAlgorithm, encoding) VALUES (?, 'timestamp,method,full_path,body', 'K', 'S', 'T', 'W', 10000, 'SHA-256', 'hex')`, [bitvavo]);
|
|
endpoint(bitvavo, 'spot', '/v2/ticker/price', 'market={symbol}-EUR', null);
|
|
covers(bitvavo, 'LINK');
|
|
});
|
|
|
|
describe('integrationsJson', () => {
|
|
it('carries every source with all it has, and no system provider', () => {
|
|
const e = exported();
|
|
assert.equal(e.format, INTEGRATIONS_FORMAT);
|
|
assert.deepEqual(e.providers.map(p => p.key), ['bitvavo', 'eulerpool', 'goldapi']);
|
|
const gold = e.providers.find(p => p.key === 'goldapi');
|
|
assert.deepEqual(gold.auth, { kind: 'header_key', required: true, headerName: 'x-access-token', parameterName: null, hmac: null });
|
|
assert.equal(gold.callsPerMonth, 100);
|
|
assert.deepEqual(gold.endpoints.map(x => [x.capability, x.pathTemplate, x.minIntervalSeconds]),
|
|
[['history_point', '/api/{symbol}/EUR/{date}', null], ['spot', '/api/{symbol}/EUR', 3600]]);
|
|
assert.deepEqual(gold.outputs, [{ capability: 'spot', internalField: 'price', jsonPath: 'price', transform: 'troy_ounce_to_gram' }]);
|
|
});
|
|
|
|
it('keeps the HMAC recipe and the catch-all, which only the reader can judge', () => {
|
|
const e = exported();
|
|
assert.equal(e.providers.find(p => p.key === 'bitvavo').auth.hmac.messageParts, 'timestamp,method,full_path,body');
|
|
assert.deepEqual(e.providers.find(p => p.key === 'eulerpool').inputMap, [
|
|
{ symbol: 'XAU', externalId: 'commodity', quoteUnit: 'ozt', isCatchAll: false },
|
|
{ symbol: null, externalId: 'crypto', quoteUnit: null, isCatchAll: true },
|
|
]);
|
|
});
|
|
|
|
it('fills in the unit of account for a NULL quote, and the kind of a covered symbol', () => {
|
|
const bitvavo = exported('USD').providers.find(p => p.key === 'bitvavo');
|
|
assert.equal(bitvavo.endpoints[0].quote, 'USD');
|
|
assert.deepEqual(exported().providers.find(p => p.key === 'eulerpool').coverage,
|
|
[{ symbol: 'LINK', kind: 'crypto' }, { symbol: 'NOASSET', kind: null }, { symbol: 'XAU', kind: 'metal' }]);
|
|
});
|
|
|
|
it('carries no secret and gives the same file twice', () => {
|
|
const text = JSON.stringify(exported());
|
|
assert.ok(!/api_key|api_secret|secret"/i.test(text), 'a secret field is in the export');
|
|
assert.equal(JSON.stringify(exported()), text);
|
|
});
|
|
});
|