2026-08-28 11:12:41 +02:00
|
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
// Toetst het toegangsbeleid van de relay: tools/evolu-relay/src/policy.js
|
|
|
|
|
//
|
|
|
|
|
// Draaien: node tests/test_limiter.mjs
|
|
|
|
|
//
|
|
|
|
|
// Geen testrunner en geen afhankelijkheden, net als de Python-tests in deze map.
|
|
|
|
|
// Geeft 0 terug als alles goed is en 1 bij de eerste fout in de telling.
|
|
|
|
|
//
|
|
|
|
|
// Waarom dit bestand er is: dit beleid is de enige plek die bepaalt wie er op de
|
|
|
|
|
// relay mag schrijven. De duurste fout zit hier niet in een berekening maar in
|
|
|
|
|
// een guard, en dat is precies wat hieronder getoetst wordt: een geblokkeerde
|
|
|
|
|
// eigenaar mag niet alsnog binnenkomen doordat de leerstand aanstaat.
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
import {
|
2026-08-30 12:26:56 +02:00
|
|
|
MAX_LEARNING_SECONDS,
|
2026-08-28 11:12:41 +02:00
|
|
|
MAX_OWNER_ID_LENGTH,
|
|
|
|
|
MAX_REJECTED,
|
|
|
|
|
applyCommand,
|
|
|
|
|
createEmptyState,
|
|
|
|
|
decideOwner,
|
2026-08-30 12:26:56 +02:00
|
|
|
expireLearning,
|
|
|
|
|
isLearningOpen,
|
2026-08-28 11:12:41 +02:00
|
|
|
normalizeState,
|
|
|
|
|
} from '../tools/evolu-relay/src/policy.js';
|
|
|
|
|
|
|
|
|
|
let goed = 0;
|
|
|
|
|
let fout = 0;
|
|
|
|
|
|
|
|
|
|
const toets = (omschrijving, voorwaarde) => {
|
|
|
|
|
if (voorwaarde) {
|
|
|
|
|
goed += 1;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
fout += 1;
|
|
|
|
|
console.log(`FOUT: ${omschrijving}`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const NU = '2026-08-28T12:00:00.000Z';
|
|
|
|
|
const LATER = '2026-08-28T13:00:00.000Z';
|
2026-08-30 12:26:56 +02:00
|
|
|
// Binnen en buiten een venster van twee minuten dat op NU begint.
|
|
|
|
|
const BINNEN = '2026-08-28T12:01:00.000Z';
|
|
|
|
|
const NA = '2026-08-28T12:02:30.000Z';
|
2026-08-28 11:12:41 +02:00
|
|
|
const EIGEN = 'owner-van-de-gebruiker';
|
|
|
|
|
const VREEMD = 'owner-van-een-ander';
|
2026-08-30 12:26:56 +02:00
|
|
|
const TWEE_MINUTEN = 120;
|
2026-08-28 11:12:41 +02:00
|
|
|
|
|
|
|
|
// ── De leerstand ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const vers = createEmptyState();
|
|
|
|
|
toets('een verse staat staat in de leerstand', vers.learning === true);
|
|
|
|
|
|
|
|
|
|
const eerste = decideOwner(vers, EIGEN, NU);
|
|
|
|
|
toets('de eerste eigenaar wordt toegelaten', eerste.allowed === true);
|
|
|
|
|
toets('en onthouden als toegelaten', eerste.state.owners.length === 1);
|
|
|
|
|
toets('met de reden dat hij geleerd is', eerste.reason === 'learned');
|
|
|
|
|
|
|
|
|
|
const nogEens = decideOwner(eerste.state, EIGEN, LATER);
|
|
|
|
|
toets('dezelfde eigenaar blijft toegelaten', nogEens.allowed === true);
|
|
|
|
|
toets('en komt er niet dubbel in', nogEens.state.owners.length === 1);
|
|
|
|
|
toets('de reden is nu dat hij bekend is', nogEens.reason === 'known-allowed');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── De leerstand uit ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const geleerd = decideOwner(createEmptyState(), EIGEN, NU).state;
|
|
|
|
|
const dicht = applyCommand(geleerd, { action: 'set-learning', value: false }, NU).state;
|
|
|
|
|
|
|
|
|
|
const eigen = decideOwner(dicht, EIGEN, LATER);
|
|
|
|
|
toets('met de leerstand uit mag de bekende eigenaar er nog steeds in', eigen.allowed === true);
|
|
|
|
|
|
|
|
|
|
const vreemd = decideOwner(dicht, VREEMD, LATER);
|
|
|
|
|
toets('maar een onbekende wordt geweigerd', vreemd.allowed === false);
|
|
|
|
|
toets('de weigering wordt onthouden voor de pagina', vreemd.state.rejected.length === 1);
|
|
|
|
|
toets('en hij belandt niet in de allowlist', vreemd.state.owners.length === 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── De guard die er het meest toe doet ────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const geleerd = decideOwner(createEmptyState(), EIGEN, NU).state;
|
|
|
|
|
const geblokkeerd = applyCommand(geleerd, { action: 'block', ownerId: EIGEN }, NU).state;
|
|
|
|
|
|
|
|
|
|
toets('de leerstand staat hier nog aan', geblokkeerd.learning === true);
|
|
|
|
|
|
|
|
|
|
const opnieuw = decideOwner(geblokkeerd, EIGEN, LATER);
|
|
|
|
|
toets(
|
|
|
|
|
'een geblokkeerde eigenaar komt er niet alsnog in via de leerstand',
|
|
|
|
|
opnieuw.allowed === false,
|
|
|
|
|
);
|
|
|
|
|
toets('en de reden zegt dat hij geblokkeerd is', opnieuw.reason === 'known-blocked');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Onbruikbare invoer ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const vers = createEmptyState();
|
|
|
|
|
for (const rommel of ['', null, undefined, 42, {}, 'x'.repeat(MAX_OWNER_ID_LENGTH + 1)]) {
|
|
|
|
|
const uitkomst = decideOwner(vers, rommel, NU);
|
|
|
|
|
toets(`een onbruikbaar id (${typeof rommel}) wordt geweigerd`, uitkomst.allowed === false);
|
|
|
|
|
toets('en wordt niet onthouden', uitkomst.state.owners.length === 0);
|
|
|
|
|
toets('ook niet als geweigerde poging', uitkomst.state.rejected.length === 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── De lijst met weigeringen loopt niet vol ───────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let staat = { ...createEmptyState(), learning: false };
|
|
|
|
|
for (let i = 0; i < MAX_REJECTED + 5; i += 1) {
|
|
|
|
|
staat = decideOwner(staat, `indringer-${i}`, NU).state;
|
|
|
|
|
}
|
|
|
|
|
toets('de lijst met weigeringen wordt afgekapt', staat.rejected.length === MAX_REJECTED);
|
|
|
|
|
toets('en de nieuwste staat vooraan', staat.rejected[0].id === `indringer-${MAX_REJECTED + 4}`);
|
|
|
|
|
|
|
|
|
|
const herhaling = decideOwner(staat, `indringer-${MAX_REJECTED + 4}`, LATER);
|
|
|
|
|
toets('een herhaalde poging telt op', herhaling.state.rejected[0].attempts === 2);
|
|
|
|
|
toets('en voegt geen tweede regel toe', herhaling.state.rejected.length === MAX_REJECTED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── De opdrachten van de pagina ───────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const geleerd = decideOwner(createEmptyState(), EIGEN, NU).state;
|
|
|
|
|
|
|
|
|
|
const uit = applyCommand(geleerd, { action: 'set-learning', value: false }, NU);
|
|
|
|
|
toets('de leerstand gaat uit', uit.state.learning === false);
|
|
|
|
|
toets('en dat is een wijziging', uit.changed === true);
|
|
|
|
|
|
|
|
|
|
const nogEensUit = applyCommand(uit.state, { action: 'set-learning', value: false }, NU);
|
|
|
|
|
toets('nog een keer uitzetten verandert niets', nogEensUit.changed === false);
|
|
|
|
|
toets('en is geen fout', nogEensUit.error === null);
|
|
|
|
|
|
|
|
|
|
const vergeten = applyCommand(geleerd, { action: 'forget', ownerId: EIGEN }, NU);
|
|
|
|
|
toets('vergeten haalt de eigenaar uit de lijst', vergeten.state.owners.length === 0);
|
|
|
|
|
|
|
|
|
|
const onbekend = applyCommand(geleerd, { action: 'forget', ownerId: VREEMD }, NU);
|
|
|
|
|
toets('een onbekende vergeten is een fout', onbekend.error === 'unknown-owner');
|
|
|
|
|
toets('en verandert niets', onbekend.changed === false);
|
|
|
|
|
|
|
|
|
|
const blokkeerOnbekend = applyCommand(geleerd, { action: 'block', ownerId: VREEMD }, NU);
|
|
|
|
|
toets('een onbekende blokkeren is een fout', blokkeerOnbekend.error === 'unknown-owner');
|
|
|
|
|
|
|
|
|
|
const dicht = { ...createEmptyState(), learning: false };
|
|
|
|
|
const geweigerd = decideOwner(dicht, VREEMD, NU).state;
|
|
|
|
|
const alsnog = applyCommand(geweigerd, { action: 'allow', ownerId: VREEMD }, LATER);
|
|
|
|
|
toets('een geweigerde poging kan alsnog toegelaten worden', alsnog.state.owners.length === 1);
|
|
|
|
|
toets('en verdwijnt dan uit de weigeringen', alsnog.state.rejected.length === 0);
|
2026-08-28 12:08:19 +02:00
|
|
|
toets(
|
|
|
|
|
'en houdt het moment waarop hij zich meldde, niet dat van de klik',
|
|
|
|
|
alsnog.state.owners[0].firstSeen === NU,
|
|
|
|
|
);
|
2026-08-28 11:12:41 +02:00
|
|
|
toets(
|
|
|
|
|
'en mag er daarna in, ook met de leerstand uit',
|
|
|
|
|
decideOwner(alsnog.state, VREEMD, LATER).allowed === true,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
toets(
|
|
|
|
|
'een onbekende actie is een fout',
|
|
|
|
|
applyCommand(geleerd, { action: 'sudo' }, NU).error === 'unknown-action',
|
|
|
|
|
);
|
|
|
|
|
toets(
|
|
|
|
|
'een opdracht zonder vorm is een fout',
|
|
|
|
|
applyCommand(geleerd, 'zet alles open', NU).error === 'malformed-command',
|
|
|
|
|
);
|
|
|
|
|
toets(
|
|
|
|
|
'een leerstand-opdracht zonder boolean is een fout',
|
|
|
|
|
applyCommand(geleerd, { action: 'set-learning', value: 'ja' }, NU).error === 'malformed-command',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-30 12:26:56 +02:00
|
|
|
// ── Het tijdvenster voor nieuwe eigenaars ─────────────────────────────────────
|
|
|
|
|
//
|
|
|
|
|
// De pagina zet de deur twee minuten open in plaats van een schakelaar om te
|
|
|
|
|
// zetten die je moet onthouden. De teller hoort in dít bestand en niet in de
|
|
|
|
|
// browser: een teller in een tabblad dat je sluit, sluit de deur niet.
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const vers = createEmptyState();
|
|
|
|
|
toets('een verse staat heeft geen tijdslot', vers.learningUntil === null);
|
|
|
|
|
toets('en staat dus open zonder dat er een klok loopt', isLearningOpen(vers, LATER) === true);
|
|
|
|
|
|
|
|
|
|
const open = applyCommand(
|
|
|
|
|
vers,
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
NU,
|
|
|
|
|
);
|
|
|
|
|
toets('openzetten met seconden is een wijziging', open.changed === true);
|
|
|
|
|
toets('en zet een tijdslot', open.state.learningUntil === '2026-08-28T12:02:00.000Z');
|
|
|
|
|
toets('binnen het venster staat de deur open', isLearningOpen(open.state, BINNEN) === true);
|
|
|
|
|
toets('erna niet meer', isLearningOpen(open.state, NA) === false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// De guard die er het meest toe doet, en dit is de reden dat `decideOwner` naar
|
|
|
|
|
// `isLearningOpen` kijkt en niet naar het veld `learning`: tussen het aflopen van
|
|
|
|
|
// het venster en de ronde die het bestand opruimt zitten een paar seconden. In dat
|
|
|
|
|
// gat mag er niemand binnenkomen.
|
|
|
|
|
{
|
|
|
|
|
const open = applyCommand(
|
|
|
|
|
createEmptyState(),
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
NU,
|
|
|
|
|
).state;
|
|
|
|
|
|
|
|
|
|
toets('in het bestand staat de leerstand nog aan', open.learning === true);
|
|
|
|
|
|
|
|
|
|
const binnen = decideOwner(open, EIGEN, BINNEN);
|
|
|
|
|
toets('binnen het venster wordt een onbekende geleerd', binnen.allowed === true);
|
|
|
|
|
|
|
|
|
|
const erna = decideOwner(open, VREEMD, NA);
|
|
|
|
|
toets(
|
|
|
|
|
'na het venster wordt hij geweigerd, ook al staat learning nog op true',
|
|
|
|
|
erna.allowed === false,
|
|
|
|
|
);
|
|
|
|
|
toets('met de reden dat er niet geleerd wordt', erna.reason === 'not-learning');
|
|
|
|
|
toets('en de poging wordt onthouden voor de pagina', erna.state.rejected.length === 1);
|
|
|
|
|
|
|
|
|
|
// En wie al binnen was, blijft binnen. Het venster gaat over onbekenden.
|
|
|
|
|
const bekend = decideOwner(binnen.state, EIGEN, LATER);
|
|
|
|
|
toets('een geleerde eigenaar mag er na het venster nog steeds in', bekend.allowed === true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const open = applyCommand(
|
|
|
|
|
createEmptyState(),
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
NU,
|
|
|
|
|
).state;
|
|
|
|
|
|
|
|
|
|
// Opnieuw indrukken verlengt. Zonder deze uitzondering zou `applyCommand`
|
|
|
|
|
// 'changed: false' teruggeven omdat learning al true was, en dan liep het
|
|
|
|
|
// venster af terwijl de gebruiker net verlengde.
|
|
|
|
|
const opnieuw = applyCommand(
|
|
|
|
|
open,
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
BINNEN,
|
|
|
|
|
);
|
|
|
|
|
toets('opnieuw openzetten is een wijziging', opnieuw.changed === true);
|
|
|
|
|
toets('en zet de klok terug', opnieuw.state.learningUntil === '2026-08-28T12:03:00.000Z');
|
|
|
|
|
toets('dus is de deur na het oude venster nog open', isLearningOpen(opnieuw.state, NA) === true);
|
|
|
|
|
|
|
|
|
|
// Vroegtijdig sluiten haalt het tijdslot mee weg. Zou het blijven staan, dan
|
|
|
|
|
// erfde een volgende 'open zonder seconden' een venster dat niet gevraagd is.
|
|
|
|
|
const dicht = applyCommand(open, { action: 'set-learning', value: false }, BINNEN);
|
|
|
|
|
toets('vroegtijdig sluiten kan', dicht.state.learning === false);
|
|
|
|
|
toets('en haalt het tijdslot weg', dicht.state.learningUntil === null);
|
|
|
|
|
toets('de deur is dan dicht', isLearningOpen(dicht.state, BINNEN) === false);
|
|
|
|
|
|
|
|
|
|
const zonderSlot = applyCommand(dicht.state, { action: 'set-learning', value: true }, BINNEN);
|
|
|
|
|
toets('openzetten zonder seconden blijft bestaan', zonderSlot.state.learning === true);
|
|
|
|
|
toets('en heeft dan geen tijdslot', zonderSlot.state.learningUntil === null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wat de agent hoort tegen te houden, houdt dit bestand ook tegen. Twee zeven, en
|
|
|
|
|
// dat is met opzet: dit is de kant die van buiten komt.
|
|
|
|
|
{
|
|
|
|
|
const vers = createEmptyState();
|
|
|
|
|
const rommel = [
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: 0 },
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: -60 },
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: 1.5 },
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: '120' },
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: true },
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: MAX_LEARNING_SECONDS + 1 },
|
|
|
|
|
];
|
|
|
|
|
for (const opdracht of rommel) {
|
|
|
|
|
const uitkomst = applyCommand(vers, opdracht, NU);
|
|
|
|
|
toets(
|
|
|
|
|
`een onbruikbaar aantal seconden wordt geweigerd: ${JSON.stringify(opdracht.seconds)}`,
|
|
|
|
|
uitkomst.error === 'malformed-command',
|
|
|
|
|
);
|
|
|
|
|
toets('en verandert niets', uitkomst.changed === false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toets(
|
|
|
|
|
'de bovengrens zelf mag wel',
|
|
|
|
|
applyCommand(vers, { action: 'set-learning', value: true, seconds: MAX_LEARNING_SECONDS }, NU)
|
|
|
|
|
.error === null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Een onleesbaar `now` mag geen venster zonder einde opleveren. Dat is de reden
|
|
|
|
|
// dat `addSeconds` null teruggeeft in plaats van iets te verzinnen.
|
|
|
|
|
const kapotteKlok = applyCommand(
|
|
|
|
|
vers,
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
'geen tijdstip',
|
|
|
|
|
);
|
|
|
|
|
toets('met een onleesbare klok wordt de opdracht geweigerd', kapotteKlok.error === 'malformed-command');
|
|
|
|
|
toets('en blijft de staat zoals hij was', kapotteKlok.changed === false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Het opruimen. Correctheid hangt hier niet aan, want `isLearningOpen` weigert al;
|
|
|
|
|
// wat dit oplevert is dat het bestand en de pagina hetzelfde zeggen als de klok.
|
|
|
|
|
{
|
|
|
|
|
const open = applyCommand(
|
|
|
|
|
createEmptyState(),
|
|
|
|
|
{ action: 'set-learning', value: true, seconds: TWEE_MINUTEN },
|
|
|
|
|
NU,
|
|
|
|
|
).state;
|
|
|
|
|
|
|
|
|
|
const nogNiet = expireLearning(open, BINNEN);
|
|
|
|
|
toets('binnen het venster wordt er niets opgeruimd', nogNiet.changed === false);
|
|
|
|
|
toets('en de staat blijft dezelfde', nogNiet.state === open);
|
|
|
|
|
|
|
|
|
|
const opgeruimd = expireLearning(open, NA);
|
|
|
|
|
toets('na het venster gaat de leerstand uit', opgeruimd.state.learning === false);
|
|
|
|
|
toets('en het tijdslot weg', opgeruimd.state.learningUntil === null);
|
|
|
|
|
toets('en dat is een wijziging', opgeruimd.changed === true);
|
|
|
|
|
|
|
|
|
|
toets(
|
|
|
|
|
'nog een keer opruimen doet niets',
|
|
|
|
|
expireLearning(opgeruimd.state, LATER).changed === false,
|
|
|
|
|
);
|
|
|
|
|
toets(
|
|
|
|
|
'een leerstand zonder tijdslot wordt niet opgeruimd',
|
|
|
|
|
expireLearning(createEmptyState(), LATER).changed === false,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Een onleesbaar tijdstip in het bestand is dicht, niet open. Dezelfde regel als
|
|
|
|
|
// bij een onleesbare owners.json in store.js: bij twijfel weigert de app.
|
|
|
|
|
const kapot = { ...createEmptyState(), learningUntil: 'ergens volgende week' };
|
|
|
|
|
toets('een onleesbaar tijdslot leest als dicht', isLearningOpen(kapot, NU) === false);
|
|
|
|
|
toets('en een onleesbare klok ook', isLearningOpen(open, 'geen tijdstip') === false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-28 11:12:41 +02:00
|
|
|
// ── Lezen van schijf ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const goedeStaat = decideOwner(createEmptyState(), EIGEN, NU).state;
|
|
|
|
|
const rondgang = normalizeState(JSON.parse(JSON.stringify(goedeStaat)));
|
|
|
|
|
toets('een eigen staat overleeft de rondgang door JSON', rondgang !== null);
|
|
|
|
|
toets('met de eigenaar er nog in', rondgang.owners[0].id === EIGEN);
|
|
|
|
|
|
|
|
|
|
const rommel = [
|
|
|
|
|
null,
|
|
|
|
|
'tekst',
|
|
|
|
|
[],
|
|
|
|
|
{ version: 999, learning: true, owners: [], rejected: [] },
|
|
|
|
|
{ version: 1, learning: 'ja', owners: [], rejected: [] },
|
|
|
|
|
{ version: 1, learning: true, owners: {}, rejected: [] },
|
|
|
|
|
{ version: 1, learning: true, owners: [{ id: '', allowed: true }], rejected: [] },
|
|
|
|
|
{ version: 1, learning: true, owners: [{ id: EIGEN }], rejected: [] },
|
|
|
|
|
];
|
|
|
|
|
for (const invoer of rommel) {
|
|
|
|
|
toets(`onbegrepen staat wordt geweigerd: ${JSON.stringify(invoer)}`, normalizeState(invoer) === null);
|
|
|
|
|
}
|
2026-08-30 12:26:56 +02:00
|
|
|
|
|
|
|
|
// Het tijdslot is toegevoegd zonder STATE_VERSION te verhogen, en dat is de
|
|
|
|
|
// toets die dat verdedigt: een bestand van een draaiende installatie mag niet
|
|
|
|
|
// ineens onleesbaar worden. Zou dat gebeuren, dan schuift store.js de allowlist
|
|
|
|
|
// opzij en gaat de deur dicht voor eigenaars die er al in stonden.
|
|
|
|
|
const oud = { version: 1, learning: true, owners: [{ id: EIGEN, allowed: true }], rejected: [] };
|
|
|
|
|
const gelezen = normalizeState(oud);
|
|
|
|
|
toets('een bestand van vóór het tijdslot leest nog', gelezen !== null);
|
|
|
|
|
toets('met de leerstand die erin stond', gelezen.learning === true);
|
|
|
|
|
toets('en zonder tijdslot', gelezen.learningUntil === null);
|
|
|
|
|
toets('en de eigenaar er nog in', gelezen.owners[0].id === EIGEN);
|
|
|
|
|
|
|
|
|
|
const metSlot = normalizeState({ ...oud, learningUntil: '2026-08-28T12:02:00.000Z' });
|
|
|
|
|
toets('een geldig tijdslot blijft staan', metSlot.learningUntil === '2026-08-28T12:02:00.000Z');
|
|
|
|
|
|
|
|
|
|
for (const onzin of ['morgen', '', 42, {}, null]) {
|
|
|
|
|
toets(
|
|
|
|
|
`een onleesbaar tijdslot wordt null en geen weigering: ${JSON.stringify(onzin)}`,
|
|
|
|
|
normalizeState({ ...oud, learningUntil: onzin }) !== null
|
|
|
|
|
&& normalizeState({ ...oud, learningUntil: onzin }).learningUntil === null,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-08-28 11:12:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('');
|
|
|
|
|
console.log(`${goed} goed, ${fout} fout`);
|
|
|
|
|
process.exit(fout === 0 ? 0 : 1);
|