source: Klonkt/test/mention-inreplyto.test.js@ 76290bf

main
Last change on this file since 76290bf was 76290bf, checked in by Robin <roboburr@…>, 2 weeks ago

Inbox: een binnengekomen antwoord houdt zijn inReplyTo

Robins melding, 26-8: de C2S-lezing serveerde antwoorden zonder ouder.

De oorzaak lag een laag dieper dan de serialisatie: ap_mentions had geen
kolom voor inReplyTo, dus de ouder viel al bij het OPSLAAN op de grond en
messageItem had niets te serveren. Een client kan een gesprek alleen
teruglopen langs inReplyTo, dus elk antwoord kwam aan als het begin van
een gesprek -- de ketenlezing in de app kon nooit verder dan een.

Vier plekken, want het is de hele weg: de kolom (met ensureColumn voor
bestaande databases), het schrijven in de inbox, MESSAGE_COLUMNS -- de
ene leesplek die zowel de inbox-lijst als de gesprekken voedt -- en
messageItem.

De ouder gaat door dezelfde poort als de note-url: alleen http(s), en zowel de
string- als de objectvorm die AS2 toestaat. Alleen de string erkennen
zou hetzelfde gat laten voor wie de objectvorm stuurt.

Nagegaan wat WEL goed ging, zodat de reparatie niet breder wordt dan de
kwaal: replyItem droeg hem al uit ap_interactions.parent_uri, sentItem
via buildNote uit ap_outbox.in_reply_to, en de tijdlijn kan hem per
definitie niet missen -- belongsInTimeline weigert alles met inReplyTo.
Deze leg was de enige.

Bestaande rijen blijven leeg: die ouder is niet meer te achterhalen
zonder hem opnieuw op te halen, en een verzonnen ouder is erger dan
geen. Twee toetsen over de hele keten, tegenbewijs gedraaid: allebei
vallen ze tegen de code van hiervoor. Volle suite 1231 groen.

  • Property mode set to 100644
File size: 3.9 KB
Line 
1// Een ANTWOORD dat als vermelding/bericht binnenkomt hoort zijn ouder te
2// houden (Robins melding, 26-8). Dat deed het niet: ap_mentions had geen
3// kolom voor inReplyTo, dus de ouder viel bij het opslaan op de grond en de
4// C2S-lezing kon hem niet serveren. Een client kan een gesprek alleen
5// teruglopen langs inReplyTo, dus elk antwoord kwam aan als het BEGIN van een
6// gesprek.
7//
8// Getoetst op de keten, niet op een kolom: binnen via handleInbox, eruit via
9// dezelfde leesweg die de app gebruikt.
10import { test } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14process.env.PUBLIC_BASE_URL = 'https://test.example';
15
16const dbMod = await import('../src/config/database.js');
17const db = dbMod.default;
18{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
19const AP = (await import('../src/services/ActivityPubService.js')).default;
20
21db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
22 .run('u1', 'robin', 'u1@t', 'x', 'god');
23db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kid', 'kid', 'u1');
24
25const mij = 'https://test.example/ap/users/kid';
26
27/** Een binnenkomende directe note, zoals de inbox hem van de andere kant krijgt. */
28async function binnen(note) {
29 const act = {
30 '@context': 'https://www.w3.org/ns/activitystreams',
31 id: `${note.id}#create`, type: 'Create', actor: note.attributedTo, object: note,
32 };
33 // preVerified: de handtekening zelf is hier niet het onderwerp, en zonder
34 // netwerk valt er niets op te halen.
35 await AP.handleInbox(
36 { body: act, headers: {}, method: 'POST', originalUrl: '/ap/users/kid/inbox', rawBody: Buffer.from(JSON.stringify(act)) },
37 'kid',
38 { id: note.attributedTo },
39 );
40}
41
42const note = (id, extra = {}) => ({
43 id, type: 'Note', attributedTo: 'https://pruts.nl/ap/users/bart',
44 content: `<p>${id}</p>`, to: [mij],
45 tag: [{ type: 'Mention', href: mij, name: '@kid@test.example' }],
46 published: '2026-08-26T10:00:00Z',
47 ...extra,
48});
49
50test('een binnengekomen antwoord houdt zijn ouder, van opslag tot lezing', async () => {
51 await binnen(note('https://pruts.nl/notes/1'));
52 await binnen(note('https://pruts.nl/notes/2', { inReplyTo: 'https://pruts.nl/notes/1' }));
53
54 const berichten = AP.getDirectMessages('kid', 20);
55 const kop = berichten.find((m) => m.object_uri === 'https://pruts.nl/notes/2');
56 const eerste = berichten.find((m) => m.object_uri === 'https://pruts.nl/notes/1');
57 assert.ok(kop && eerste, 'allebei de berichten kwamen binnen');
58 assert.equal(kop.in_reply_to, 'https://pruts.nl/notes/1', 'het antwoord draagt zijn ouder');
59 assert.equal(eerste.in_reply_to, null, 'en wie niets beantwoordt heeft er geen');
60
61 // Dezelfde rij zoals een gesprek hem opvraagt: een keten teruglopen gebeurt
62 // langs deze weg, niet langs de inbox-lijst.
63 const [uitGesprek] = AP.messageRowsByUri('kid', ['https://pruts.nl/notes/2']);
64 assert.equal(uitGesprek.in_reply_to, 'https://pruts.nl/notes/1', 'ook via de gesprekslezing');
65});
66
67test('de ouder mag alleen een http(s)-adres zijn, in beide AS2-vormen', async () => {
68 // AS2 staat een string of een object toe. Alleen de string erkennen laat
69 // hetzelfde gat open voor iedereen die de objectvorm stuurt.
70 await binnen(note('https://pruts.nl/notes/3', { inReplyTo: { id: 'https://pruts.nl/notes/1', type: 'Note' } }));
71 const [obj] = AP.messageRowsByUri('kid', ['https://pruts.nl/notes/3']);
72 assert.equal(obj.in_reply_to, 'https://pruts.nl/notes/1', 'de objectvorm telt net zo goed');
73
74 // En een vreemde mag langs dit veld geen ander schema binnensmokkelen.
75 await binnen(note('https://pruts.nl/notes/4', { inReplyTo: 'javascript:alert(1)' }));
76 const [vies] = AP.messageRowsByUri('kid', ['https://pruts.nl/notes/4']);
77 assert.equal(vies.in_reply_to, null, 'geen javascript:-adres in de kolom');
78});
Note: See TracBrowser for help on using the repository browser.