// Generated, do not edit. sha256:ba0d002b43c3a3cea3c4fcfb895e0825ada81fa58dfcdb0f545bd34613b051e6 import { describe, it, before, beforeEach, mock } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createDbHarness } from '../helpers/db-node.mjs'; const DATA_DIR = await mkdtemp(join(tmpdir(), 'assetapp-dryrun-')); process.env.ASSETAPP_DATA_DIR = DATA_DIR; const harness = createDbHarness({ seedPreferences: true }); mock.module('../../server/db.js', { namedExports: harness.exports }); const { createMcpServer } = await import('../../server/mcp/protocol.mjs'); const { TOOLS } = await import('../../server/mcp/tools.mjs'); const { dryRunTools, dryRunTool, _resetDryRunServer } = await import('../../server/mcp/dry-run.mjs'); const { listAiToolCalls, countAiToolCalls } = await import('../../server/data/ai-audit.js'); const { setPref } = await import('../../server/data/preferences.js'); const { createAsset } = await import('../../server/data/assets.js'); const { createAccount } = await import('../../server/data/accounts.js'); let logboek; const maak = () => { logboek = []; return createMcpServer({ tools: TOOLS, logCall: async (r) => { logboek.push(r); } }); }; const verzoek = (method, params, id = 1) => ({ jsonrpc: '2.0', id, method, params }); before(async () => { harness.recreate(); await createAsset({ symbol: 'BTC', name: 'Bitcoin', kind: 'crypto', unit: 'BTC', subunit: 'sat', decimals: 8, displayUnit: 'unit' }); await createAccount({ name: 'Testrekening', kind: 'wallet', isOwnCustody: 1, scanEnabled: 1 }); }); beforeEach(async () => { await setPref('ai', 'allow_read', 1); _resetDryRunServer(); }); describe('dry-run: hetzelfde codepad als een echte aanroep', () => { it('geeft dezelfde tekst als de echte aanroep', async () => { const server = maak(); const echt = await server.handle(verzoek('tools/call', { name: 'admin_overview' })); const droog = await server.dryRun('admin_overview'); assert.equal(droog.text, echt.result.content[0].text); }); it('geeft dezelfde auditregel als de echte aanroep zou schrijven', async () => { const server = maak(); await server.handle(verzoek('tools/call', { name: 'admin_overview' })); const droog = await server.dryRun('admin_overview'); assert.equal(droog.outcome, logboek[0].outcome); assert.deepEqual(droog.fieldsShared, logboek[0].fieldsShared); assert.equal(droog.charactersOut, logboek[0].charactersOut); }); it('noemt de velden die zouden vertrekken, en dat is de vraag die deze modus beantwoordt', async () => { const droog = await dryRunTool('admin_overview'); assert.ok(droog.fieldsShared.length > 0, 'een leeslijst zonder velden zegt niets'); assert.ok(droog.charactersOut > 0); }); }); describe('dry-run: er gaat niets de deur uit', () => { it('schrijft GEEN auditregel', async () => { const voor = countAiToolCalls().total; await dryRunTool('admin_overview'); await dryRunTool('data_health'); assert.equal(countAiToolCalls().total, voor); }); it('laat het logboek van een ECHTE aanroep ongemoeid ernaast', async () => { const server = maak(); await server.handle(verzoek('tools/call', { name: 'connection_test', arguments: { note: 'echt' } })); const na = listAiToolCalls().length; await dryRunTool('connection_test', { note: 'droog' }); assert.equal(listAiToolCalls().length, na); }); }); describe('dry-run: niet bereikbaar via het protocol', () => { it('kent geen JSON-RPC-methode die hem aanroept', async () => { const server = maak(); for (const method of ['dryRun', 'dry-run', 'tools/dryRun', 'tools/dry-run', 'tools/preview']) { const a = await server.handle(verzoek(method, { name: 'admin_overview' })); assert.equal(a.error?.code, -32601, `methode '${method}' werd afgehandeld`); } assert.equal(logboek.length, 0, 'een onbekende methode is geen tool-aanroep en hoort niets te loggen'); }); it('is ook niet als tool te bereiken', async () => { const server = maak(); const a = await server.handle(verzoek('tools/call', { name: 'dry_run' })); assert.ok(a.error, 'er hoort geen tool met deze naam te bestaan'); assert.equal(logboek[0].outcome, 'refused'); }); it('staat niet in de tool-lijst die een client krijgt', async () => { const server = maak(); const a = await server.handle(verzoek('tools/list', {})); for (const t of a.result.tools) assert.doesNotMatch(t.name, /dry/i); }); }); describe('dry-run: de poort geldt gewoon', () => { it('toont een weigering als het lezen uit staat, en dat is het eerlijke antwoord', async () => { await setPref('ai', 'allow_read', 0); try { const droog = await dryRunTool('admin_overview'); assert.equal(droog.outcome, 'refused'); assert.deepEqual(droog.fieldsShared, []); assert.match(droog.text, /niet lezen/i); } finally { await setPref('ai', 'allow_read', 1); } }); it('laat een tool zonder administratie-scope wél droog lopen met het lezen uit', async () => { await setPref('ai', 'allow_read', 0); try { const droog = await dryRunTool('connection_test'); assert.equal(droog.outcome, 'ok'); } finally { await setPref('ai', 'allow_read', 1); } }); }); describe('dry-run: de keuzelijst voor de gebruiker', () => { it('noemt elke tool met zijn scope, zodat zichtbaar is wat hij aanraakt', () => { const lijst = dryRunTools(); assert.equal(lijst.length, TOOLS.length); for (const t of lijst) { assert.ok(t.name && t.title && t.description, `${t.name} mist een veld`); assert.ok(t.scope, `${t.name} mist zijn scope`); } }); it('geeft geen `run` mee, want dat is geen gegeven maar gedrag', () => { for (const t of dryRunTools()) assert.equal(t.run, undefined); }); }); describe('dry-run: rare invoer', () => { it('meldt een onbekende tool netjes in plaats van om te vallen', async () => { const droog = await dryRunTool('bestaat_niet'); assert.equal(droog.outcome, 'refused'); assert.match(droog.text, /bestaat niet/i); }); it('verdraagt ontbrekende of onzinnige argumenten', async () => { for (const args of [undefined, null, 'tekst', 42, []]) { const droog = await dryRunTool('connection_test', args); assert.equal(droog.outcome, 'ok', `argumenten ${JSON.stringify(args)} lieten de dry-run omvallen`); } }); });