Files

274 lines
13 KiB
JavaScript
Raw Permalink Normal View History

2026-10-30 12:28:48 +01:00
// Generated, do not edit. sha256:b4e0276b7d4bc8c4728c77282dde12358131e2103dd3182aa9dbf27cc334764b
2026-10-29 11:57:29 +01:00
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { openDatabase, closeDatabase, database, run } from '../../server/db/conn.mjs';
import { SCHEMA } from '../../server/db/schema.js';
import { seedAssets, seedPriceSource, seedDashboardProfile, seedProviders, seedPreferences } from '../../server/db/seeds.js';
import { DASHBOARD, PREFERENCES } from '../../shared/config.js';
const verseDatabase = (munt) => {
closeDatabase(); openDatabase(':memory:'); run(SCHEMA);
run(`INSERT INTO Portfolio (id, baseCurrency, createdAt) VALUES (1, ?, '2026-01-01T00:00:00Z')`, [munt]);
};
beforeEach(() => verseDatabase('EUR'));
afterEach(() => closeDatabase());
const profielen = () => database().prepare(`SELECT * FROM DashboardProfile ORDER BY id`).all().map(r => ({ ...r }));
const widgets = () => database().prepare(`SELECT * FROM DashboardWidget ORDER BY instanceKey`).all().map(r => ({ ...r }));
describe('seedDashboardProfile', () => {
it('maakt één actief systeemprofiel', () => {
seedDashboardProfile();
const p = profielen();
assert.equal(p.length, 1);
assert.equal(p[0].name, 'Standaard');
assert.equal(p[0].isSystem, 1);
assert.equal(p[0].isActive, 1);
assert.equal(p[0].sortOrder, 0);
assert.equal(p[0].searchName, 'standaard', 'afgeleid via toSearchName, niet met de hand');
});
it('zet de standaardindeling als rijen neer', () => {
seedDashboardProfile();
assert.deepEqual(widgets().map(w => w.type).sort(), DASHBOARD.DEFAULT_LAYOUT.map(w => w.type).sort());
assert.deepEqual(widgets().map(w => w.instanceKey).sort(), DASHBOARD.DEFAULT_LAYOUT.map(w => w.instId).sort());
});
it('laat hoogte en rij open, want die komen uit de meting', () => {
seedDashboardProfile();
for (const w of widgets()) {
assert.equal(w.spanHeight, null, `${w.instanceKey} heeft al een hoogte`);
assert.equal(w.gridY, 0);
assert.ok(w.spanWidth > 0, 'de breedte komt wél uit de registry: die wordt gekozen');
}
});
it('neemt de voorkeurskolom mee, zodat Meldingen rechts begint', () => {
seedDashboardProfile();
const kolom = Object.fromEntries(widgets().map(w => [w.instanceKey, w.gridX]));
assert.equal(kolom.clocks, undefined, 'geen wereldklokken bij een verse installatie');
assert.equal(kolom.notifications, DASHBOARD.GRID_COLUMNS - DASHBOARD.WIDGETS.notifications.defaultSpan);
});
it('koppelt de widgets aan het profiel en laat assetId leeg', () => {
seedDashboardProfile();
const id = profielen()[0].id;
for (const w of widgets()) {
assert.equal(w.profileId, id);
assert.equal(w.assetId, null, 'geen enkele standaardwidget hangt aan één asset');
assert.equal(w.settings, null, 'en geen van hen heeft parameters');
}
});
it('is idempotent', () => {
seedDashboardProfile();
seedDashboardProfile();
assert.equal(profielen().length, 1);
assert.equal(widgets().length, DASHBOARD.DEFAULT_LAYOUT.length, 'geen dubbele widget-rijen');
});
it('vertelt of hij iets heeft aangemaakt', () => {
assert.equal(seedDashboardProfile(), true, 'eerste keer: aangemaakt');
assert.equal(seedDashboardProfile(), false, 'tweede keer: niets te doen');
});
it('loopt niet op de unieke index als er al een ander actief profiel is', () => {
run(`INSERT INTO DashboardProfile (name, searchName, sortOrder, isActive, isSystem, createdAt, updatedAt)
VALUES ('Eigen','eigen',0,1,0,'2026-01-01T00:00:00Z','2026-01-01T00:00:00Z')`);
seedDashboardProfile();
const p = profielen();
assert.equal(p.length, 2);
assert.equal(p.filter(x => x.isActive).length, 1, 'nog steeds precies één actief');
assert.equal(p.find(x => x.isActive).name, 'Eigen', 'de bestaande keuze blijft staan');
assert.equal(p.find(x => x.isSystem).isActive, 0);
});
});
describe('seeds: de vaste-koers-provider en het EUR-asset', () => {
const provider = () => database().prepare(`SELECT * FROM Provider WHERE key = 'fixed'`).get();
const bronkeuze = () => database().prepare(
`SELECT capability, rank, status FROM PriceSource
WHERE assetId = (SELECT id FROM Asset WHERE symbol = 'EUR')
ORDER BY capability`).all().map(r => ({ ...r }));
it('zet de provider neer met een vaste koers van 1,00, zijn dekking en zijn twee capabilities', () => {
seedProviders();
const p = provider();
assert.equal(p.isSystem, 1);
assert.equal(p.baseUrl, '', 'geen URL, want er gaat geen verzoek uit');
assert.equal(database().prepare(`SELECT fiatPrice FROM ProviderFixedRate WHERE providerId = ?`).get(p.id).fiatPrice, 1);
assert.deepEqual(
database().prepare(`SELECT symbol FROM ProviderCoverage WHERE providerId = ?`).all(p.id).map(r => r.symbol),
['EUR']);
assert.deepEqual(
database().prepare(`SELECT capability FROM ProviderEndpoint WHERE providerId = ? ORDER BY capability`)
.all(p.id).map(r => r.capability),
['history_point', 'spot'], 'geen daily_history: een vaste koers heeft geen reeks');
});
it('geeft alleen de vaste-koers-provider een vaste koers', () => {
seedProviders();
assert.deepEqual(
database().prepare(`SELECT p.key FROM ProviderFixedRate f JOIN Provider p ON p.id = f.providerId`).all()
.map(r => r.key),
['fixed']);
});
it('zet het EUR-asset neer in centen', () => {
seedAssets();
const eur = database().prepare(`SELECT * FROM Asset WHERE symbol = 'EUR'`).get();
assert.equal(eur.kind, 'fiat');
assert.equal(eur.unit, 'EUR');
assert.equal(eur.subunit, 'cent', 'de cent is de opslag-eenheid van 10^decimals');
assert.equal(eur.decimals, 2);
assert.equal(eur.displayUnit, 'unit', 'niemand leest een saldo in centen');
});
it('koppelt EUR aan de vaste koers voor de spot en de historie', () => {
seedProviders();
seedAssets();
seedPriceSource(null, 'confirmed');
assert.deepEqual(bronkeuze(), [
{ capability: 'history', rank: 1, status: 'confirmed' },
{ capability: 'spot', rank: 1, status: 'confirmed' },
]);
});
it('geeft een bron alleen de keuze die hij kan leveren', () => {
seedProviders();
seedAssets();
database().exec(`DELETE FROM ProviderEndpoint WHERE capability <> 'spot'
AND providerId = (SELECT id FROM Provider WHERE key = 'fixed')`);
seedPriceSource(null, 'confirmed');
assert.deepEqual(bronkeuze(), [{ capability: 'spot', rank: 1, status: 'confirmed' }]);
});
it('is idempotent: twee keer zaaien levert één provider en één asset', () => {
seedProviders(); seedAssets(); seedPriceSource(null, 'confirmed');
seedProviders(); seedAssets(); seedPriceSource(null, 'confirmed');
const n = (q) => database().prepare(q).get().n;
assert.equal(n(`SELECT COUNT(*) AS n FROM Provider WHERE key = 'fixed'`), 1);
assert.equal(n(`SELECT COUNT(*) AS n FROM Asset WHERE symbol = 'EUR'`), 1);
assert.equal(n(`SELECT COUNT(*) AS n FROM ProviderFixedRate`), 1);
assert.equal(bronkeuze().length, 2);
});
});
describe('seeds per rekenvaluta', () => {
const zaai = () => { seedProviders(); seedAssets(); seedPriceSource(null, 'confirmed'); };
const dekking = (key) => database().prepare(
`SELECT symbol FROM ProviderCoverage WHERE providerId = (SELECT id FROM Provider WHERE key = ?) ORDER BY symbol`)
.all(key).map(r => r.symbol);
const startbron = (symbool) => database().prepare(
`SELECT DISTINCT Provider.key FROM PriceSource
JOIN Provider ON Provider.id = PriceSource.providerId
JOIN Asset ON Asset.id = PriceSource.assetId
WHERE Asset.symbol = ?`).all(symbool).map(r => r.key);
const fiat = () => database().prepare(`SELECT symbol, name, subunit, decimals FROM Asset WHERE kind = 'fiat' ORDER BY symbol`)
.all().map(r => ({ ...r }));
it('in euro: de vaste koers op de euro, en de dollar voor zijn koers', () => {
zaai();
assert.deepEqual(dekking('fixed'), ['EUR']);
assert.deepEqual(fiat().map(a => a.symbol), ['EUR', 'USD'], 'geen derde munt als de rekenvaluta al één van de twee is');
});
it('in dollars: de vaste koers op de dollar, en de euro zonder eigen bron', () => {
verseDatabase('USD');
zaai();
assert.deepEqual(dekking('fixed'), ['USD']);
assert.deepEqual(startbron('USD'), ['fixed']);
assert.deepEqual(startbron('EUR'), [], 'de koers van de euro komt van de dienst');
});
it('geeft een later toegevoegd BTC de bron die de portefeuille al heeft', () => {
zaai();
run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
VALUES ('BTC', 'Bitcoin', 'bitcoin', 'crypto', 'BTC', 'sat', 8, 'subunit')`);
const id = database().prepare(`SELECT id FROM Asset WHERE symbol = 'BTC'`).get().id;
seedPriceSource(id, 'confirmed');
assert.deepEqual(startbron('BTC'), [], 'een verse database heeft geen mempool-provider');
run(`INSERT INTO Provider (key, name, searchName, baseUrl, authenticationKind, authenticationRequired, isActive, isSeed, isSystem, createdAt)
VALUES ('mempool', 'mempool', 'mempool', 'https://mempool.space', 'none', 0, 1, 1, 0, '2026-01-01T00:00:00Z')`);
const pid = database().prepare(`SELECT id FROM Provider WHERE key = 'mempool'`).get().id;
run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate) VALUES (?, 'spot', 'GET', '/api/v1/prices')`, [pid]);
seedPriceSource(id, 'confirmed');
assert.deepEqual(startbron('BTC'), ['mempool']);
});
it('geeft de assets uit de onboarding geen eigen bron: hun koers komt van de dienst', async () => {
const { ASSET_DEFINITIONS } = await import('../../shared/config.js');
zaai();
for (const d of Object.values(ASSET_DEFINITIONS)) {
run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit) VALUES (?,?,?,?,?,?,?,?)`,
[d.symbol, d.name, d.name.toLowerCase(), d.kind, d.unit, d.subunit, d.decimals, d.displayUnit]);
seedPriceSource(database().prepare(`SELECT id FROM Asset WHERE symbol = ?`).get(d.symbol).id, 'confirmed');
assert.deepEqual(startbron(d.symbol), [], `${d.symbol} kreeg een eigen bron`);
}
});
it('zet alleen de systeemproviders neer', () => {
zaai();
const rijen = database().prepare(`SELECT key, isSystem FROM Provider ORDER BY key`).all().map(r => ({ ...r }));
assert.deepEqual(rijen.map(r => r.key), ['atreides_services', 'fixed', 'manual']);
assert.ok(rijen.every(r => r.isSystem === 1));
});
it('in ponden: drie fiat-assets, en alleen de rekenvaluta met een bron', () => {
verseDatabase('GBP');
zaai();
const munten = fiat();
assert.deepEqual(munten.map(({ name, ...rest }) => rest), [
{ symbol: 'EUR', subunit: 'cent', decimals: 2 },
{ symbol: 'GBP', subunit: null, decimals: 2 },
{ symbol: 'USD', subunit: 'cent', decimals: 2 },
]);
const pond = munten.find(a => a.symbol === 'GBP').name;
assert.match(pond, /^[A-Z].*pond$/, `'${pond}' is geen Nederlandse naam met hoofdletter`);
assert.deepEqual(dekking('fixed'), ['GBP']);
assert.deepEqual(startbron('GBP'), ['fixed']);
assert.deepEqual(startbron('USD'), []);
});
it('zet in een verse database alleen de fiat-assets neer, geen crypto en geen metaal', () => {
zaai();
const soorten = database().prepare(`SELECT DISTINCT kind FROM Asset`).all().map(r => r.kind);
assert.deepEqual(soorten, ['fiat']);
});
it('geeft een munt zonder centen ook geen centen', () => {
verseDatabase('JPY');
seedAssets();
assert.equal(fiat().find(a => a.symbol === 'JPY').decimals, 0);
});
it('weigert te zaaien zonder Portfolio-rij, in plaats van de euro aan te nemen', () => {
closeDatabase(); openDatabase(':memory:'); run(SCHEMA);
assert.throws(() => seedAssets(), /Portfolio row/);
assert.throws(() => seedProviders(), /Portfolio row/);
assert.throws(() => seedPriceSource(null, 'confirmed'), /Portfolio row/);
});
});
describe('seedPreferences', () => {
const waarde = (scope, key) => JSON.parse(database().prepare(
`SELECT value FROM Preference WHERE scope = ? AND key = ?`).get(scope, key).value);
it('zet de dienst vooraan in een verse database', () => {
seedPreferences();
assert.equal(waarde('app', 'market_spot'), 'first');
assert.equal(PREFERENCES.app.market_spot.initial, 'off', 'de terugval van een bestaande portefeuille');
});
2026-10-30 12:28:48 +01:00
it('zet de AI-koppeling uit in een verse database, en laat de terugval aan', () => {
seedPreferences();
assert.equal(waarde('ai', 'enabled'), 0);
assert.equal(PREFERENCES.ai.enabled.initial, 1, 'de terugval van een bestaande portefeuille');
});
2026-10-29 11:57:29 +01:00
it('laat de andere voorkeuren op hun initial', () => {
seedPreferences();
assert.equal(waarde('app', 'result_signs'), PREFERENCES.app.result_signs.initial);
});
});