source: Klonkt/test/gates-family.test.js@ 2220c49

main
Last change on this file since 2220c49 was 3b43e4c, checked in by Robin <roboburr@…>, 5 weeks ago

De gate-familie functioneel: acht poorten die echt schakelen (shaer-ahy.1)

Barts opdracht (8-8): "maak ze allemaal maar functioneel." Alle
setting-gates uit de catalogus hebben nu een kolom, zijn voorstelbaar en
beslisbaar via de bestaande tally, en worden ECHT afgedwongen -- bij de
aflevering (wat dicht is wordt nooit geserialiseerd, de regel die de
embeds al hadden) of bij de inname (wat de ward niet mag versturen
weigert de outbox met een eerlijke 403).

externalThreads de thread-kring (shaer-9y2): dicht is de kring van de

guardians met telling, open is alles; per verzoek en
buiten de threadcache om, want een poort die net
dichtging mag niet twee minuten open nawerken

images/music bijlagen gefilterd op mediaType, ook in de thread
quoteCards shaer:quote niet geserialiseerd
customEmoji Emoji-tags en byline-emoji niet geserialiseerd; de

:shortcode: blijft als tekst staan, dat is eerlijk

messages de berichten-poot dicht voor vreemden en vrienden,

maar NOOIT voor het guardian-kanaal, en de outbox
weigert directe berichten -- behalve de reddingsboei:
een poort die het hulpkanaal afsnijdt beschermt
niemand

compose de outbox weigert eigen posts; een antwoord valt

onder het gesprek, niet onder een eigen podium

accountMove de harde weigering van shaer-tge is een gate

geworden met dezelfde standaard: guardians kunnen
hem nu openzetten

De capabilities dragen de hele familie, zodat de app VOORAF weet wat hij
mag aanbieden -- de (+) kaart leest shaer:compose al. De voorstelroute
herschreef een onbekende feature stilletjes naar externalEmbeds; dat is
nu een 400, want een voorstel dat op de verkeerde poort landt mag een
guardian nooit overkomen. Het paneel leest de standen catalogusbreed.

TWEE blijven bewust gepland. publicProfile is niet een veld dat je
wegfiltert maar het hele publieke web-oppervlak (de ontwerpvraag van
shaer-hj0); een half slot leest als een heel slot en dat is gevaarlijker
dan geen. independence draagt gezag over een kind over en zijn vorm
hoort bij shaer-90v beslist te worden, niet hier geimproviseerd.

Zestien nieuwe/aangepaste toetsen, alle 640 groen.

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

  • Property mode set to 100644
File size: 5.7 KB
Line 
1// De gate-familie functioneel (shaer-ahy.1, Barts opdracht 8-8): niet alleen
2// een toets in het paneel, maar een poort die echt dichtkan -- bij de
3// AFLEVERING (wat dicht is wordt nooit geserialiseerd) of bij de INNAME (wat
4// de ward niet mag versturen wordt aan de outbox geweigerd).
5//
6// De twee dingen die hier stil kunnen breken: een poort die dicht lijkt maar
7// niets tegenhoudt (een guardian waant het kind beschermd), en een poort die
8// het hulpkanaal mee afsluit (de boei MOET door elke dichte deur heen).
9//
10// In-memory SQLite. Run: npm test
11import { test } from 'node:test';
12import assert from 'node:assert/strict';
13
14process.env.DATABASE_PATH = ':memory:';
15process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
16
17const dbMod = await import('../src/config/database.js');
18const db = dbMod.default;
19dbMod.initializeDatabase();
20const AP = await import('../src/services/ActivityPubService.js');
21const Guardianship = await import('../src/services/guardianship/index.js');
22
23db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
24 .run('u1', 'u1', 'u1@test', 'x', 'god');
25db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'kind', 'Kind', 'u1');
26const site = () => db.prepare('SELECT * FROM sites WHERE id = ?').get('s1');
27const user = { id: 'u1', username: 'u1' };
28
29test('elke beschikbare gate in de catalogus heeft een kolom', () => {
30 // De bewaker van de bedrading: een catalogusrij zonder kolom is een toets
31 // die nergens op aangesloten is, en dat merkt pas iemand die erop drukt.
32 for (const g of Guardianship.gated.GATE_CATALOGUE.filter((x) => x.available !== false)) {
33 if (g.fixed) continue; // shaer:follows is een stroom, geen stand
34 assert.ok(Guardianship.gated.featureColumn(g.feature), `${g.feature} mist een kolom`);
35 }
36});
37
38test('de generieke regel: expliciet wint, de automatiek sluit voor een ward', () => {
39 assert.equal(Guardianship.wardGateAllowed(null, false), true);
40 assert.equal(Guardianship.wardGateAllowed(null, true), false);
41 assert.equal(Guardianship.wardGateAllowed(1, true), true); // guardians openden hem
42 assert.equal(Guardianship.wardGateAllowed(0, false), false); // expliciete 0 wint ook zonder wardstatus
43});
44
45test('gateAttachments: beeld en muziek dicht laten de rest staan, leeg wordt undefined', () => {
46 const atts = [
47 { type: 'Document', mediaType: 'image/webp', url: 'https://x/p.webp' },
48 { type: 'Audio', mediaType: 'audio/mpeg', url: 'https://x/a.mp3' },
49 { type: 'Document', mediaType: 'video/mp4', url: 'https://x/v.mp4' },
50 ];
51 const zonderBeeld = AP.gateAttachments(atts, { images: false });
52 assert.deepEqual(zonderBeeld.map((a) => a.mediaType), ['audio/mpeg', 'video/mp4']);
53 const zonderMuziek = AP.gateAttachments(atts, { audio: false });
54 assert.deepEqual(zonderMuziek.map((a) => a.mediaType), ['image/webp', 'video/mp4']);
55 // Alles weg is undefined, zoals de serialisatie een leeg veld overal weglaat.
56 assert.equal(AP.gateAttachments([atts[0]], { images: false }), undefined);
57 // En de poort open verandert niets.
58 assert.equal(AP.gateAttachments(atts, {}).length, 3);
59});
60
61test('stripEmojiTags haalt alleen de Emoji-tags weg', () => {
62 const tags = [
63 { type: 'Emoji', name: ':hartje:', icon: { url: 'https://x/h.png' } },
64 { type: 'Mention', href: 'https://x/u', name: '@u' },
65 { type: 'Hashtag', name: '#muziek' },
66 ];
67 const uit = AP.stripEmojiTags(tags);
68 assert.deepEqual(uit.map((t) => t.type), ['Mention', 'Hashtag']);
69 assert.equal(AP.stripEmojiTags([tags[0]]), undefined);
70});
71
72test('compose dicht weigert een eigen post aan de outbox, maar geen antwoord', async () => {
73 db.prepare("UPDATE sites SET gate_compose = 0 WHERE slug = 'kind'").run();
74 const post = { type: 'Create', object: { type: 'Note', content: '<p>hoi</p>', to: ['https://www.w3.org/ns/activitystreams#Public'] } };
75 const uit = await AP.ingestOutboxActivity(site(), user, post);
76 assert.equal(uit.status, 403);
77 assert.equal(uit.error, 'gated_compose');
78 // Een antwoord is meedoen aan een gesprek, geen eigen podium: dat valt
79 // onder messages/het gesprek, niet onder compose.
80 const reply = { type: 'Create', object: { type: 'Note', content: '<p>ja!</p>', inReplyTo: 'https://elders/x', to: ['https://www.w3.org/ns/activitystreams#Public'] } };
81 const uit2 = await AP.ingestOutboxActivity(site(), user, reply);
82 assert.notEqual(uit2.error, 'gated_compose');
83 db.prepare("UPDATE sites SET gate_compose = NULL WHERE slug = 'kind'").run();
84});
85
86test('messages dicht weigert een direct bericht, maar NOOIT de reddingsboei', async () => {
87 db.prepare("UPDATE sites SET gate_messages = 0 WHERE slug = 'kind'").run();
88 const dm = { type: 'Create', object: { type: 'Note', content: '<p>psst</p>', to: ['https://elders/ap/users/vreemde'] } };
89 const uit = await AP.ingestOutboxActivity(site(), user, dm);
90 assert.equal(uit.status, 403);
91 assert.equal(uit.error, 'gated_messages');
92 // De boei gaat door elke dichte deur heen: een poort die het hulpkanaal
93 // afsnijdt beschermt niemand.
94 const hulp = { type: 'Create', object: { type: 'Note', content: '<p>🛟</p>', 'shaer:helpRequest': true, to: ['https://elders/ap/users/oma'] } };
95 const uit2 = await AP.ingestOutboxActivity(site(), user, hulp);
96 assert.notEqual(uit2.error, 'gated_messages');
97 db.prepare("UPDATE sites SET gate_messages = NULL WHERE slug = 'kind'").run();
98});
99
100test('accountMove dicht weigert de verhuizing voordat er iets vertrekt', async () => {
101 db.prepare("UPDATE sites SET gate_account_move = 0 WHERE slug = 'kind'").run();
102 const uit = await AP.moveAccount(site(), '@iemand@elders.example');
103 assert.equal(uit.error, 'guarded_account');
104 db.prepare("UPDATE sites SET gate_account_move = NULL WHERE slug = 'kind'").run();
105});
Note: See TracBrowser for help on using the repository browser.