Files
AtreidesAssetsFree/tests/repo/ai-audit.test.mjs
T

280 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// 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 });
});
});