| 1 | // Twee lijsten die hetzelfde horen te weten, en niets dat ze bij elkaar houdt.
|
|---|
| 2 | //
|
|---|
| 3 | // GATE_CATALOGUE zegt WAT er gated is. Het shaer:capabilities-blok zegt wat een
|
|---|
| 4 | // account op dit moment MAG. Ze worden allebei met de hand bijgehouden, op twee
|
|---|
| 5 | // plekken, en er komt geen fout van als er eentje achterblijft: de gate
|
|---|
| 6 | // verschijnt netjes in het paneel van de guardian, en de app van het kind hoort
|
|---|
| 7 | // er nooit van. Die stilte is het probleem -- de app ontdekt de poort dan pas
|
|---|
| 8 | // bij de eerste weigering, wat precies is wat het commentaar bij dat blok zegt
|
|---|
| 9 | // te willen voorkomen ("de app hoort VOORAF te weten wat hij mag aanbieden").
|
|---|
| 10 | //
|
|---|
| 11 | // Zo'n gat is echt gevallen: shaer:accountMove stond in de catalogus met een
|
|---|
| 12 | // kolom en al, en ontbrak in capabilities. Deze test is de reden dat het de
|
|---|
| 13 | // volgende keer opvalt zonder dat iemand het toevallig ziet.
|
|---|
| 14 | //
|
|---|
| 15 | // Eén richting, met opzet. Niet elke capability is een gate: shaer:externalLinks
|
|---|
| 16 | // hangt aan de playback-poort en shaer:cursor is helemaal geen poort maar een
|
|---|
| 17 | // leesteken voor de wachtrij.
|
|---|
| 18 | import { test } from 'node:test';
|
|---|
| 19 | import assert from 'node:assert/strict';
|
|---|
| 20 | import fs from 'fs';
|
|---|
| 21 |
|
|---|
| 22 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 23 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 24 |
|
|---|
| 25 | const dbMod = await import('../src/config/database.js');
|
|---|
| 26 | dbMod.initializeDatabase();
|
|---|
| 27 | const gated = await import('../src/services/guardianship/gated.js');
|
|---|
| 28 |
|
|---|
| 29 | const SOURCE = 'src/routes/activitypub.js';
|
|---|
| 30 |
|
|---|
| 31 | /** De sleutels binnen het shaer:capabilities-blok, gelezen uit de bron. */
|
|---|
| 32 | function capabilityKeys() {
|
|---|
| 33 | const src = fs.readFileSync(SOURCE, 'utf8');
|
|---|
| 34 | const start = src.indexOf("'shaer:capabilities': {");
|
|---|
| 35 | assert.notEqual(start, -1, `${SOURCE} heeft nog een shaer:capabilities-blok`);
|
|---|
| 36 | const open = src.indexOf('{', start);
|
|---|
| 37 | let depth = 0;
|
|---|
| 38 | let end = open;
|
|---|
| 39 | for (let i = open; i < src.length; i++) {
|
|---|
| 40 | if (src[i] === '{') depth++;
|
|---|
| 41 | else if (src[i] === '}') { depth--; if (depth === 0) { end = i; break; } }
|
|---|
| 42 | }
|
|---|
| 43 | const body = src.slice(open + 1, end);
|
|---|
| 44 | return new Set([...body.matchAll(/'(shaer:[a-zA-Z]+)'\s*:/g)].map((m) => m[1]));
|
|---|
| 45 | }
|
|---|
| 46 |
|
|---|
| 47 | test('elke gate met een eigen kolom staat ook in shaer:capabilities', () => {
|
|---|
| 48 | const caps = capabilityKeys();
|
|---|
| 49 | assert.ok(caps.size > 5, 'het blok is gevonden en niet leeg');
|
|---|
| 50 |
|
|---|
| 51 | // Een kolom hebben is het criterium: dat is precies de verzameling poorten
|
|---|
| 52 | // waar een stand van bewaard wordt, en dus waar de app iets aan heeft. Een
|
|---|
| 53 | // vaste poort (shaer:follows) heeft er geen, want er valt niets te bewaren,
|
|---|
| 54 | // en een geplande (available: false) bestaat nog niet.
|
|---|
| 55 | const missing = gated.GATE_CATALOGUE
|
|---|
| 56 | .filter((g) => g.available !== false && gated.featureColumn(g.feature))
|
|---|
| 57 | .map((g) => g.feature)
|
|---|
| 58 | .filter((f) => !caps.has(f));
|
|---|
| 59 |
|
|---|
| 60 | assert.deepEqual(missing, [],
|
|---|
| 61 | `deze poorten bestaan wel maar de app krijgt ze niet te horen: ${missing.join(', ')}`);
|
|---|
| 62 | });
|
|---|
| 63 |
|
|---|
| 64 | test('een vaste of geplande poort hoeft er niet in te staan', () => {
|
|---|
| 65 | // De regel hierboven mag niet zo streng worden dat hij dingen eist die geen
|
|---|
| 66 | // stand hebben. shaer:follows ligt vast (§5.3) en shaer:publicProfile bestaat
|
|---|
| 67 | // nog niet; allebei terecht afwezig, en dat moet zo kunnen blijven.
|
|---|
| 68 | const caps = capabilityKeys();
|
|---|
| 69 | assert.equal(caps.has('shaer:follows'), false, 'vast: geen kolom, geen capability');
|
|---|
| 70 | assert.equal(gated.featureColumn('shaer:publicProfile'), null, 'gepland: nog geen kolom');
|
|---|
| 71 | });
|
|---|