Files
AtreidesAssetsFree/tests/server/ai-gate.test.mjs
T

210 lines
7.8 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:f2303f8f896c78080d5d688ce3e2df97456f8a2894444e19e5c3457a31b7283d
import { describe, it, before, 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({ seedPreferences: true });
mock.module('../../server/db.js', { namedExports: harness.exports });
let licentieStand = { level: 'full', enforced: true, why: 'licensed' };
const echteLicentie = await import('../../server/license.mjs');
mock.module('../../server/license.mjs', {
namedExports: { ...echteLicentie, hasFeature: (naam) => echteLicentie.hasFeature(naam, licentieStand) },
});
const { assertScope, mayScope, isAiEnabled, isAiWriteAllowed, isAiControlAllowed, isAiActive,
AI_INACTIVE, AI_DISABLED, AI_WRITE_DENIED, AI_CONTROL_DENIED, AI_UNLICENSED, AI_REFUSALS } =
await import('../../server/ai/gate.js');
const { setPref } = await import('../../server/data/preferences.js');
const zet = async (enabled, allowWrite, allowControl = false) => {
await setPref('ai', 'allow_read', enabled ? 1 : 0);
await setPref('ai', 'allow_write', allowWrite ? 1 : 0);
await setPref('ai', 'allow_control', allowControl ? 1 : 0);
};
before(() => { harness.recreate(); });
beforeEach(async () => { await zet(true, false, false); });
describe('scope-poort: de schuif "Actief" gaat vóór alles', () => {
it('weigert ELKE scope, ook none, als de koppeling niet actief is', async () => {
await setPref('ai', 'enabled', 0);
try {
for (const scope of ['none', 'read', 'write', 'control']) {
assert.throws(() => assertScope(scope), e => e.code === AI_INACTIVE,
`scope ${scope} hoort geweigerd te worden met de koppeling uit`);
}
assert.equal(isAiActive(), false);
assert.equal(mayScope('none'), false);
} finally {
await setPref('ai', 'enabled', 1);
}
});
it('zegt iets ANDERS dan de leesschuif, want dat is een andere schuif', async () => {
await setPref('ai', 'enabled', 0);
let koppeling;
try { assertScope('read'); } catch (e) { koppeling = e; }
await setPref('ai', 'enabled', 1);
await setPref('ai', 'allow_read', 0);
let lezen;
try { assertScope('read'); } catch (e) { lezen = e; }
assert.notEqual(koppeling.code, lezen.code);
assert.match(koppeling.message, /Actief/);
assert.match(lezen.message, /niet lezen/i);
});
});
describe('scope-poort: de standen', () => {
it('laat scope none door zolang de koppeling actief is, ook met de drie rechten uit', async () => {
await zet(false, false);
assert.doesNotThrow(() => assertScope('none'));
});
it('laat scope read door als de koppeling aan staat', () => {
assert.doesNotThrow(() => assertScope('read'));
});
it('weigert scope read als de koppeling uit staat', async () => {
await zet(false, false);
assert.throws(() => assertScope('read'), e => e.code === AI_DISABLED);
});
it('weigert scope write standaard, ook al staat de koppeling aan', () => {
assert.throws(() => assertScope('write'), e => e.code === AI_WRITE_DENIED);
});
it('laat scope write door als het schrijfrecht aan staat', async () => {
await zet(true, true);
assert.doesNotThrow(() => assertScope('write'));
});
it('laat scope write door terwijl LEZEN uit staat, want elke schuif gaat over zichzelf', async () => {
await zet(false, true);
assert.doesNotThrow(() => assertScope('write'));
assert.throws(() => assertScope('read'), e => e.code === AI_DISABLED);
});
it('weigert scope control standaard, ook al staat de koppeling aan', () => {
assert.throws(() => assertScope('control'), e => e.code === AI_CONTROL_DENIED);
});
it('laat scope control door als het besturen aan staat', async () => {
await zet(true, false, true);
assert.doesNotThrow(() => assertScope('control'));
assert.equal(isAiControlAllowed(), true);
});
it('laat scope control door terwijl lezen uit staat, om dezelfde reden als bij schrijven', async () => {
await zet(false, false, true);
assert.doesNotThrow(() => assertScope('control'));
assert.throws(() => assertScope('read'), e => e.code === AI_DISABLED);
});
it('houdt besturen en schrijven uit elkaar', () => {
assert.equal(isAiWriteAllowed(), false);
assert.equal(isAiControlAllowed(), false);
});
it('weigert een onbekende scope', () => {
for (const raar of ['lezen', 'READ', '', null, undefined, 'admin']) {
assert.throws(() => assertScope(raar), e => AI_REFUSALS.includes(e.code),
`scope ${JSON.stringify(raar)} werd doorgelaten`);
}
});
});
describe('scope-poort: de melding die het model leest', () => {
it('zegt bij een geweigerde schrijfactie iets anders dan bij een uitgezette koppeling', async () => {
let schrijf;
try { assertScope('write'); } catch (e) { schrijf = e; }
await zet(false, false);
let lees;
try { assertScope('read'); } catch (e) { lees = e; }
assert.notEqual(schrijf.code, lees.code);
assert.notEqual(schrijf.message, lees.message);
assert.match(schrijf.message, /niets gewijzigd/i);
});
it('telt elke weigering als "bewust niet uitgevoerd"', () => {
assert.deepEqual([...AI_REFUSALS].sort(),
[AI_UNLICENSED, AI_INACTIVE, AI_DISABLED, AI_WRITE_DENIED, AI_CONTROL_DENIED].sort());
});
});
describe('scope-poort: bij twijfel dicht', () => {
it('zegt nee als de voorkeur onleesbaar is', async () => {
const origineel = harness.exports.all;
harness.exports.all = () => { throw new Error('database weg'); };
try {
assert.equal(isAiEnabled(), false);
assert.equal(isAiWriteAllowed(), false);
} finally {
harness.exports.all = origineel;
}
});
});
describe('scope-poort: de SSOT', () => {
it('kent precies de scopes uit de enum', () => {
for (const scope of ENUMS.AI_SCOPE) {
assert.doesNotThrow(() => {
try { assertScope(scope); } catch (e) {
if (!AI_REFUSALS.includes(e.code)) throw e;
}
}, `${scope} wordt niet herkend`);
}
});
});
describe('scope-poort: mayScope stelt dezelfde vraag zonder te gooien', () => {
const standen = [];
for (const enabled of [true, false]) {
for (const write of [true, false]) {
for (const control of [true, false]) standen.push([enabled, write, control]);
}
}
it('geeft voor elke scope in elke stand hetzelfde oordeel als assertScope', async () => {
for (const [enabled, write, control] of standen) {
await zet(enabled, write, control);
for (const scope of ENUMS.AI_SCOPE) {
let gooide = false;
try { assertScope(scope); } catch { gooide = true; }
assert.equal(mayScope(scope), !gooide,
`scope '${scope}' bij enabled=${enabled} write=${write} control=${control}`);
}
}
});
it('weigert een onbekende scope, net als assertScope', () => {
assert.equal(mayScope('admin'), false);
assert.equal(mayScope(''), false);
assert.equal(mayScope(undefined), false);
});
it('laat none door, ook met alles uit', async () => {
await zet(false, false, false);
assert.equal(mayScope('none'), true);
});
it('zegt nee tegen read zodra de koppeling uit staat', async () => {
await zet(false, false, false);
assert.equal(mayScope('read'), false);
});
});
describe('scope-poort: de licentie gaat vóór de schuiven', () => {
it('laat de koppeling toe in de gratis stand, want AI hoort daar sinds 27-10-2026 bij', () => {
assert.equal(CONFIG.LICENSE.FREE.HAS.ai, true);
licentieStand = { level: 'free', enforced: true, why: 'none' };
for (const scope of ['none', 'read']) {
assert.doesNotThrow(() => assertScope(scope), `scope ${scope} hoort door de licentie te komen`);
}
licentieStand = { level: 'full', enforced: true, why: 'licensed' };
});
});