Files
2026-10-29 11:57:29 +01:00

161 lines
6.4 KiB
JavaScript

// 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`);
}
});
});