| 1 | // De outbox en de losse Note zijn HETZELFDE object (Robin, 30-9).
|
|---|
| 2 | //
|
|---|
| 3 | // Aanleiding: de hub vond de hashtags van soundfabrics niet. Haal je een post
|
|---|
| 4 | // los op (/ap/notes/:id) dan draagt hij `#kawaii`; lees je hem uit de outbox,
|
|---|
| 5 | // dan niet. Oorzaak: outboxSlice haalde de posts op met een vaste lijst
|
|---|
| 6 | // kolommen, en die lijst faalt stil -- een vergeten kolom is `undefined`,
|
|---|
| 7 | // buildNote beslist zonder, en er komt geen foutmelding. `tags` ontbrak, maar
|
|---|
| 8 | // ook poll_json, quote_uri, quote_actor, cover_alt en language. Het was de
|
|---|
| 9 | // vierde keer: eerder ging het zo mis met fan_only en met paid.
|
|---|
| 10 | //
|
|---|
| 11 | // Deze toets vergelijkt daarom niet een paar velden maar het HELE object. Een
|
|---|
| 12 | // kolom die er later bijkomt en in de outbox niet meekomt, valt hier meteen op.
|
|---|
| 13 | import { test } from 'node:test';
|
|---|
| 14 | import assert from 'node:assert/strict';
|
|---|
| 15 |
|
|---|
| 16 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 17 | process.env.PUBLIC_BASE_URL = 'https://ons.test';
|
|---|
| 18 |
|
|---|
| 19 | const dbMod = await import('../src/config/database.js');
|
|---|
| 20 | const db = dbMod.default;
|
|---|
| 21 | dbMod.initializeDatabase();
|
|---|
| 22 | const AP = await import('../src/services/ActivityPubService.js');
|
|---|
| 23 |
|
|---|
| 24 | const BASE = 'https://ons.test';
|
|---|
| 25 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 26 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 27 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
|
|---|
| 28 | .run('s1', 'band', 'De Band', 'u1');
|
|---|
| 29 | const site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
|
|---|
| 30 |
|
|---|
| 31 | // Een post die ELK veld gebruikt waar het misging -- en de hashtags staan
|
|---|
| 32 | // bewust niet in de tekst, precies zoals bij soundfabrics.
|
|---|
| 33 | db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at,
|
|---|
| 34 | tags, poll_json, quote_uri, quote_actor, cover_image_url, cover_alt, language)
|
|---|
| 35 | VALUES (?,?,?,?,?,?,'published',?,?,?,?,?,?,?,?)`)
|
|---|
| 36 | .run('p1', 's1', 'u1', 'alles', 'Alles', '<p>geen hashtag in deze zin</p>', '2026-09-30T10:00:00Z',
|
|---|
| 37 | JSON.stringify(['kawaii', 'leuk']),
|
|---|
| 38 | JSON.stringify({ options: [{ name: 'ja' }, { name: 'nee' }], endTime: '2099-01-01T00:00:00Z' }),
|
|---|
| 39 | 'https://elders.test/notes/1', 'https://elders.test/users/iemand',
|
|---|
| 40 | '/media/hoes.jpg', 'Een oranje hoes', 'nl');
|
|---|
| 41 | // Een gewone post met alleen een omslag: bij een citaat of peiling laat
|
|---|
| 42 | // buildNote de omslag bewust weg, dus de alt-tekst toetsen we los.
|
|---|
| 43 | db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at,
|
|---|
| 44 | cover_image_url, cover_alt) VALUES (?,?,?,?,?,?,'published',?,?,?)`)
|
|---|
| 45 | .run('p2', 's1', 'u1', 'hoes', 'Hoes', '<p>een plaatje</p>', '2026-09-29T10:00:00Z',
|
|---|
| 46 | '/media/hoes.jpg', 'Een oranje hoes');
|
|---|
| 47 | const los = (id) => AP.buildNote(BASE, site, db.prepare('SELECT * FROM posts WHERE id = ?').get(id));
|
|---|
| 48 |
|
|---|
| 49 | const uitOutbox = (apId) => {
|
|---|
| 50 | const { posts, tracks } = AP.outboxSlice('s1', {});
|
|---|
| 51 | const ob = AP.buildOutbox(BASE, site, posts, tracks);
|
|---|
| 52 | const create = (ob.orderedItems || []).find((a) => a.object && a.object.id === apId);
|
|---|
| 53 | return create && create.object;
|
|---|
| 54 | };
|
|---|
| 55 |
|
|---|
| 56 | test('de outbox levert hetzelfde object als de losse Note', () => {
|
|---|
| 57 | // Zoals /ap/notes/:id hem ophaalt: SELECT *. Voor elke post, niet een.
|
|---|
| 58 | for (const id of ['p1', 'p2']) {
|
|---|
| 59 | const note = los(id);
|
|---|
| 60 | const uitDeOutbox = uitOutbox(note.id);
|
|---|
| 61 | assert.ok(uitDeOutbox, `${id} staat in de outbox`);
|
|---|
| 62 | assert.deepEqual(uitDeOutbox, note, 'wie een post op twee manieren ophaalt, hoort een bericht te zien');
|
|---|
| 63 | }
|
|---|
| 64 | });
|
|---|
| 65 |
|
|---|
| 66 | test('en de velden waar het misging zitten er echt in', () => {
|
|---|
| 67 | const o = uitOutbox(los('p1').id);
|
|---|
| 68 | const tags = (o.tag || []).filter((t) => t.type === 'Hashtag').map((t) => t.name).sort();
|
|---|
| 69 | assert.deepEqual(tags, ['#kawaii', '#leuk'], 'de hashtags, ook al staan ze niet in de tekst');
|
|---|
| 70 | assert.equal(o.type, 'Question', 'een peiling blijft een peiling');
|
|---|
| 71 | assert.ok(JSON.stringify(o).includes('https://elders.test/notes/1'), 'het citaat reist mee');
|
|---|
| 72 | // Zonder audio is de omslag een BIJLAGE (met alt als `name`); `image` krijgt
|
|---|
| 73 | // hij alleen als de bijlagen onderdrukt worden voor een speler.
|
|---|
| 74 | const o2 = uitOutbox(los('p2').id);
|
|---|
| 75 | const hoes = (o2.attachment || []).find((a) => String(a.url || '').endsWith('/media/hoes.jpg'));
|
|---|
| 76 | assert.equal(hoes && hoes.name, 'Een oranje hoes', 'de omslag houdt zijn alt-tekst');
|
|---|
| 77 | assert.ok(o.contentMap && o.contentMap.nl, 'de taal staat erbij');
|
|---|
| 78 | });
|
|---|