| [792af53] | 1 | // Het gate-paneel per ward (shaer-ahy.1).
|
|---|
| 2 | //
|
|---|
| 3 | // Wat gated wordt is een ontwerpkeuze van de implementatie -- de FEP levert het
|
|---|
| 4 | // mechanisme en een paar voorbeelden, niet de lijst. GATE_CATALOGUE is die lijst
|
|---|
| 5 | // op een plek, en gateRows maakt er de rijen van die het paneel toont.
|
|---|
| 6 | //
|
|---|
| 7 | // De regels die hier bewaakt worden zijn geen opmaak maar betekenis: een gate
|
|---|
| 8 | // die je niet kunt verzetten hoort tOch zichtbaar te zijn, onbekend is niet uit,
|
|---|
| 9 | // en er wordt geen drempel verzonnen die we niet kennen.
|
|---|
| 10 | //
|
|---|
| 11 | // Run: npm test
|
|---|
| 12 |
|
|---|
| 13 | import { test } from 'node:test';
|
|---|
| 14 | import assert from 'node:assert/strict';
|
|---|
| 15 |
|
|---|
| 16 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 17 | const gated = await import('../src/services/guardianship/gated.js');
|
|---|
| 18 |
|
|---|
| 19 | const rij = (rows, f) => rows.find((r) => r.feature === f);
|
|---|
| 20 |
|
|---|
| 21 | test('elke gate uit de catalogus krijgt een rij, ook de vaste', () => {
|
|---|
| 22 | // Een paneel dat alleen verstelbare dingen toont verzwijgt de helft van wat er
|
|---|
| 23 | // voor dit kind geldt.
|
|---|
| 24 | const rows = gated.gateRows({});
|
|---|
| 25 | assert.equal(rows.length, gated.GATE_CATALOGUE.length);
|
|---|
| 26 | assert.ok(rij(rows, 'shaer:follows'), 'volgverzoeken zijn altijd gated en horen er tOch te staan');
|
|---|
| 27 | assert.equal(rij(rows, 'shaer:follows').adjustable, false, 'maar niet te verzetten');
|
|---|
| 28 | });
|
|---|
| 29 |
|
|---|
| 30 | test('het soort staat erbij, want ze werken niet hetzelfde', () => {
|
|---|
| 31 | const rows = gated.gateRows({});
|
|---|
| 32 | assert.equal(rij(rows, 'shaer:externalEmbeds').kind, 'setting');
|
|---|
| 33 | assert.equal(rij(rows, 'shaer:follows').kind, 'perRequest');
|
|---|
| 34 | });
|
|---|
| 35 |
|
|---|
| 36 | test('de drempel volgt het aantal guardians', () => {
|
|---|
| 37 | const drie = gated.gateRows({ guardianCount: 3 });
|
|---|
| 38 | assert.deepEqual(rij(drie, 'shaer:externalEmbeds').threshold, { need: 2, of: 3 });
|
|---|
| 39 | const een = gated.gateRows({ guardianCount: 1 });
|
|---|
| 40 | assert.deepEqual(rij(een, 'shaer:externalEmbeds').threshold, { need: 1, of: 1 });
|
|---|
| 41 | });
|
|---|
| 42 |
|
|---|
| 43 | test('een ONBEKEND aantal guardians levert GEEN drempel op', () => {
|
|---|
| 44 | // Bij een ward elders wordt die set op diens eigen server bijgehouden. Een
|
|---|
| 45 | // verzonnen getal leest als een feit, en dit is precies waar een guardian op
|
|---|
| 46 | // afgaat voordat hij iets voorstelt.
|
|---|
| 47 | const rows = gated.gateRows({ guardianCount: null });
|
|---|
| 48 | assert.equal(rij(rows, 'shaer:externalEmbeds').threshold, null);
|
|---|
| 49 | });
|
|---|
| 50 |
|
|---|
| 51 | test('afspelen is dicht zolang zien UIT staat', () => {
|
|---|
| 52 | const rows = gated.gateRows({ settings: { 'shaer:externalEmbeds': false } });
|
|---|
| 53 | const p = rij(rows, 'shaer:externalPlayback');
|
|---|
| 54 | assert.equal(p.adjustable, false);
|
|---|
| 55 | assert.equal(p.blockedBy, 'shaer:externalEmbeds');
|
|---|
| 56 | });
|
|---|
| 57 |
|
|---|
| 58 | test('maar ONBEKEND is niet hetzelfde als uit', () => {
|
|---|
| 59 | // Verbergen bij onbekend betekende ooit dat een guardian elders afspelen nooit
|
|---|
| 60 | // kon voorstellen -- daar ging een hele voorstelronde de verkeerde gate in.
|
|---|
| 61 | const rows = gated.gateRows({ settings: { 'shaer:externalEmbeds': null } });
|
|---|
| 62 | const p = rij(rows, 'shaer:externalPlayback');
|
|---|
| 63 | assert.equal(p.adjustable, true);
|
|---|
| 64 | assert.equal(p.blockedBy, undefined);
|
|---|
| 65 | });
|
|---|
| 66 |
|
|---|
| 67 | test('een lopend voorstel hangt aan zijn eigen gate', () => {
|
|---|
| 68 | const rows = gated.gateRows({
|
|---|
| 69 | proposals: [{ feature: 'shaer:externalPlayback', value: true, status: 'open' }],
|
|---|
| 70 | });
|
|---|
| 71 | assert.equal(rij(rows, 'shaer:externalPlayback').proposal.status, 'open');
|
|---|
| 72 | assert.equal(rij(rows, 'shaer:externalEmbeds').proposal, undefined);
|
|---|
| 73 | });
|
|---|
| 74 |
|
|---|
| 75 | test('wat er wacht staat bij de gate waar het op wacht', () => {
|
|---|
| 76 | const rows = gated.gateRows({ waiting: { 'shaer:follows': 2 } });
|
|---|
| 77 | assert.equal(rij(rows, 'shaer:follows').waiting, 2);
|
|---|
| 78 | });
|
|---|
| 79 |
|
|---|
| [72d8eff] | 80 | test('de stand komt uit de BESLUITEN, en zegt of er wel besloten is', () => {
|
|---|
| 81 | // Er zijn geen lokale accounts: elke ward woont elders, dus de kolom op onze
|
|---|
| 82 | // eigen sites-tabel is voor iedere ward leeg. Wat een guardian wel heeft is de
|
|---|
| 83 | // uitslag van wat hij voorstelde.
|
|---|
| 84 | const aan = gated.gateRows({ settings: { 'shaer:externalEmbeds': { value: true, decided: true } } });
|
|---|
| 85 | assert.equal(rij(aan, 'shaer:externalEmbeds').value, true);
|
|---|
| 86 | assert.equal(rij(aan, 'shaer:externalEmbeds').decided, true);
|
|---|
| 87 | });
|
|---|
| 88 |
|
|---|
| 89 | test('nooit besloten is iets anders dan besloten-uit', () => {
|
|---|
| 90 | // "Uit" en "voor zover wij weten uit" zijn niet hetzelfde. Dat tweede als een
|
|---|
| 91 | // besluit tonen suggereert dat iemand het genomen heeft.
|
|---|
| 92 | const r = rij(gated.gateRows({ settings: { 'shaer:externalEmbeds': { value: false, decided: false } } }), 'shaer:externalEmbeds');
|
|---|
| 93 | assert.equal(r.value, false);
|
|---|
| 94 | assert.equal(r.decided, false);
|
|---|
| 95 | });
|
|---|
| 96 |
|
|---|
| 97 | test('de trap blokkeert alleen op een BESLOTEN dicht', () => {
|
|---|
| 98 | // Blokkeren op de standaard zou betekenen dat afspelen nooit als eerste
|
|---|
| 99 | // voorgesteld kan worden -- en zo ging er ooit een hele voorstelronde de
|
|---|
| 100 | // verkeerde gate in.
|
|---|
| 101 | const standaard = gated.gateRows({ settings: { 'shaer:externalEmbeds': { value: false, decided: false } } });
|
|---|
| 102 | assert.equal(rij(standaard, 'shaer:externalPlayback').adjustable, true);
|
|---|
| 103 | const beslist = gated.gateRows({ settings: { 'shaer:externalEmbeds': { value: false, decided: true } } });
|
|---|
| 104 | assert.equal(rij(beslist, 'shaer:externalPlayback').adjustable, false);
|
|---|
| 105 | });
|
|---|
| 106 |
|
|---|
| 107 | test('de oude vorm (kale boolean) blijft werken', () => {
|
|---|
| 108 | // Backwards: een aanroeper die nog true/false doorgeeft hoort niet om te vallen.
|
|---|
| 109 | const r = rij(gated.gateRows({ settings: { 'shaer:externalEmbeds': true } }), 'shaer:externalEmbeds');
|
|---|
| 110 | assert.equal(r.value, true);
|
|---|
| 111 | assert.equal(r.decided, true);
|
|---|
| 112 | });
|
|---|
| 113 |
|
|---|
| [792af53] | 114 | test('omkeerbaarheid staat per gate genoteerd', () => {
|
|---|
| 115 | // Nu is alles omkeerbaar. Zodra independence erbij komt (shaer-90v) is dat het
|
|---|
| 116 | // niet, en dan moet het paneel dat kunnen zeggen zonder verbouwing.
|
|---|
| 117 | for (const r of gated.gateRows({})) assert.equal(typeof r.reversible, 'boolean');
|
|---|
| 118 | });
|
|---|
| [b2646cc] | 119 |
|
|---|
| 120 | // ── De waarschuwing bij een voorstel (shaer-nf9) ────────────────────────
|
|---|
| 121 |
|
|---|
| 122 | test('een gewone gate waarschuwt omkeerbaar, niet onomkeerbaar', () => {
|
|---|
| 123 | // Barts zin "een geopende poort gaat niet meer dicht" klopt niet over de
|
|---|
| 124 | // INSTELLING -- die gaat wel weer dicht. Zo'n aantoonbaar onware waarschuwing
|
|---|
| 125 | // neemt de rest van het scherm mee in zijn val zodra iemand het merkt.
|
|---|
| 126 | assert.equal(gated.gateConsequence('shaer:externalEmbeds'), 'reversible');
|
|---|
| 127 | });
|
|---|
| 128 |
|
|---|
| 129 | test('independence waarschuwt WEL onomkeerbaar', () => {
|
|---|
| 130 | // De enige die gezag overdraagt (shaer-90v). Daar is Barts zin wel waar.
|
|---|
| 131 | assert.equal(gated.gateConsequence('shaer:independence'), 'irreversible');
|
|---|
| 132 | });
|
|---|
| 133 |
|
|---|
| 134 | test('een gate die we NIET kennen krijgt de zwaarste tekst', () => {
|
|---|
| 135 | // Een mede-guardian elders kan iets voorstellen dat onze catalogus niet kent.
|
|---|
| 136 | // Bij twijfel waarschuwen we zwaarder, niet lichter: de faalstand die hier pijn
|
|---|
| 137 | // doet is een guardian die iets doorlaat omdat het scherm er licht over deed.
|
|---|
| 138 | assert.equal(gated.gateConsequence('shaer:ietsNieuws'), 'unknown');
|
|---|
| 139 | });
|
|---|
| 140 |
|
|---|
| 141 | test('elke gate uit de catalogus levert een gevolg op', () => {
|
|---|
| 142 | // Anders valt er stilletjes een rij zonder waarschuwing door.
|
|---|
| 143 | for (const g of gated.GATE_CATALOGUE) {
|
|---|
| 144 | assert.ok(['reversible', 'irreversible'].includes(gated.gateConsequence(g.feature)), g.feature);
|
|---|
| 145 | }
|
|---|
| 146 | });
|
|---|
| [f3a2556] | 147 |
|
|---|
| 148 | // ── Dezelfde poorten voor het paneel en voor de apps (shaer-ahy.1) ───────
|
|---|
| 149 |
|
|---|
| 150 | test('de wards-queue draagt de poorten mee', async () => {
|
|---|
| 151 | // Zonder dit kon een app een ward wel TONEN maar niets over hem zeggen. De
|
|---|
| 152 | // apps lezen deze collectie; het PWA-paneel leest wardGates rechtstreeks.
|
|---|
| 153 | const q = await import('../src/services/guardianship/queues.js');
|
|---|
| 154 | const rel = await import('../src/services/guardianship/relations.js');
|
|---|
| 155 | const dbm = await import('../src/config/database.js');
|
|---|
| 156 | dbm.initializeDatabase();
|
|---|
| 157 | dbm.default.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 158 | .run('gu', 'gu', 'gu@t', 'x', 'god');
|
|---|
| 159 | dbm.default.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('gs', 'oma', 'Oma', 'gu');
|
|---|
| 160 | rel.commitWardForGuardian('oma', 'https://elders/ap/users/kind', { handle: '@kind' });
|
|---|
| 161 |
|
|---|
| 162 | const coll = q.wardsCollection('https://x/queues/wards', 'oma');
|
|---|
| 163 | const ward = coll.orderedItems.find((w) => w.id === 'https://elders/ap/users/kind');
|
|---|
| 164 | assert.ok(ward, 'de ward staat in de collectie');
|
|---|
| 165 | assert.ok(Array.isArray(ward['shaer:gates']), 'en draagt zijn poorten');
|
|---|
| 166 | assert.equal(ward['shaer:gates'].length, gated.GATE_CATALOGUE.length);
|
|---|
| 167 | });
|
|---|
| 168 |
|
|---|
| 169 | test('de app krijgt PRECIES dezelfde rijen als het paneel', async () => {
|
|---|
| 170 | // Twee berekeningen naast elkaar geven vroeg of laat een ander antwoord op
|
|---|
| 171 | // dezelfde vraag, en dat is hier geen schoonheidsfoutje: dan krijgen twee
|
|---|
| 172 | // guardians een verschillend beeld van hetzelfde kind.
|
|---|
| 173 | const q = await import('../src/services/guardianship/queues.js');
|
|---|
| 174 | const coll = q.wardsCollection('https://x/queues/wards', 'oma');
|
|---|
| 175 | const uitQueue = coll.orderedItems.find((w) => w.id === 'https://elders/ap/users/kind')['shaer:gates'];
|
|---|
| 176 | const uitPaneel = q.wardGates('oma', 'https://elders/ap/users/kind');
|
|---|
| 177 | assert.deepEqual(uitQueue, uitPaneel);
|
|---|
| 178 | });
|
|---|