140 lines
6.3 KiB
JavaScript
140 lines
6.3 KiB
JavaScript
// Generated, do not edit. sha256:9bbc954e336de37a55fc73310bcc550947a6f91fd3fa5c98c1be9921acfadb2d
|
|||
|
|
import { describe, it, before, mock } from 'node:test';
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
|
||
|
|
import { createDbHarness } from '../helpers/db-node.mjs';
|
||
|
|
import { fieldClass } from '../../server/ai/policy.js';
|
||
|
|
import { createTranslator } from '../../server/ai/translate.js';
|
||
|
|
|
||
|
|
const TEST_GEHEIM = 'test-geheim-voor-de-suite-0123456789abcdef';
|
||
|
|
|
||
|
|
const harness = createDbHarness({ seedPreferences: true });
|
||
|
|
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||
|
|
|
||
|
|
const modules = {
|
||
|
|
accounts: await import('../../server/data/accounts.js'),
|
||
|
|
assets: await import('../../server/data/assets.js'),
|
||
|
|
'api-metrics': await import('../../server/data/api-metrics.js'),
|
||
|
|
connections: await import('../../server/data/connections.js'),
|
||
|
|
counts: await import('../../server/data/counts.js'),
|
||
|
|
dashboard: await import('../../server/data/dashboard.js'),
|
||
|
|
events: await import('../../server/data/events.js'),
|
||
|
|
'interest-credits': await import('../../server/data/interest-credits.js'),
|
||
|
|
'interest-schedules': await import('../../server/data/interest-schedules.js'),
|
||
|
|
labels: await import('../../server/data/labels.js'),
|
||
|
|
metals: await import('../../server/data/metals.js'),
|
||
|
|
notifications: await import('../../server/data/notifications.js'),
|
||
|
|
'price-history': await import('../../server/data/price-history.js'),
|
||
|
|
'price-source': await import('../../server/data/price-source.js'),
|
||
|
|
proposals: await import('../../server/data/proposals.js'),
|
||
|
|
providers: await import('../../server/data/providers.js'),
|
||
|
|
'scan-logs': await import('../../server/data/scan-logs.js'),
|
||
|
|
'tx-links': await import('../../server/data/tx-links.js'),
|
||
|
|
valuations: await import('../../server/data/valuations.js'),
|
||
|
|
'wallet-addresses': await import('../../server/data/wallet-addresses.js'),
|
||
|
|
'wallet-sources': await import('../../server/data/wallet-sources.js'),
|
||
|
|
'wallet-txs': await import('../../server/data/wallet-txs.js'),
|
||
|
|
};
|
||
|
|
|
||
|
|
const LOK = Object.freeze({
|
||
|
|
xpub: 'xpubLOKVOGEL66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666',
|
||
|
|
adres: 'bc1qlokvogel99999999999999999999999999zzzz',
|
||
|
|
txid: 'lokvogel4444444444444444444444444444444444444444444444444444beef',
|
||
|
|
extern: 'bc1qlokvogelextern9999999999999999999zzzzz',
|
||
|
|
notitie: 'LOKVOGELNOTITIE',
|
||
|
|
rekening: 'LOKVOGELREKENING',
|
||
|
|
});
|
||
|
|
const LOKWAARDEN = Object.values(LOK);
|
||
|
|
|
||
|
|
const MUTEERT = /^(create|update|delete|upsert|set|purge|merge|assert|emit|forget|take|sync|book|restore|import|save|apply|resolve|dismiss|link|unlink|reset|seed|ensure|add|remove|move|rename|record|log|mark|clear|prune|revert)/;
|
||
|
|
|
||
|
|
let ids = {};
|
||
|
|
let resultaten = [];
|
||
|
|
let overgeslagen = [];
|
||
|
|
|
||
|
|
before(async () => {
|
||
|
|
harness.recreate();
|
||
|
|
|
||
|
|
const asset = await modules.assets.createAsset(
|
||
|
|
{ symbol: 'BTC', name: 'Bitcoin', kind: 'crypto', unit: 'BTC', subunit: 'sat', decimals: 8, displayUnit: 'unit' });
|
||
|
|
const account = await modules.accounts.createAccount(
|
||
|
|
{ name: LOK.rekening, kind: 'wallet', isOwnCustody: 1, scanEnabled: 1 });
|
||
|
|
ids = { asset: idVan(asset), account: idVan(account) };
|
||
|
|
|
||
|
|
const bron = await modules['wallet-sources'].createWalletSource(
|
||
|
|
{ accountId: ids.account, kind: 'xpub', value: LOK.xpub, name: LOK.rekening, scriptType: 'p2wpkh' });
|
||
|
|
ids.bron = idVan(bron);
|
||
|
|
|
||
|
|
await modules['wallet-addresses'].upsertWalletAddresses([{
|
||
|
|
accountId: ids.account, sourceId: ids.bron, address: LOK.adres,
|
||
|
|
chainIndex: 0, derivationIndex: 3, balanceSats: 123456789, transactionCount: 2,
|
||
|
|
}]);
|
||
|
|
await modules['wallet-txs'].upsertWalletTxs([{
|
||
|
|
accountId: ids.account, sourceId: ids.bron, txid: LOK.txid,
|
||
|
|
direction: 'in', valueSats: 123456789, feeSats: 250, blockHeight: 800000,
|
||
|
|
blockTime: '2026-01-02T03:04:05Z', address: LOK.adres,
|
||
|
|
}]);
|
||
|
|
await modules.events.createEvent({
|
||
|
|
assetId: ids.asset, type: 'buy', occurredAt: '2026-01-02T03:04:05Z', amount: 123456789,
|
||
|
|
fiatPrice: 54321.5, toAccountId: ids.account, source: 'manual',
|
||
|
|
note: LOK.notitie, externalAddress: LOK.extern,
|
||
|
|
});
|
||
|
|
|
||
|
|
for (const [mod, exports] of Object.entries(modules)) {
|
||
|
|
for (const [naam, fn] of Object.entries(exports)) {
|
||
|
|
if (typeof fn !== 'function' || MUTEERT.test(naam)) { overgeslagen.push(`${mod}.${naam}`); continue; }
|
||
|
|
let ruw, gelukt = false;
|
||
|
|
for (const args of [[], [ids.account], [ids.bron], [ids.asset]]) {
|
||
|
|
try { ruw = await fn(...args); gelukt = true; break; } catch { }
|
||
|
|
}
|
||
|
|
if (gelukt && ruw !== undefined) resultaten.push({ naam: `${mod}.${naam}`, ruw });
|
||
|
|
else overgeslagen.push(`${mod}.${naam}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function idVan(r) { return typeof r === 'object' && r !== null ? (r.id ?? r) : r; }
|
||
|
|
|
||
|
|
function bevat(structuur, naald) {
|
||
|
|
return JSON.stringify(structuur ?? null).includes(naald);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('AI-sweep, richting 1: geen lokvogel overleeft de vertaalslag', () => {
|
||
|
|
it('roept genoeg leesfuncties aan om iets te bewijzen', () => {
|
||
|
|
assert.ok(resultaten.length >= 30,
|
||
|
|
`te weinig aangeroepen om iets te bewijzen: ${resultaten.length} gelukt, ${overgeslagen.length} overgeslagen`);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('ziet de lokvogels in de ONvertaalde uitvoer (anders bewijst de rest niets)', () => {
|
||
|
|
const gezien = LOKWAARDEN.filter(w => resultaten.some(r => bevat(r.ruw, w)));
|
||
|
|
assert.deepEqual(gezien.sort(), [...LOKWAARDEN].sort(),
|
||
|
|
'niet elke lokvogel kwam in de ruwe uitvoer voor; de sweep raakt die paden dus niet');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('laat geen enkele lokvogel door de vertaalslag', () => {
|
||
|
|
const t = createTranslator({ secret: TEST_GEHEIM });
|
||
|
|
const lekken = [];
|
||
|
|
for (const { naam, ruw } of resultaten) {
|
||
|
|
const uit = t.toAi(ruw);
|
||
|
|
for (const w of LOKWAARDEN) if (bevat(uit, w)) lekken.push(`${naam} lekt ${w.slice(0, 16)}…`);
|
||
|
|
}
|
||
|
|
assert.deepEqual(lekken, []);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('AI-sweep, richting 2: elk veld in de uitvoer is gedeclareerd', () => {
|
||
|
|
it('kent voor elk veld in de vertaalde uitvoer een klasse', () => {
|
||
|
|
const t = createTranslator({ secret: TEST_GEHEIM });
|
||
|
|
const onbekend = new Set();
|
||
|
|
const loop = (v) => {
|
||
|
|
if (Array.isArray(v)) { v.forEach(loop); return; }
|
||
|
|
if (v === null || typeof v !== 'object') return;
|
||
|
|
for (const [k, w] of Object.entries(v)) {
|
||
|
|
if (fieldClass(k) === null) onbekend.add(k);
|
||
|
|
loop(w);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
for (const { ruw } of resultaten) loop(t.toAi(ruw));
|
||
|
|
assert.deepEqual([...onbekend].sort(), []);
|
||
|
|
});
|
||
|
|
});
|