211 lines
7.8 KiB
JavaScript
211 lines
7.8 KiB
JavaScript
// 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' };
|
|
});
|
|
});
|