| 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 |
|
|---|
| 15 | import { test } from 'node:test';
|
|---|
| 16 | import assert from 'node:assert/strict';
|
|---|
| 17 |
|
|---|
| 18 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 19 | process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
|
|---|
| 20 |
|
|---|
| 21 | const dbMod = await import('../src/config/database.js');
|
|---|
| 22 | const db = dbMod.default;
|
|---|
| 23 | dbMod.initializeDatabase();
|
|---|
| 24 | const help = await import('../src/services/guardianship/help.js');
|
|---|
| 25 |
|
|---|
| 26 | const NOTE = 'https://kind.test/ap/notes/hulp-1';
|
|---|
| 27 | const OMA = 'https://oma.test/ap/users/oma';
|
|---|
| 28 | const OPA = 'https://opa.test/ap/users/opa';
|
|---|
| 29 |
|
|---|
| 30 | test('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 |
|
|---|
| 37 | test('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 |
|
|---|
| 47 | test('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 |
|
|---|
| 53 | test('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 |
|
|---|
| 58 | test('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 |
|
|---|
| 68 | test('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 |
|
|---|
| 76 | test('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 |
|
|---|
| 84 | test('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 |
|
|---|
| 93 | test('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 |
|
|---|
| 101 | test('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 |
|
|---|
| 107 | test('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 | });
|
|---|