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

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

De kaart op je eigen post, en de composer-preview (shaer-k3f)

Het bead-uitgangspunt "serverkant is klaar" bleek half waar: AP-quotes
kregen bij publiceren een quote_uri, maar eigen posts hadden geen kolom
voor een kaart-snapshot en de outbox serveerde geen shaer:quote of
shaer:embed -- de app kon een eigen post dus nooit als kaart tonen, ook
na publicatie niet.

Drie stukken, allemaal langs de pijplijn die er al was:

OPSLAAN deliverCreate resolvet de eerste externe link net als de

inbox dat voor binnenkomende posts doet: een fediverse-
object wordt een quote-snapshot (resolveQuoteByUri, de kern
van resolveQuote maar vanaf een kale URI), anders probeert
de link een externe kaart (resolveExternalEmbed). In
posts.quote_json/embed_json, VOOR de vroege return: ook een
post zonder volgers hoort zijn kaart te krijgen, de app
leest hem uit de outbox en niet uit een bezorging.

SERVEREN de friend-poot van de outbox draagt shaer:quote en -- alleen

voor de bearer en langs zijn eigen embeds-poort --
shaer:embed. Een remote vriend krijgt de embed niet: diens
server resolvet en gate zelf bij ontvangst.

PREVIEW GET /ap/users/:slug/card?url= (bearer-only): een URL langs

exact dezelfde pijplijn, zodat de preview in de composer
nooit iets belooft dat de post niet waarmaakt. Zelfde
gate-regels als de tijdlijn.

Drie tests met gestubde fetch: extern -> embed-snapshot, fediverse ->
quote-snapshot (en niet allebei), en de preview die voor dezelfde links
dezelfde kaarten geeft. Alle 723 groen.

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

  • Property mode set to 100644
File size: 4.4 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 db.prepare("INSERT INTO posts (id, site_id, author_id, slug, title, content, status) VALUES ('p1','s1','u1','p1','', ?, 'published')")
49 .run(`<p>Kijk: <a href="${EXTERN}">${EXTERN}</a></p>`);
50 const post = db.prepare("SELECT * FROM posts WHERE id = 'p1'").get();
51 await AP.deliverCreate(site, post);
52 const rij = db.prepare("SELECT quote_json, embed_json FROM posts WHERE id = 'p1'").get();
53 assert.ok(rij.embed_json, 'embed-snapshot opgeslagen');
54 const kaart = JSON.parse(rij.embed_json);
55 assert.equal(kaart.title, 'Een mooi artikel');
56 assert.ok(!rij.quote_json, 'geen quote: het is geen fediverse-object');
57});
58
59test('een fediverse-link wordt een quote-snapshot, geen embed', async () => {
60 db.prepare("INSERT INTO posts (id, site_id, author_id, slug, title, content, status) VALUES ('p2','s1','u1','p2','', ?, 'published')")
61 .run(`<p>Dit! <a href="${APNOTE}">${APNOTE}</a></p>`);
62 const post = db.prepare("SELECT * FROM posts WHERE id = 'p2'").get();
63 await AP.deliverCreate(site, post);
64 const rij = db.prepare("SELECT quote_uri, quote_json, embed_json FROM posts WHERE id = 'p2'").get();
65 assert.equal(rij.quote_uri, APNOTE);
66 assert.ok(rij.quote_json, 'quote-snapshot opgeslagen');
67 const kaart = JSON.parse(rij.quote_json);
68 assert.ok(kaart.content.includes('de toot'));
69 assert.equal(kaart.author.name, 'Schrijfster');
70 assert.ok(!rij.embed_json, 'geen embed naast de quote');
71});
72
73test('previewCard geeft voor dezelfde twee links dezelfde twee kaarten', async () => {
74 const extern = await AP.previewCard(EXTERN);
75 assert.ok(extern.embedJson, 'externe link -> embed');
76 assert.equal(JSON.parse(extern.embedJson).title, 'Een mooi artikel');
77 assert.ok(!extern.quoteJson);
78
79 const ap = await AP.previewCard(APNOTE);
80 assert.ok(ap.quoteJson, 'fediverse-link -> quote');
81 assert.equal(JSON.parse(ap.quoteJson).author.handle, '@schrijfster@203.0.113.61');
82 assert.ok(!ap.embedJson);
83
84 // Rommel is geen kaart en geen fout.
85 assert.deepEqual(await AP.previewCard('geen url'), {});
86});
Note: See TracBrowser for help on using the repository browser.