// Generated, do not edit. sha256:8974817703a099c61e2ed0be93a4edca07beef7a6be37fd3761e3616029a859f import { describe, it, beforeEach, mock } from 'node:test'; import assert from 'node:assert/strict'; import { createDbHarness } from '../helpers/db-node.mjs'; import { CONFIG, ENUMS } from '../../shared/config.js'; const harness = createDbHarness(); mock.module('../../server/db.js', { namedExports: harness.exports }); const { logAiToolCall, listAiToolCalls, countAiToolCalls, lastAiToolCallAt, aiSessions, aiToolCallMessages, countAiToolCallsSince } = await import('../../server/data/ai-audit.js'); const { takeChanges } = await import('../../server/data/_events.js'); const regel = (extra = {}) => ({ sessionId: 'sessie-1', client: 'voorbeeld-client', tool: 'connection_test', outcome: 'ok', ...extra, }); beforeEach(() => { harness.recreate(); takeChanges(); }); describe('ai-audit: vastleggen', () => { it('legt een gelukte aanroep vast met alles wat er nodig is om hem te verantwoorden', async () => { const id = await logAiToolCall(regel({ arguments: { note: 'hallo' }, fieldsShared: [], charactersOut: 42, })); assert.ok(id > 0, 'geeft het id van de nieuwe regel terug'); const [r] = listAiToolCalls(); assert.equal(r.id, id); assert.equal(r.sessionId, 'sessie-1'); assert.equal(r.client, 'voorbeeld-client'); assert.equal(r.tool, 'connection_test'); assert.equal(r.outcome, 'ok'); assert.equal(r.charactersOut, 42); assert.deepEqual(r.fieldsShared, [], 'een lege lijst betekent: er ging geen enkel veld mee'); assert.equal(JSON.parse(r.arguments).note, 'hallo'); assert.ok(r.occurredAt, 'en er staat een moment bij'); }); it('persisteert', async () => { const voor = harness.calls.persist; await logAiToolCall(regel()); assert.equal(harness.calls.persist, voor + 1, 'een auditregel die niet op schijf komt, bestaat niet'); }); it('houdt het verschil tussen "niets gedeeld" en "onbekend wat er gedeeld is"', async () => { await logAiToolCall(regel({ fieldsShared: [] })); await logAiToolCall(regel({ sessionId: 'sessie-2' })); const rijen = listAiToolCalls(); const leeg = rijen.find(r => r.sessionId === 'sessie-1'); const onbekend = rijen.find(r => r.sessionId === 'sessie-2'); assert.deepEqual(leeg.fieldsShared, []); assert.equal(onbekend.fieldsShared, null); }); it('bewaart de veldnamen en niet hun waarden', async () => { await logAiToolCall(regel({ fieldsShared: ['name', 'symbol'] })); const [r] = listAiToolCalls(); assert.deepEqual(r.fieldsShared, ['name', 'symbol']); }); }); describe('ai-audit: welke serverversie een aanroep afhandelde', () => { it('legt de versie vast als de aanroeper hem meegeeft', async () => { await logAiToolCall(regel({ serverVersion: '2' })); assert.equal(listAiToolCalls()[0].serverVersion, '2'); }); it('laat hem LEEG als de aanroeper hem niet kent, en vult niets in', async () => { await logAiToolCall(regel()); assert.equal(listAiToolCalls()[0].serverVersion, null); }); }); describe('ai-audit: de actieve koppelingen', () => { it('groepeert per sessie, met aantal en laatste moment', async () => { await logAiToolCall(regel({ sessionId: 's1', serverVersion: '2', occurredAt: '2026-08-15T10:00:00.000Z' })); await logAiToolCall(regel({ sessionId: 's1', serverVersion: '2', occurredAt: '2026-08-15T10:05:00.000Z' })); await logAiToolCall(regel({ sessionId: 's2', serverVersion: '2', occurredAt: '2026-08-15T09:00:00.000Z' })); const s = aiSessions(24 * 365 * 100); assert.equal(s.length, 2); assert.equal(s[0].sessionId, 's1', 'nieuwste eerst'); assert.equal(s[0].calls, 2); assert.equal(s[0].lastAt, '2026-08-15T10:05:00.000Z'); }); it('markeert een sessie zonder serverversie, want die kent de schakelaar niet', async () => { await logAiToolCall(regel({ sessionId: 'oud', occurredAt: '2026-08-15T10:00:00.000Z' })); await logAiToolCall(regel({ sessionId: 'nieuw', serverVersion: '2', occurredAt: '2026-08-15T10:01:00.000Z' })); const s = aiSessions(24 * 365 * 100); assert.equal(s.find(x => x.sessionId === 'oud').zonderVersie, true); assert.equal(s.find(x => x.sessionId === 'nieuw').zonderVersie, false); }); it('laat een sessie buiten het venster weg', async () => { await logAiToolCall(regel({ sessionId: 'oud', occurredAt: '2020-01-01T00:00:00.000Z' })); assert.deepEqual(aiSessions(24), []); }); }); describe('ai-audit: de guards', () => { it('weigert een onbekende uitkomst', async () => { await assert.rejects(() => logAiToolCall(regel({ outcome: 'geslaagd' })), /Invalid outcome/); assert.equal(listAiToolCalls().length, 0, 'en er staat niets in de tabel'); }); it('weigert een regel zonder sessie', async () => { await assert.rejects(() => logAiToolCall(regel({ sessionId: null })), /without a session/); assert.equal(listAiToolCalls().length, 0); }); it('weigert een regel zonder toolnaam', async () => { await assert.rejects(() => logAiToolCall(regel({ tool: '' })), /without a tool name/); assert.equal(listAiToolCalls().length, 0); }); it('weigert een negatief aantal verstuurde tekens', async () => { await assert.rejects(() => logAiToolCall(regel({ charactersOut: -1 }))); assert.equal(listAiToolCalls().length, 0); }); it('legt élke uitkomst uit de registry vast, ook de geweigerde', async () => { for (const uitkomst of ENUMS.AI_CALL_OUTCOME) { await logAiToolCall(regel({ outcome: uitkomst, detail: 'reden' })); } assert.equal(listAiToolCalls().length, ENUMS.AI_CALL_OUTCOME.length); }); }); describe('ai-audit: teruglezen', () => { it('geeft de nieuwste eerst', async () => { const t = '2026-08-14T12:00:00.000Z'; const oudste = await logAiToolCall(regel({ occurredAt: '2026-08-14T11:00:00.000Z' })); const midden = await logAiToolCall(regel({ occurredAt: t })); const nieuwste = await logAiToolCall(regel({ occurredAt: '2026-08-14T13:00:00.000Z' })); assert.deepEqual(listAiToolCalls().map(r => r.id), [nieuwste, midden, oudste]); }); it('klemt de limiet en valt terug op de SSOT-waarde', async () => { for (let i = 0; i < 5; i++) await logAiToolCall(regel()); assert.equal(listAiToolCalls(3).length, 3); assert.equal(listAiToolCalls(0).length, 5, '0 is geen geldige limiet, dus de standaard geldt'); assert.equal(listAiToolCalls('onzin').length, 5); assert.ok(CONFIG.AI.AUDIT_PAGE_SIZE >= 5, 'anders meet deze test iets anders dan hij bedoelt'); }); it('overleeft onleesbare inhoud in fieldsShared', async () => { harness.exports.run( `INSERT INTO AiToolCall (sessionId, tool, fieldsShared, outcome, occurredAt) VALUES ('s','t','{geen json','ok','2026-08-14T12:00:00.000Z')`); const [r] = listAiToolCalls(); assert.deepEqual(r.fieldsShared, [], 'geen exception, maar een lege lijst'); }); it('telt per uitkomst, en noemt elke soort ook als hij nul is', async () => { await logAiToolCall(regel({ outcome: 'ok' })); await logAiToolCall(regel({ outcome: 'refused' })); const telling = countAiToolCalls(); assert.equal(telling.total, 2); assert.equal(telling.byOutcome.ok, 1); assert.equal(telling.byOutcome.refused, 1); assert.equal(telling.byOutcome.error, 0, 'een soort die niet voorkomt is 0 en niet afwezig'); }); it('geeft op een leeg logboek nul en geen null', () => { assert.deepEqual(countAiToolCalls(), { total: 0, byOutcome: { ok: 0, error: 0, refused: 0 } }); assert.equal(lastAiToolCallAt(), null); }); it('noemt het moment van de laatste aanroep', async () => { await logAiToolCall(regel({ occurredAt: '2026-08-14T12:00:00.000Z' })); await logAiToolCall(regel({ occurredAt: '2026-08-14T13:00:00.000Z' })); assert.equal(lastAiToolCallAt(), '2026-08-14T13:00:00.000Z'); }); }); const gesprek = (extra = {}) => ({ kind: 'request', text: '{"note":"hallo"}', ...extra }); describe('ai-audit: het gesprek vastleggen', () => { it('legt de momenten vast bij de aanroep waar ze bij horen', async () => { const id = await logAiToolCall(regel({ messages: [ gesprek({ occurredAt: '2026-08-17T10:00:00.000Z' }), gesprek({ kind: 'response', text: 'Verbinding werkt.', occurredAt: '2026-08-17T10:00:01.000Z' }), ], })); const m = aiToolCallMessages(id); assert.equal(m.length, 2); assert.deepEqual(m.map(x => x.kind), ['request', 'response'], 'op volgorde van gebeuren'); assert.equal(m[1].text, 'Verbinding werkt.'); assert.equal(m[0].callId, id); assert.equal(m[0].occurredAt, '2026-08-17T10:00:00.000Z'); }); it('houdt de volgorde ook als beide momenten in dezelfde milliseconde vallen', async () => { const t = '2026-08-17T10:00:00.000Z'; const id = await logAiToolCall(regel({ messages: [gesprek({ occurredAt: t }), gesprek({ kind: 'response', text: 'x', occurredAt: t })], })); assert.deepEqual(aiToolCallMessages(id).map(x => x.kind), ['request', 'response']); }); it('laat een aanroep zonder gesprek gewoon bestaan', async () => { const id = await logAiToolCall(regel()); assert.deepEqual(aiToolCallMessages(id), []); }); it('weigert een verzonnen moment, en laat dan ook de auditregel niet half achter', async () => { await assert.rejects( () => logAiToolCall(regel({ messages: [gesprek({ kind: 'antwoord' })] })), /Invalid kind/); assert.equal(listAiToolCalls().length, 0, 'geen halve aanroep in het logboek'); }); it('kapt een te lange tekst af en zegt dat er afgekapt is', async () => { const grens = CONFIG.AI.LIMITS.MAX_LOG_CHARS; const id = await logAiToolCall(regel({ messages: [gesprek({ kind: 'response', text: 'x'.repeat(grens + 500) })], })); const [m] = aiToolCallMessages(id); assert.ok(m.text.length <= grens, `bewaard: ${m.text.length}, grens: ${grens}`); assert.match(m.text, /Afgekapt/, 'en dát staat er zichtbaar bij'); }); it('bewaart een leeg gesprek als leeg en niet als ontbrekend', async () => { const id = await logAiToolCall(regel({ messages: [gesprek({ text: '' })] })); assert.equal(aiToolCallMessages(id).length, 1); assert.equal(aiToolCallMessages(id)[0].text, ''); }); it('geeft een lege lijst bij een aanroep die niet bestaat', async () => { await logAiToolCall(regel({ messages: [gesprek()] })); assert.deepEqual(aiToolCallMessages(9999), [], 'geen exception op een onbekend id'); assert.deepEqual(aiToolCallMessages(null), []); assert.deepEqual(aiToolCallMessages('onzin'), []); }); it('houdt de gesprekken van twee aanroepen uit elkaar', async () => { const een = await logAiToolCall(regel({ messages: [gesprek({ text: 'eerste' })] })); const twee = await logAiToolCall(regel({ messages: [gesprek({ text: 'tweede' })] })); assert.deepEqual(aiToolCallMessages(een).map(m => m.text), ['eerste']); assert.deepEqual(aiToolCallMessages(twee).map(m => m.text), ['tweede']); }); }); describe('ai-audit: laten weten dát er iets gebeurd is', () => { it('zet een verandersignaal bij elke vastgelegde aanroep', async () => { await logAiToolCall(regel()); assert.deepEqual(takeChanges(), ['ai-activity-changed']); }); it('zet dat signaal óók bij een geweigerde aanroep', async () => { await logAiToolCall(regel({ outcome: 'refused', detail: 'buiten scope' })); assert.deepEqual(takeChanges(), ['ai-activity-changed']); }); it('zet geen signaal als de regel geweigerd wordt', async () => { await assert.rejects(() => logAiToolCall(regel({ outcome: 'geslaagd' }))); assert.deepEqual(takeChanges(), []); }); it('telt hoeveel aanroepen er ná een gezien id zijn, mét het hoogste id van nu', async () => { const een = await logAiToolCall(regel()); const twee = await logAiToolCall(regel()); assert.deepEqual(countAiToolCallsSince(een), { count: 1, newestId: twee }); assert.deepEqual(countAiToolCallsSince(twee), { count: 0, newestId: twee }, 'alles gezien, maar het id komt wél mee'); }); it('rekent zonder gezien id alles als nieuw', async () => { await logAiToolCall(regel()); await logAiToolCall(regel()); assert.equal(countAiToolCallsSince(0).count, 2); assert.equal(countAiToolCallsSince(null).count, 2, 'nooit gekeken leest als 0'); assert.equal(countAiToolCallsSince('onzin').count, 2); }); it('geeft op een leeg logboek nul en geen null', async () => { assert.deepEqual(countAiToolCallsSince(0), { count: 0, newestId: 0 }); }); });