// Generated, do not edit. sha256:3566aeffe3b8fd67fb12a75b9b7a38c50a27b00abed96a2cec53436ed08cde28 import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { schijnwaarde, isSchijnwaarde, createAliasRegistry, ALIAS_SOORTEN } from '../../server/ai/aliases.js'; import { bucket, bucketLabel, shareBucket, shareLabel } from '../../server/ai/buckets.js'; import { createTranslator } from '../../server/ai/translate.js'; import { CONFIG } from '../../shared/config.js'; const TEST_GEHEIM = 'test-geheim-voor-de-suite-0123456789abcdef'; describe('schijnwaarden: de onmogelijke letter', () => { it('maakt een adres dat geen geldig bech32 kan zijn', () => { const a = schijnwaarde('address', 0); assert.ok(a.startsWith('bc1q'), a); assert.equal(a.length, 42); assert.ok(a.slice(4).includes('o'), `geen onmogelijke letter in ${a}`); }); it('maakt een xpub die geen geldig base58 kan zijn', () => { const x = schijnwaarde('xpub', 0); assert.ok(x.startsWith('xpub'), x); assert.equal(x.length, 111); assert.ok(x.slice(4).includes('0'), `geen onmogelijke letter in ${x}`); }); it('maakt een txid die geen geldige hex kan zijn', () => { const t = schijnwaarde('txid', 0); assert.equal(t.length, 64); assert.ok(t.includes('z'), `geen onmogelijke letter in ${t}`); }); it('herkent zijn eigen maaksel, ook uit een andere sessie', () => { for (const soort of ALIAS_SOORTEN) { assert.equal(isSchijnwaarde(schijnwaarde(soort, 7)), true, soort); } }); it('houdt een waarde die alleen uit het echte alfabet bestaat voor echt', () => { const echtVormig = `bc1q${'qpzry9x8gf2tvdw0s3jn54khce6mua7l'.repeat(2).slice(0, 38)}`; assert.equal(echtVormig.length, 42); assert.equal(isSchijnwaarde(echtVormig), false); }); it('geeft dezelfde invoer dezelfde schijnwaarde, en verschillende invoer een andere', () => { const r = createAliasRegistry({ secret: TEST_GEHEIM }); const een = r.alias('address', 'bc1qecht1'); assert.equal(r.alias('address', 'bc1qecht1'), een); assert.notEqual(r.alias('address', 'bc1qecht2'), een); assert.equal(r.resolve(een), 'bc1qecht1'); }); }); describe('handles: stabiel over sessies, want anders wijzen ze stil naar de verkeerde rij', () => { const versDeSessie = () => createTranslator({ secret: TEST_GEHEIM }); it('geeft dezelfde rij in twee losse sessies dezelfde handle', () => { const een = versDeSessie().toAi({ id: 42 }, { entity: 'account' }).id; const twee = versDeSessie().toAi({ id: 42 }, { entity: 'account' }).id; assert.equal(een, twee); }); it('geeft twee rijen een andere handle, ook als ze in omgekeerde volgorde langskomen', () => { const a = versDeSessie(); const b = versDeSessie(); const a7 = a.toAi({ id: 7 }, { entity: 'account' }).id; const a9 = a.toAi({ id: 9 }, { entity: 'account' }).id; const b9 = b.toAi({ id: 9 }, { entity: 'account' }).id; const b7 = b.toAi({ id: 7 }, { entity: 'account' }).id; assert.equal(a7, b7); assert.equal(a9, b9); assert.notEqual(a7, a9); }); it('geeft een ANDER geheim andere handles', () => { const anders = createTranslator({ secret: 'een-heel-ander-geheim-0123456789abcdef' }); assert.notEqual( versDeSessie().toAi({ id: 42 }, { entity: 'account' }).id, anders.toAi({ id: 42 }, { entity: 'account' }).id); }); it('geeft dezelfde waarde onder twee SOORTEN een andere handle', () => { const t = versDeSessie(); const rekening = t.toAi({ id: 3 }, { entity: 'account' }).id; const asset = t.toAi({ id: 3 }, { entity: 'asset' }).id; assert.notEqual(rekening.split(':')[1], asset.split(':')[1]); }); it('weigert te bestaan zonder geheim', () => { assert.throws(() => createAliasRegistry(), /secret/i); assert.throws(() => createAliasRegistry({ secret: 'kort' }), /secret/i); }); it('maakt ook een schijnwaarde stabiel over sessies', () => { const een = versDeSessie().toAi({ address: 'bc1qecht' }, { entity: 'walletAddress' }).address; const twee = versDeSessie().toAi({ address: 'bc1qecht' }, { entity: 'walletAddress' }).address; assert.equal(een, twee); assert.ok(isSchijnwaarde(een), een); }); }); describe('emmers', () => { it('legt 12.347 in "10000 tot 15000" op de grove ladder', () => { assert.equal(bucketLabel(12347, 'COARSE'), '10000 tot 15000'); }); it('is op elke schaal even nauwkeurig', () => { assert.equal(bucketLabel(123.47, 'COARSE'), '100 tot 150'); assert.equal(bucketLabel(1234700, 'COARSE'), '1000000 tot 1500000'); }); it('is fijner op de fijne ladder', () => { assert.equal(bucketLabel(12347, 'FINE'), '10000 tot 12500'); }); it('laat nul met rust, want nul is geen geheim', () => { assert.deepEqual(bucket(0), { from: 0, to: 0, exact: true }); assert.equal(bucketLabel(0), '0'); }); it('houdt het teken van een negatief bedrag', () => { const b = bucket(-12347, 'COARSE'); assert.ok(b.from < 0 && b.to < 0 && b.from < b.to, JSON.stringify(b)); }); it('valt niet om op een exacte macht van tien', () => { assert.equal(bucketLabel(10000, 'COARSE'), '10000 tot 15000'); assert.equal(bucketLabel(100000, 'COARSE'), '100000 tot 150000'); }); it('geeft niets terug voor iets dat geen getal is', () => { assert.equal(bucket(NaN), null); assert.equal(bucket('12347'), null); }); }); describe('emmers: aandelen', () => { it('legt een aandeel in een marge van vijf punt', () => { assert.equal(shareLabel(42.7), '40 tot 45%'); assert.equal(shareLabel(45), '45 tot 50%'); assert.equal(shareLabel(99.9), '95 tot 100%'); }); it('is fijner in de staart, want daar zijn de verschillen klein', () => { assert.equal(shareLabel(2), '2 tot 4%'); assert.equal(shareLabel(7.4), '6 tot 8%'); assert.equal(shareLabel(11), '10 tot 15%'); }); it('zegt van een piepklein aandeel dat het onder de ondergrens ligt', () => { assert.equal(shareLabel(0.4), 'minder dan 2%'); assert.equal(shareBucket(0.4).below, true); assert.equal(shareBucket(3).below, false); }); it('houdt 0% en 100% exact, want dat zijn feiten en geen marges', () => { assert.equal(shareLabel(0), '0%'); assert.equal(shareLabel(100), '100%'); }); it('geeft niets terug voor iets dat geen aandeel is', () => { assert.equal(shareBucket(NaN), null); assert.equal(shareBucket(-3), null); assert.equal(shareBucket('42'), null); }); it('heeft een ladder die oploopt en op 100 eindigt', () => { const g = CONFIG.AI.SHARE_BUCKETS; assert.equal(g[g.length - 1], 100); for (let i = 1; i < g.length; i++) assert.ok(g[i] > g[i - 1], `stap ${i} loopt niet op`); assert.ok(g[0] > 0, 'de ondergrens hoort boven nul te liggen'); }); }); describe('emmers: de bodem, want een relatieve ladder heeft een absolute grens', () => { const BODEM = 3000; it('geeft onder de bodem een bovengrens in plaats van een bereik', () => { assert.equal(bucketLabel(1200, 'COARSE'), `minder dan ${BODEM}`); assert.equal(bucketLabel(42, 'COARSE'), `minder dan ${BODEM}`); assert.equal(bucketLabel(2, 'COARSE'), `minder dan ${BODEM}`); }); it('laat de bodem los zodra er genoeg waarden in de emmer passen', () => { assert.equal(bucketLabel(BODEM, 'COARSE'), '3000 tot 5000'); assert.equal(bucketLabel(BODEM - 1, 'COARSE'), `minder dan ${BODEM}`); }); it('geldt ALLEEN voor gehele waarden', () => { assert.equal(bucketLabel(123.47, 'COARSE'), '100 tot 150'); assert.equal(bucketLabel(1.6, 'COARSE'), '1.5 tot 2'); }); it('geldt NIET voor de fijne ladder', () => { assert.equal(bucketLabel(1200, 'FINE'), '1000 tot 1250'); assert.equal(bucketLabel(25, 'FINE'), '25 tot 31.5'); }); it('houdt het teken van een negatieve waarde onder de bodem', () => { assert.equal(bucketLabel(-42, 'COARSE'), `meer dan -${BODEM}`); const b = bucket(-42, 'COARSE'); assert.ok(b.from < 0 && b.to === 0, JSON.stringify(b)); }); it('laat nul nog steeds met rust', () => { assert.equal(bucketLabel(0, 'COARSE'), '0'); }); it('schuift mee als de ladder fijner wordt', () => { const smalst = Math.min(...CONFIG.AI.BUCKET_LADDERS.COARSE.map((v, i, a) => ((i + 1 < a.length ? a[i + 1] : 10) - v) / v)); assert.equal(BODEM, CONFIG.AI.BUCKET_MIN_VALUES.COARSE / smalst); }); }); describe('het choke point, heen', () => { const rij = () => ({ id: 4, accountId: 9, address: 'bc1qechtadresvandegebruiker0000000000000', txid: 'a'.repeat(64), valueSats: 123456789, fiatPrice: 54321.5, direction: 'in', note: 'geheime notitie', blockHeight: 800000, geenBekendVeld: 'zou niet mee mogen', }); it('laat alleen gedeclareerde velden door', () => { const uit = createTranslator({ secret: TEST_GEHEIM }).toAi(rij(), { entity: 'walletTransaction' }); assert.equal(Object.hasOwn(uit, 'geenBekendVeld'), false); assert.equal(Object.hasOwn(uit, 'blockHeight'), false, 'keten-metadata zonder alias-vorm gaat niet mee'); }); it('vervangt keten-data door een schijnwaarde van dezelfde soort', () => { const uit = createTranslator({ secret: TEST_GEHEIM }).toAi(rij(), { entity: 'walletTransaction' }); assert.ok(uit.address.startsWith('bc1q'), uit.address); assert.notEqual(uit.address, rij().address); assert.equal(isSchijnwaarde(uit.address), true); assert.equal(uit.txid.length, 64); assert.equal(isSchijnwaarde(uit.txid), true); }); it('emmert hoeveelheden en laat koersen exact', () => { const uit = createTranslator({ secret: TEST_GEHEIM }).toAi(rij(), { entity: 'walletTransaction' }); assert.equal(uit.valueSats, '100000000 tot 150000000'); assert.equal(uit.fiatPrice, 54321.5); assert.equal(uit.direction, 'in'); }); it('vertelt welke velden er doorgekomen zijn, zonder de waarden', () => { const t = createTranslator({ secret: TEST_GEHEIM }); t.toAi(rij(), { entity: 'walletTransaction' }); const velden = t.fieldsShared(); assert.ok(velden.includes('valueSats')); assert.equal(velden.includes('geenBekendVeld'), false); assert.equal(velden.includes('blockHeight'), false); }); it('deelt Preference.value alleen mét de tabel-hint', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const zonder = t.toAi({ scope: 'app', key: 'currency', value: 'eur' }); assert.equal(Object.hasOwn(zonder, 'value'), false, 'zonder hint blijft `value` binnen'); const met = t.toAi({ scope: 'app', key: 'currency', value: 'eur' }, { table: 'Preference' }); assert.equal(met.value, 'eur'); assert.equal(met.scope, 'app'); }); it('houdt de xpub binnen, ook met een tabel-hint erbij', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const uit = t.toAi({ id: 3, value: 'xpub6CUGRUo...' }, { table: 'WalletSource' }); assert.equal(Object.hasOwn(uit, 'value'), false); assert.ok(!JSON.stringify(uit).includes('xpub')); }); it('schrijft de GEKWALIFICEERDE naam in het auditlog zodra de uitzondering is gebruikt', () => { const t = createTranslator({ secret: TEST_GEHEIM }); t.toAi({ scope: 'app', key: 'currency', value: 'eur' }, { table: 'Preference' }); const velden = t.fieldsShared(); assert.ok(velden.includes('Preference.value')); assert.equal(velden.includes('value'), false); assert.ok(velden.includes('scope')); assert.equal(velden.includes('Preference.scope'), false); }); it('erft de tabel niet naar een genest object', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const uit = t.toAi({ scope: 'app', source: { value: 'xpub6CUGRUo...' } }, { table: 'Preference' }); assert.equal(uit.source?.value, undefined); }); it('werkt op diepte, niet alleen op de bovenste laag', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const uit = t.toAi({ id: 1, accounts: [{ id: 2, address: 'bc1qdiep', note: 'diep' }] }); assert.equal(Object.hasOwn(uit, 'accounts'), false); }); }); describe('het choke point, terug', () => { it('vertaalt terug in lopende tekst, niet alleen in velden', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const uit = t.toAi({ id: 7, name: 'Bitvavo' }, { entity: 'account' }); const antwoord = `Ik zie dat ${uit.id} (${uit.name}) een tekort heeft.`; const { text } = t.fromAi(antwoord); assert.equal(text, 'Ik zie dat 7 (Bitvavo) een tekort heeft.'); }); it('kan geen handle verwarren met een andere, want ze zijn allemaal even lang', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const tokens = []; for (const naam of ['EEN', 'TWEE', 'DRIE', 'VIER', 'VIJF', 'ZES', 'ZEVEN', 'ACHT', 'NEGEN', 'TIEN']) { tokens.push(t.toAi({ id: naam }, { entity: 'account' }).id); } const lengtes = new Set(tokens.map(x => x.length)); assert.equal(lengtes.size, 1, `handles zijn niet even lang: ${[...lengtes].join(', ')}`); assert.equal(new Set(tokens).size, tokens.length, 'twee entiteiten kregen dezelfde handle'); for (const a of tokens) { for (const b of tokens) { if (a !== b) assert.equal(b.startsWith(a), false, `${a} is een prefix van ${b}`); } } const { text } = t.fromAi(`${tokens[9]} en ${tokens[0]}`); assert.equal(text, 'TIEN en EEN'); }); it('laat een schijnwaarde die hij niet kent STAAN, en meldt hem', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const vreemd = schijnwaarde('address', 999); const { text, unresolved } = t.fromAi(`Kijk naar ${vreemd} eens.`); assert.ok(text.includes(vreemd), 'de onbekende schijnwaarde hoort te blijven staan'); assert.deepEqual(unresolved, [vreemd]); }); it('meldt niets bij een tekst zonder schijnwaarden', () => { const t = createTranslator({ secret: TEST_GEHEIM }); const { text, unresolved } = t.fromAi('Er is niets bijzonders gevonden.'); assert.equal(text, 'Er is niets bijzonders gevonden.'); assert.deepEqual(unresolved, []); }); }); describe('onbewerkt delen: de vertaalslag uit (ai.allow_raw)', () => { const RIJ = Object.freeze({ id: 7, name: 'Spaarrekening bij de bank', amount: 12345.678, fiatFee: 4321, fiatPrice: 98765, address: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', occurredAt: '2026-01-02T03:04:05Z', }); const vertaald = () => createTranslator({ secret: TEST_GEHEIM }); const onbewerkt = () => createTranslator({ secret: TEST_GEHEIM, precision: { rawValues: true } }); it('geeft bedragen als getal in plaats van als emmer', () => { const normaal = vertaald().toAi(RIJ, { entity: 'account' }); const ruw = onbewerkt().toAi(RIJ, { entity: 'account' }); assert.equal(typeof normaal.amount, 'string', 'voorwaarde: normaal is het een emmer-label'); assert.equal(typeof normaal.fiatFee, 'string'); assert.equal(ruw.amount, 12345.678); assert.equal(ruw.fiatFee, 4321); assert.equal(ruw.fiatPrice, 98765, 'een koers ging altijd al ongefilterd mee'); }); it('geeft namen en id\'s zoals ze zijn, in plaats van als handle', () => { const normaal = vertaald().toAi(RIJ, { entity: 'account' }); const ruw = onbewerkt().toAi(RIJ, { entity: 'account' }); assert.match(normaal.name, /^text:[0-9a-f]{6}$/, 'voorwaarde: normaal is het een handle'); assert.equal(ruw.name, 'Spaarrekening bij de bank'); assert.equal(ruw.id, 7); }); it('geeft een adres echt mee in plaats van als schijnwaarde', () => { const ruw = onbewerkt().toAi(RIJ, { entity: 'account' }); assert.equal(ruw.address, RIJ.address); assert.equal(isSchijnwaarde(ruw.address), false, 'er hoort geen onmogelijke letter in te zitten'); }); it('HOUDT DE ALLOWLIST OVEREIND: een ongedeclareerd veld gaat ook onbewerkt niet mee', () => { const ruw = onbewerkt().toAi({ ...RIJ, ietsOnbekends: 'geheim' }, { entity: 'account' }); assert.equal('ietsOnbekends' in ruw, false, 'de allowlist is een ander mechanisme en blijft gelden'); assert.equal(ruw.name, 'Spaarrekening bij de bank', 'voorwaarde: de rest gaat wél mee'); }); it('blijft melden wat er gedeeld is, want het auditlog moet ook hier kloppen', () => { const t = onbewerkt(); t.beginCall(); t.toAi(RIJ, { entity: 'account' }); const gedeeld = t.fieldsShared(); assert.ok(gedeeld.includes('amount'), 'een gedeeld bedrag hoort in het logboek te staan'); assert.ok(gedeeld.includes('name')); assert.ok(gedeeld.includes('address')); }); it('laat vrije tekst met rust bij het terugvertalen, want er is niets vervangen', () => { const t = onbewerkt(); t.toAi(RIJ, { entity: 'account' }); const uit = t.fromAi('Het saldo van Spaarrekening bij de bank is 12345.678.'); assert.equal(uit.text, 'Het saldo van Spaarrekening bij de bank is 12345.678.'); assert.deepEqual(uit.unresolved, []); }); });