| 1 | // Een C2S-antwoord houdt ALLE mentions die de client stuurde (Robins melding,
|
|---|
| 2 | // 26-8). Dat deed het niet: de inname gaf deliverReply geen `mentions` mee, en
|
|---|
| 3 | // dat betekent daar "oud gedrag -- noem alleen de auteur van de ouder". Wie er
|
|---|
| 4 | // drie stuurde zag er dus een gepubliceerd worden.
|
|---|
| 5 | //
|
|---|
| 6 | // Getoetst op wat er de deur uit gaat: de Mention-tags op de note, want dat is
|
|---|
| 7 | // wat de ontvangende kant leest.
|
|---|
| 8 | import { test } from 'node:test';
|
|---|
| 9 | import assert from 'node:assert/strict';
|
|---|
| 10 |
|
|---|
| 11 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 12 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 13 |
|
|---|
| 14 | const dbMod = await import('../src/config/database.js');
|
|---|
| 15 | const db = dbMod.default;
|
|---|
| 16 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 17 | const APmod = await import('../src/services/ActivityPubService.js');
|
|---|
| 18 | const AP = APmod.default;
|
|---|
| 19 |
|
|---|
| 20 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 21 | .run('u1', 'robin', 'u1@t', 'x', 'god');
|
|---|
| 22 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kid', 'kid', 'u1');
|
|---|
| 23 | const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get('kid');
|
|---|
| 24 | const user = db.prepare('SELECT * FROM users WHERE id = ?').get('u1');
|
|---|
| 25 |
|
|---|
| 26 | // De ouder waarop geantwoord wordt: een eigen post, zodat er geen netwerk aan
|
|---|
| 27 | // te pas komt. resolveRemoteNote leest die rechtstreeks uit de databank.
|
|---|
| 28 | db.prepare(`INSERT INTO posts (id, site_id, slug, author_id, title, content, excerpt, status, type, language, created_at, updated_at, published_at)
|
|---|
| 29 | VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`)
|
|---|
| 30 | .run('p1', 's1', 'de-post', 'u1', '', '<p>de ouder</p>', '', 'published', 'post', 'nl',
|
|---|
| 31 | '2026-08-26T09:00:00Z', '2026-08-26T09:00:00Z', '2026-08-26T09:00:00Z');
|
|---|
| 32 |
|
|---|
| 33 | const OUDER = 'https://test.example/ap/notes/p1';
|
|---|
| 34 | const BART = 'https://pruts.nl/ap/users/bart';
|
|---|
| 35 | const ESMEE = 'https://boiert.eu/users/esmee';
|
|---|
| 36 |
|
|---|
| 37 | async function antwoord(tags) {
|
|---|
| 38 | const r = await AP.ingestOutboxActivity(site, user, {
|
|---|
| 39 | type: 'Create',
|
|---|
| 40 | object: {
|
|---|
| 41 | type: 'Note',
|
|---|
| 42 | inReplyTo: OUDER,
|
|---|
| 43 | content: '<p>ja, mee eens</p>',
|
|---|
| 44 | source: { content: 'ja, mee eens', mediaType: 'text/plain' },
|
|---|
| 45 | to: [`https://test.example/ap/users/kid/followers`],
|
|---|
| 46 | cc: ['https://www.w3.org/ns/activitystreams#Public'],
|
|---|
| 47 | ...(tags ? { tag: tags } : {}),
|
|---|
| 48 | },
|
|---|
| 49 | });
|
|---|
| 50 | assert.equal(r.status, 201, `het antwoord hoort te landen, kreeg ${r.status} ${r.error || ''}`);
|
|---|
| 51 | const rij = db.prepare('SELECT * FROM ap_outbox WHERE id = ?').get(r.id);
|
|---|
| 52 | return AP.buildNote('https://test.example', site, rij, { isReply: true });
|
|---|
| 53 | }
|
|---|
| 54 |
|
|---|
| 55 | const mentionsVan = (note) => (note.tag || []).filter((t) => t.type === 'Mention').map((t) => t.href).sort();
|
|---|
| 56 |
|
|---|
| 57 | test('drie meegestuurde mentions worden er drie, niet een', async () => {
|
|---|
| 58 | const note = await antwoord([
|
|---|
| 59 | { type: 'Mention', href: AP.actorId('https://test.example', 'kid'), name: '@kid@test.example' },
|
|---|
| 60 | { type: 'Mention', href: BART, name: '@bart@pruts.nl' },
|
|---|
| 61 | { type: 'Mention', href: ESMEE, name: '@esmee@boiert.eu' },
|
|---|
| 62 | ]);
|
|---|
| 63 | assert.deepEqual(mentionsVan(note), [ESMEE, BART, AP.actorId('https://test.example', 'kid')].sort(),
|
|---|
| 64 | 'alle drie de genoemden staan als Mention op de note');
|
|---|
| 65 | });
|
|---|
| 66 |
|
|---|
| 67 | test('een dubbele mention telt een keer, en een raar adres telt niet mee', async () => {
|
|---|
| 68 | const note = await antwoord([
|
|---|
| 69 | { type: 'Mention', href: BART, name: '@bart@pruts.nl' },
|
|---|
| 70 | { type: 'Mention', href: BART, name: '@bart@pruts.nl' }, // dezelfde
|
|---|
| 71 | { type: 'Mention', href: 'javascript:alert(1)', name: '@stout' },
|
|---|
| 72 | { type: 'Hashtag', href: 'https://pruts.nl/tags/muziek', name: '#muziek' },
|
|---|
| 73 | ]);
|
|---|
| 74 | assert.deepEqual(mentionsVan(note), [BART], 'ontdubbeld, en alleen echte Mentions met een http-adres');
|
|---|
| 75 | });
|
|---|
| 76 |
|
|---|
| 77 | test('zonder tags blijft het oude gedrag: de auteur van de ouder', async () => {
|
|---|
| 78 | // Een client die geen tags kent hoort niet stil zonder mentions te eindigen.
|
|---|
| 79 | const note = await antwoord(null);
|
|---|
| 80 | assert.deepEqual(mentionsVan(note), [AP.actorId('https://test.example', 'kid')],
|
|---|
| 81 | 'de ouder-auteur wordt genoemd, zoals altijd');
|
|---|
| 82 | });
|
|---|