source: Klonkt/test/outbox-gelijk-aan-note.test.js@ 31680c3

main
Last change on this file since 31680c3 was 76bd53c, checked in by Robin <roboburr@…>, 7 hours ago

Outbox serves the same Note as /ap/notes/:id — no more column list

The hub could not find soundfabrics' hashtags. Fetched on its own
(/ap/notes/:id) a post carries #kawaii; read from the outbox, it does not.
outboxSlice loaded posts with a hand-kept column list, and that list fails
silently: a forgotten column is undefined, buildNote decides without it,
and nothing errors. tags was missing — and so were poll_json (a poll left
the outbox as a plain note), quote_uri/quote_actor (a quote post lost its
quote), cover_alt and language.

This was the fourth time. fan_only once sent a friends-only post out as
public; paid once put the full text of a paid post in the outbox. Each fix
added a name to the list and a warning above it. The note route has always
used SELECT *, so the outbox now does the same: the two paths can no longer
drift apart.

The new test compares the WHOLE object, outbox against the single Note,
rather than a handful of fields — a column added later that the outbox
doesn't carry fails it immediately. Verified it goes red on the old code.

Co-Authored-By: Claude Opus 5.5 <noreply@…>

  • Property mode set to 100644
File size: 4.2 KB
RevLine 
[76bd53c]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.
13import { test } from 'node:test';
14import assert from 'node:assert/strict';
15
16process.env.DATABASE_PATH = ':memory:';
17process.env.PUBLIC_BASE_URL = 'https://ons.test';
18
19const dbMod = await import('../src/config/database.js');
20const db = dbMod.default;
21dbMod.initializeDatabase();
22const AP = await import('../src/services/ActivityPubService.js');
23
24const BASE = 'https://ons.test';
25db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
26 .run('u1', 'u1', 'u1@t', 'x', 'god');
27db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
28 .run('s1', 'band', 'De Band', 'u1');
29const 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.
33db.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.
43db.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');
47const los = (id) => AP.buildNote(BASE, site, db.prepare('SELECT * FROM posts WHERE id = ?').get(id));
48
49const 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
56test('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
66test('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});
Note: See TracBrowser for help on using the repository browser.