source: Klonkt/test/own-card.test.js@ 75f2897

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

Het dialect eruit waar de standaard het al draagt

shaer-nmw, tweede helft. shaer:author, shaer:booster, shaer:quote en
shaer:embed gaan van alle C2S-legs af: outbox-vriendenleg, tijdlijn,
berichten, antwoorden, eigen verzonden notes, de composer-kaart en de thread.
De standaardvormen uit de vorige commit dragen ze nu alleen.

De thread-leg had de standaardvorm nog niet en kreeg hem hier: de byline van
een antwoord zit in de ingesloten attributedTo, en de emoji-poort knipt in de
tag van die actor in plaats van in een eigen emoji-kaart ernaast.

Twee dingen die stil hadden kunnen breken:

  • filterThreadToCircle vergeleek n.attributedTo als string. Nu dat een object is zou hij ALLES wegfilteren: een ward met een lege thread en nergens een foutmelding. Loopt nu door actorUriOf.
  • de composer-kaart las op beide clients alleen de oude velden; die had ik in ronde een gemist. De preview zou leeg zijn gebleven.

Wat blijft: shaer:liked en shaer:boosted (per-lezer-interactiestatus, AS2 heeft
er niets voor) en de FEP-633c-termen, die bewust van ons zijn.

Nieuwe wachten: de C2S-leg door de echte route op afwezig dialect -- op de
SLEUTEL, want 'shaer:quote' zit ook in shaer:quoteCards en dat is een poort die
blijft -- en de thread-route, die tot nu toe geen route-dekking had.

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].attributedTo;
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.