| 1 | // Guardianship Fase 2 (shaer-jdb): gate-verzoeken bereiken een C2S-client.
|
|---|
| 2 | //
|
|---|
| 3 | // De gating zelf werkt sinds shaer-hxg. Wat ontbrak was het DOORGEVEN: de
|
|---|
| 4 | // wachtrij `follows` gaf hardgecodeerd een lege lijst terug ("not built in
|
|---|
| 5 | // Klonkt yet (Fase 2)") en `outgoing-follows` serveerde alleen de ward-kant.
|
|---|
| 6 | // Daardoor bleven beide secties in Shaer altijd leeg.
|
|---|
| 7 | //
|
|---|
| 8 | // En daaronder zat de echte fout: shaer:direction werd bij het versturen gezet
|
|---|
| 9 | // en nergens gelezen, dus een UITGAAND verzoek werd opgeslagen als "deze ward
|
|---|
| 10 | // wil deze ward volgen" met het doel weggegooid.
|
|---|
| 11 | //
|
|---|
| 12 | // Run: npm test
|
|---|
| 13 |
|
|---|
| 14 | import { test } from 'node:test';
|
|---|
| 15 | import assert from 'node:assert/strict';
|
|---|
| 16 |
|
|---|
| 17 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 18 | process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
|
|---|
| 19 |
|
|---|
| 20 | const dbMod = await import('../src/config/database.js');
|
|---|
| 21 | const db = dbMod.default;
|
|---|
| 22 | dbMod.initializeDatabase();
|
|---|
| 23 | const G = await import('../src/services/guardianship/index.js');
|
|---|
| 24 |
|
|---|
| 25 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 26 | .run('u1', 'oma', 'o@test', 'x', 'god');
|
|---|
| 27 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'oma', 'Oma', 'u1');
|
|---|
| 28 |
|
|---|
| 29 | const WARD = 'https://kind.test/ap/users/kind';
|
|---|
| 30 | const VREEMDE = 'https://elders.test/users/vreemde';
|
|---|
| 31 | const DOEL = 'https://muziek.test/users/band';
|
|---|
| 32 | const ME = 'https://klonkt.test/ap/users/oma';
|
|---|
| 33 |
|
|---|
| 34 | test('een INKOMEND verzoek komt in de follows-wachtrij', () => {
|
|---|
| 35 | G.follows.recordReview('oma', {
|
|---|
| 36 | id: 'f-in', wardUri: WARD, follower: VREEMDE, followerHandle: '@vreemde@elders.test',
|
|---|
| 37 | direction: 'incoming',
|
|---|
| 38 | });
|
|---|
| 39 | const c = G.followsCollection(`${ME}/queues/follows`, 'oma', ME);
|
|---|
| 40 | assert.equal(c.totalItems, 1, 'deze wachtrij gaf hiervoor altijd een lege lijst terug');
|
|---|
| 41 | const it = c.orderedItems[0];
|
|---|
| 42 | assert.equal(it.actor, VREEMDE, 'de vreemde is de volger');
|
|---|
| 43 | assert.equal(it.object, WARD, 'en de ward is het doel');
|
|---|
| 44 | assert.equal(it['shaer:direction'], 'incoming');
|
|---|
| 45 | });
|
|---|
| 46 |
|
|---|
| 47 | test('een UITGAAND verzoek komt in de outgoing-wachtrij, met het doel erin', () => {
|
|---|
| 48 | // De kern van de bug: hiervoor werd dit als inkomend opgeslagen met ward ==
|
|---|
| 49 | // follower, en het doel -- waar het antwoord over gaat -- viel weg.
|
|---|
| 50 | G.follows.recordReview('oma', {
|
|---|
| 51 | id: 'f-uit', wardUri: WARD, follower: WARD, direction: 'outgoing',
|
|---|
| 52 | target: DOEL, targetHandle: '@band@muziek.test',
|
|---|
| 53 | });
|
|---|
| 54 | const c = G.outgoingFollowsCollection(`${ME}/queues/outgoing-follows`, 'oma', ME);
|
|---|
| 55 | const it = c.orderedItems.find((x) => x.id === 'f-uit');
|
|---|
| 56 | assert.ok(it, 'een guardian zag hier nooit iets: de wachtrij serveerde alleen de ward-kant');
|
|---|
| 57 | assert.equal(it.actor, WARD, 'de ward is hier de volger');
|
|---|
| 58 | assert.equal(it.object, DOEL);
|
|---|
| 59 | assert.equal(it['shaer:target'], DOEL, 'zonder dit valt er niets te beoordelen');
|
|---|
| 60 | assert.equal(it['shaer:targetHandle'], '@band@muziek.test');
|
|---|
| 61 | assert.equal(it['shaer:direction'], 'outgoing');
|
|---|
| 62 | });
|
|---|
| 63 |
|
|---|
| 64 | test('de twee richtingen lopen niet door elkaar', () => {
|
|---|
| 65 | const inn = G.followsCollection(`${ME}/q/f`, 'oma', ME).orderedItems.map((x) => x.id);
|
|---|
| 66 | const uit = G.outgoingFollowsCollection(`${ME}/q/o`, 'oma', ME).orderedItems.map((x) => x.id);
|
|---|
| 67 | assert.deepEqual(inn, ['f-in']);
|
|---|
| 68 | assert.ok(uit.includes('f-uit') && !uit.includes('f-in'));
|
|---|
| 69 | });
|
|---|
| 70 |
|
|---|
| 71 | test('bij een remote ward wordt het aantal guardians WEGGELATEN, niet op nul gezet', () => {
|
|---|
| 72 | // De guardian-set van een remote ward wordt op diens eigen server bijgehouden.
|
|---|
| 73 | // Nul sturen zou lezen als "dit kind heeft geen guardians", en dat is het
|
|---|
| 74 | // tegenovergestelde van onbekend.
|
|---|
| 75 | const it = G.followsCollection(`${ME}/q/f`, 'oma', ME).orderedItems[0];
|
|---|
| 76 | assert.equal(it['shaer:guardianCount'], undefined);
|
|---|
| 77 | });
|
|---|
| 78 |
|
|---|
| 79 | test('een oude rij zonder richting telt als inkomend', () => {
|
|---|
| 80 | // Bestaande rijen missen de kolom en zijn niet te repareren -- de informatie
|
|---|
| 81 | // stond er nooit in. Ze horen terug te vallen op het geval dat ze toen waren.
|
|---|
| 82 | db.prepare(`INSERT INTO ap_follow_reviews (id, guardian_slug, ward_uri, follower_uri, direction)
|
|---|
| 83 | VALUES ('f-oud','oma',?,?,NULL)`).run(WARD, VREEMDE);
|
|---|
| 84 | const inn = G.followsCollection(`${ME}/q/f`, 'oma', ME).orderedItems.map((x) => x.id);
|
|---|
| 85 | assert.ok(inn.includes('f-oud'));
|
|---|
| 86 | });
|
|---|
| 87 |
|
|---|
| 88 | // ── De drempel voor een volgverzoek (Barts besluit, 8-8) ────────────────
|
|---|
| 89 |
|
|---|
| 90 | test('een eenvoudige meerderheid: 1 van 2 is voldoende', () => {
|
|---|
| 91 | // Barts woorden. Bewust soepeler dan de POORTdrempel: een gate opent een deur
|
|---|
| 92 | // voor alles wat daarna komt, een volgverzoek gaat over een persoon en is met
|
|---|
| 93 | // ontvolgen terug te draaien.
|
|---|
| 94 | assert.equal(G.follows.followThreshold(1), 1);
|
|---|
| 95 | assert.equal(G.follows.followThreshold(2), 1);
|
|---|
| 96 | assert.equal(G.follows.followThreshold(3), 2);
|
|---|
| 97 | assert.equal(G.follows.followThreshold(4), 2);
|
|---|
| 98 | });
|
|---|
| 99 |
|
|---|
| 100 | test('en dat is een AANSCHERPING, geen versoepeling', () => {
|
|---|
| 101 | // Tot vandaag stond dit op 'any': een enkele ja, hoeveel guardians er ook
|
|---|
| 102 | // waren. Bij drie of meer is er nu meer nodig, niet minder.
|
|---|
| 103 | assert.ok(G.follows.followThreshold(3) > 1);
|
|---|
| 104 | assert.ok(G.follows.followThreshold(5) > 1);
|
|---|
| 105 | });
|
|---|
| 106 |
|
|---|
| 107 | test('nul guardians vraagt nog steeds iemand', () => {
|
|---|
| 108 | // Een lege set mag nooit "iedereen is het eens" opleveren. Dat is de stille
|
|---|
| 109 | // fout waarmee een verzoek zichzelf goedkeurt.
|
|---|
| 110 | assert.equal(G.follows.followThreshold(0), 1);
|
|---|
| 111 | });
|
|---|
| 112 |
|
|---|
| 113 | test('de drempel voor een POORT blijft strikter', () => {
|
|---|
| 114 | // Twee verschillende vragen, twee drempels, en dat verschil is opzet: 2 van 2
|
|---|
| 115 | // voor een gate, 1 van 2 voor een volgverzoek.
|
|---|
| 116 | assert.equal(G.gated.thresholdFor(2), 2);
|
|---|
| 117 | assert.equal(G.follows.followThreshold(2), 1);
|
|---|
| 118 | });
|
|---|