source: Klonkt/test/own-card.test.js@ c49808e

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

actorInfo leest een url-array: de html-Link, niet de array gestringd

Barts boei-melding (8-8): een hulpvraag van @mee kwam bij @dev binnen met
mention-hrefs vol "[object Object],[object Object]". De keten: onze eigen
buildActor serveert url als ARRAY van Links (profiel + RSS, allebei
legitiem AS2), en een oudere consument op de weg van de ward stringde die
array in de href. De hedendaagse actorInfo hield met safeUrl de rommel
buiten de deur maar liet daarmee ook de profiel-URL vallen; nu kiest hij
via pickLink de html-Link, met de actor-id als terugval -- pickLink was er
nota bene al, met het commentaar "dit is de enige plek die dat weet". Dat
is nu iets minder onwaar.

De test legt het vast via de thread-byline (shaer:author), die door
dezelfde actorInfo loopt. Alle 724 groen; dev bijgewerkt.

De bezorging zelf bleek overigens NIET stuk: de hulpvraag staat gewoon in
dev's ap_mentions (help_request=1). Wat hem onvindbaar maakt is de kudde
-- 101 wards waarvan 100 daemons boven mee in elke lijst -- en dat is
Barts eigen caseload-werkstroom (shaer-6wt), niet deze fix.

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

  • Property mode set to 100644
File size: 6.3 KB
Line 
1// De kaart op je EIGEN post (shaer-k3f): een URL in wat je publiceert wordt
2// een quote- of embed-snapshot, langs dezelfde pijplijn als een binnenkomende
3// post -- en de composer-preview loopt door diezelfde pijplijn, zodat de
4// preview nooit iets belooft dat de post niet waarmaakt.
5//
6// In-memory SQLite, gestubde fetch op TEST-NET-3. Run: npm test
7import { test } from 'node:test';
8import assert from 'node:assert/strict';
9
10process.env.DATABASE_PATH = ':memory:';
11process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
12
13const dbMod = await import('../src/config/database.js');
14const db = dbMod.default;
15dbMod.initializeDatabase();
16const AP = await import('../src/services/ActivityPubService.js');
17
18db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
19 .run('u1', 'u1', 'u1@test', 'x', 'god');
20db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'schrijver', 'Schrijver', 'u1');
21const site = db.prepare('SELECT * FROM sites WHERE id = ?').get('s1');
22
23const EXTERN = 'https://203.0.113.60/artikel';
24const APNOTE = 'https://203.0.113.61/notes/toot-1';
25const APAUTEUR = 'https://203.0.113.61/users/schrijfster';
26
27const OG_PAGINA = `<!doctype html><html><head>
28 <meta property="og:title" content="Een mooi artikel">
29 <meta property="og:image" content="https://203.0.113.60/plaatje.jpg">
30</head><body>tekst</body></html>`;
31
32globalThis.fetch = async (url, opts = {}) => {
33 const u = String(url);
34 const accepts = String((opts.headers && (opts.headers.Accept || opts.headers.accept)) || '');
35 const json = (o) => new Response(JSON.stringify(o), { status: 200, headers: { 'content-type': 'application/activity+json' } });
36 const html = (h) => new Response(h, { status: 200, headers: { 'content-type': 'text/html' } });
37 if (u === EXTERN) return html(OG_PAGINA); // geen AP, wel OpenGraph
38 if (u === APNOTE) {
39 // Een echte fediverse-note: op de AP-vraag AP-json, anders een kale pagina.
40 if (accepts.includes('activity+json')) return json({ id: APNOTE, type: 'Note', attributedTo: APAUTEUR, content: '<p>de toot</p>', published: '2026-08-01T10:00:00Z' });
41 return html('<html><body>toot</body></html>');
42 }
43 if (u === APAUTEUR) return json({ id: APAUTEUR, type: 'Person', preferredUsername: 'schrijfster', name: 'Schrijfster', inbox: `${APAUTEUR}/inbox` });
44 return new Response('not found', { status: 404 });
45};
46
47test('een externe link in een eigen post wordt een embed-snapshot op de post', async () => {
48 // PLATTE tekst, geen anker: zo komt een post uit de app binnen (het
49 // C2S-pad bakt niet voor het opslaan). Op het toestel bleef precies dit
50 // geval een kale link terwijl de web-editor wel een kaart kreeg.
51 db.prepare("INSERT INTO posts (id, site_id, author_id, slug, title, content, status) VALUES ('p1','s1','u1','p1','', ?, 'published')")
52 .run(`<p>Kijk: ${EXTERN}</p>`);
53 const post = db.prepare("SELECT * FROM posts WHERE id = 'p1'").get();
54 await AP.deliverCreate(site, post);
55 const rij = db.prepare("SELECT quote_json, embed_json FROM posts WHERE id = 'p1'").get();
56 assert.ok(rij.embed_json, 'embed-snapshot opgeslagen');
57 const kaart = JSON.parse(rij.embed_json);
58 assert.equal(kaart.title, 'Een mooi artikel');
59 assert.ok(!rij.quote_json, 'geen quote: het is geen fediverse-object');
60});
61
62test('een fediverse-link wordt een quote-snapshot, geen embed', async () => {
63 db.prepare("INSERT INTO posts (id, site_id, author_id, slug, title, content, status) VALUES ('p2','s1','u1','p2','', ?, 'published')")
64 .run(`<p>Dit! <a href="${APNOTE}">${APNOTE}</a></p>`);
65 const post = db.prepare("SELECT * FROM posts WHERE id = 'p2'").get();
66 await AP.deliverCreate(site, post);
67 const rij = db.prepare("SELECT quote_uri, quote_json, embed_json FROM posts WHERE id = 'p2'").get();
68 assert.equal(rij.quote_uri, APNOTE);
69 assert.ok(rij.quote_json, 'quote-snapshot opgeslagen');
70 const kaart = JSON.parse(rij.quote_json);
71 assert.ok(kaart.content.includes('de toot'));
72 assert.equal(kaart.author.name, 'Schrijfster');
73 assert.ok(!rij.embed_json, 'geen embed naast de quote');
74});
75
76test('previewCard geeft voor dezelfde twee links dezelfde twee kaarten', async () => {
77 const extern = await AP.previewCard(EXTERN);
78 assert.ok(extern.embedJson, 'externe link -> embed');
79 assert.equal(JSON.parse(extern.embedJson).title, 'Een mooi artikel');
80 assert.ok(!extern.quoteJson);
81
82 const ap = await AP.previewCard(APNOTE);
83 assert.ok(ap.quoteJson, 'fediverse-link -> quote');
84 assert.equal(JSON.parse(ap.quoteJson).author.handle, '@schrijfster@203.0.113.61');
85 assert.ok(!ap.embedJson);
86
87 // Rommel is geen kaart en geen fout.
88 assert.deepEqual(await AP.previewCard('geen url'), {});
89});
90
91
92test('actorInfo kiest de html-Link uit een url-array (geen object-stringing)', async () => {
93 // Barts vondst (8-8): buildActor serveert `url` als array van Links
94 // (profiel + RSS), en een consument die dat niet kent stringde de array tot
95 // "[object Object],[object Object]" in de mention-href van een HULPVRAAG.
96 // De thread-byline loopt door dezelfde actorInfo, dus die legt het vast.
97 const HOST = 'https://203.0.113.62';
98 const NOTE = `${HOST}/notes/n1`;
99 const AUTEUR = `${HOST}/users/array`;
100 const vorige = globalThis.fetch;
101 globalThis.fetch = async (url) => {
102 const u = String(url);
103 const json = (o) => new Response(JSON.stringify(o), { status: 200, headers: { 'content-type': 'application/activity+json' } });
104 if (u === NOTE) return json({
105 id: NOTE, type: 'Note', attributedTo: AUTEUR, content: '<p>bron</p>',
106 replies: { type: 'Collection', first: { type: 'CollectionPage', items: [
107 { id: `${NOTE}/r1`, type: 'Note', attributedTo: AUTEUR, inReplyTo: NOTE, content: '<p>antwoord</p>', published: '2026-08-02T10:00:00Z' },
108 ] } },
109 });
110 if (u === AUTEUR) return json({
111 id: AUTEUR, type: 'Person', preferredUsername: 'array', inbox: `${AUTEUR}/inbox`,
112 url: [
113 { type: 'Link', href: `${HOST}/`, mediaType: 'text/html' },
114 { type: 'Link', href: `${HOST}/feed.xml`, mediaType: 'application/rss+xml' },
115 ],
116 });
117 return vorige(url);
118 };
119 const uit = await AP.getThread('schrijver', NOTE);
120 globalThis.fetch = vorige;
121 assert.equal(uit.notes.length, 1);
122 const auteur = uit.notes[0]['shaer:author'];
123 assert.equal(auteur.url, `${HOST}/`, 'de html-Link, niet de array gestringd');
124 assert.ok(!String(auteur.url).includes('object'), 'nergens [object Object]');
125});
Note: See TracBrowser for help on using the repository browser.