source: Klonkt/test/gates-capabilities-parity.test.js@ 6c83c9e

main
Last change on this file since 6c83c9e was 6c83c9e, checked in by Bart <bart@…>, 4 weeks ago

Wat gated is hoort de app te weten: een test die dat afdwingt

GATE_CATALOGUE zegt wat er gated is, shaer:capabilities zegt wat een account
mag. Twee handmatige lijsten op twee plekken, en niets dat ze bij elkaar hield:
bleef er eentje achter, dan verscheen de poort netjes in het paneel van de
guardian en hoorde de app van het kind er nooit van. Die stilte is het gat --
de app ontdekt de poort dan pas bij de eerste weigering, precies wat het
commentaar bij dat blok zegt te willen voorkomen.

De test viel meteen om over shaer:accountMove: catalogus ja, kolom ja,
capability nee. Nu wel.

Eén richting, met opzet. Niet elke capability is een poort: shaer:externalLinks
hangt aan de playback-poort en shaer:cursor is een leesteken voor de wachtrij.
En vast (shaer:follows) of gepland (shaer:publicProfile) hoeft er niet in, want
daar valt geen stand van te bewaren -- een tweede test houdt de eerste ervan af
strenger te worden dan dat.

Co-Authored-By: Claude Opus 5 <claude@…>

  • Property mode set to 100644
File size: 3.3 KB
Line 
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.
18import { test } from 'node:test';
19import assert from 'node:assert/strict';
20import fs from 'fs';
21
22process.env.DATABASE_PATH = ':memory:';
23process.env.PUBLIC_BASE_URL = 'https://test.example';
24
25const dbMod = await import('../src/config/database.js');
26dbMod.initializeDatabase();
27const gated = await import('../src/services/guardianship/gated.js');
28
29const SOURCE = 'src/routes/activitypub.js';
30
31/** De sleutels binnen het shaer:capabilities-blok, gelezen uit de bron. */
32function 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
47test('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
64test('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});
Note: See TracBrowser for help on using the repository browser.