source: Klonkt/test/guardian-help-state.test.js@ a29c8c8

main
Last change on this file since a29c8c8 was a29c8c8, checked in by roboburr <roboburr@…>, 5 weeks ago

Een hulpvraag oppikken en afsluiten, zichtbaar voor alle guardians (shaer-lgo)

Een hulpvraag (5.2.1) gaat naar ALLE guardians van een kind, op verschillende
servers. Zonder gedeelde staat denken er twee dat de ander het oppakt -- precies
het scenario waar de reddingsboei voor bestaat. Tot nu toe droeg een hulpvraag
geen enkele staat: een vlag op ap_mentions, verder niets.

DE FAALSTAND IS HIER NIET VEILIG, en dat stuurt het hele ontwerp. Bij een gate is
"dicht" het veilige antwoord. Hier is de faalstand "iedereen denkt dat het
geregeld is", en dat is gevaarlijker dan geen markering. Daarom staat een
hulpvraag bij twijfel OPEN: een lege lijst, een rij die we niet kunnen lezen, een
soort die we niet kennen -- alles wat geen expliciete afsluiting is telt als
"er wacht nog iemand".

Twee besluiten van Bart zitten in de vorm.

OPGEPIKT mag stapelen en vervalt niet, maar veroudert zichtbaar. Twee mensen die
tegelijk reageren op een kind is geen probleem; twee die allebei niets doen omdat
de ander het "geclaimd" had, wel. En een signaal dat vanzelf verdwijnt laat een
hulpvraag er onaangeroerd uitzien terwijl er iemand mee bezig is -- dus het blijft
staan en toont hoe oud het is.

AFGEHANDELD kent GEEN terugdraai. Sluiten gaat met een stevige bevestiging (geen
window.confirm, zelfde lijn als het loslaten van een ward), en leeft de vraag
daarna nog, dan wordt hij opnieuw gesteld -- een nieuwe hulpvraag. Er wordt niets
herschreven, er wordt toegevoegd. Een test bewaakt dat er geen undo() of
reopen() bestaat.

Geen nieuw protocol: de markering is een gewone directe note met een
shaer:-eigenschap, net als de zwaai en de afwezigheidsmelding. Daardoor reist hij
over de bestaande bezorging, ziet de WARD hem als bericht ("er komt iemand") en
houden de mede-guardians er staat aan over. Naar de ward en naar de andere
guardians tegelijk.

De eigen kopie wordt meteen weggeschreven, voordat er bezorgd is: het scherm van
degene die klikt hoort niet te liegen omdat een andere server traag is.

11 tests op het pure stuk. Gecontroleerd dat ze bijten: laat helpStatus altijd
"afgehandeld" zeggen en er vallen er vijf om. nl/en/de. Suite 576/576.

Niet gedekt: de weergave zelf (client-JS), en er is geen guardianship op dev om
het end-to-end te zien lopen.

  • Property mode set to 100644
File size: 5.0 KB
Line 
1// Oppikken en afhandelen van een hulpvraag (shaer-lgo).
2//
3// Een hulpvraag gaat naar ALLE guardians van een kind, op verschillende servers.
4// Zonder gedeelde staat denken er twee dat de ander het oppakt -- en dat is
5// precies het scenario waar de reddingsboei voor bestaat.
6//
7// DE FAALSTAND IS HIER NIET VEILIG. Bij een gate is 'dicht' het veilige
8// antwoord; hier is de faalstand 'iedereen denkt dat het geregeld is', en dat is
9// gevaarlijker dan geen markering. Vandaar dat de meeste tests hieronder gaan
10// over wat er gebeurt als er IETS niet klopt: dan hoort de hulpvraag OPEN te
11// staan.
12//
13// Run: npm test
14
15import { test } from 'node:test';
16import assert from 'node:assert/strict';
17
18process.env.DATABASE_PATH = ':memory:';
19process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
20
21const dbMod = await import('../src/config/database.js');
22const db = dbMod.default;
23dbMod.initializeDatabase();
24const help = await import('../src/services/guardianship/help.js');
25
26const NOTE = 'https://kind.test/ap/notes/hulp-1';
27const OMA = 'https://oma.test/ap/users/oma';
28const OPA = 'https://opa.test/ap/users/opa';
29
30test('een verse hulpvraag staat open', () => {
31 const s = help.statusOf(NOTE);
32 assert.equal(s.open, true);
33 assert.deepEqual(s.pickedUpBy, []);
34 assert.equal(s.handled, null);
35});
36
37test('oppikken mag STAPELEN', () => {
38 // Twee mensen die tegelijk reageren op een kind dat om hulp vraagt is geen
39 // probleem. Twee die allebei niets doen omdat de ander het geclaimd had, wel.
40 help.record(NOTE, OMA, 'pickup', '@oma@oma.test');
41 help.record(NOTE, OPA, 'pickup', '@opa@opa.test');
42 const s = help.statusOf(NOTE);
43 assert.equal(s.pickedUpBy.length, 2);
44 assert.equal(s.open, true, 'opgepikt is nog niet afgehandeld');
45});
46
47test('en het staat erbij DOOR WIE, want dat was de hele vraag', () => {
48 const s = help.statusOf(NOTE);
49 assert.ok(s.pickedUpBy.some((p) => p.handle === '@oma@oma.test'));
50 assert.ok(s.pickedUpBy.every((p) => p.at), 'met een tijdstip, anders kun je niet zien dat het oud wordt');
51});
52
53test('twee keer hetzelfde oppikken telt een keer', () => {
54 help.record(NOTE, OMA, 'pickup', '@oma@oma.test');
55 assert.equal(help.statusOf(NOTE).pickedUpBy.length, 2);
56});
57
58test('een oppik VERVALT NIET maar veroudert wel zichtbaar', () => {
59 // Barts keuze: niets verdwijnt, want een signaal dat wegvalt laat een
60 // hulpvraag er onaangeroerd uitzien terwijl er iemand mee bezig is. Het scherm
61 // toont in plaats daarvan hoe oud het is.
62 const rijen = [{ kind: 'pickup', guardian_uri: OMA, created_at: '2026-08-05T10:00:00.000Z' }];
63 const s = help.helpStatus(rijen, Date.parse('2026-08-07T10:00:00.000Z'));
64 assert.equal(s.open, true);
65 assert.equal(s.ageMs, 2 * 24 * 3600 * 1000, 'twee dagen oud, en dat hoort te zien te zijn');
66});
67
68test('afhandelen sluit hem, met naam', () => {
69 help.record(NOTE, OPA, 'handled', '@opa@opa.test');
70 const s = help.statusOf(NOTE);
71 assert.equal(s.open, false);
72 assert.equal(s.handled.handle, '@opa@opa.test');
73 assert.equal(s.ageMs, null, 'de leeftijd van een oppik zegt niets meer als het klaar is');
74});
75
76test('er is GEEN terugdraai', () => {
77 // Besluit van Bart: sluiten gebeurt met een stevige bevestiging, en leeft de
78 // vraag daarna nog, dan wordt hij OPNIEUW GESTELD -- een nieuwe hulpvraag. Er
79 // wordt niets herschreven.
80 assert.equal(typeof help.default.undo, 'undefined');
81 assert.equal(typeof help.default.reopen, 'undefined');
82});
83
84test('bij twijfel staat een hulpvraag OPEN', () => {
85 // Alles wat geen expliciete afsluiting is telt als open. Een lege lijst, rommel,
86 // een onbekende soort: allemaal 'er wacht nog iemand'. De omgekeerde fout --
87 // iets als afgehandeld tonen dat het niet is -- is de gevaarlijke.
88 for (const rijen of [[], null, [{ kind: 'iets', guardian_uri: OMA }], [{ kind: 'pickup', guardian_uri: OMA }]]) {
89 assert.equal(help.helpStatus(rijen).open, true);
90 }
91});
92
93test('een markering wordt herkend, en iets anders niet', () => {
94 assert.deepEqual(help.parseMarker({ 'shaer:helpPickup': NOTE }), { kind: 'pickup', noteUri: NOTE });
95 assert.deepEqual(help.parseMarker({ 'shaer:helpHandled': NOTE }), { kind: 'handled', noteUri: NOTE });
96 assert.equal(help.parseMarker({ content: '<p>hoi</p>' }), null);
97 assert.equal(help.parseMarker(null), null);
98 assert.equal(help.parseMarker({ 'shaer:helpPickup': '' }), null, 'leeg is geen markering');
99});
100
101test('afhandelen wint van oppikken in hetzelfde bericht', () => {
102 // Kan niet ontstaan uit onze eigen verzending, maar wel uit een andere
103 // implementatie. Het zwaarste signaal telt.
104 assert.equal(help.parseMarker({ 'shaer:helpPickup': NOTE, 'shaer:helpHandled': NOTE }).kind, 'handled');
105});
106
107test('een lijst hulpvragen kost EEN query', () => {
108 const N2 = 'https://kind.test/ap/notes/hulp-2';
109 help.record(N2, OMA, 'pickup', '@oma@oma.test');
110 const m = help.statusFor([NOTE, N2, 'https://kind.test/ap/notes/leeg']);
111 assert.equal(m.size, 3);
112 assert.equal(m.get(NOTE).open, false);
113 assert.equal(m.get(N2).pickedUpBy.length, 1);
114 assert.equal(m.get('https://kind.test/ap/notes/leeg').open, true, 'onbekend is open');
115});
Note: See TracBrowser for help on using the repository browser.