source: Klonkt/test/standard-shapes.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.9 KB
Line 
1// Standaardvormen in plaats van eigen dialect (shaer-nmw).
2//
3// Robins waarschuwing: geen Klonkt/Shaer-dialect schrijven waar AS2 of een FEP
4// het al regelt. Deze toetsen zijn het criterium uit die bead -- niet "onze app
5// snapt het", maar: HEEFT EEN GENERIEKE AP-LEZER ER IETS AAN. Daarom kijken ze
6// alleen naar standaardvelden en nooit naar een shaer:-property.
7//
8// In-memory SQLite. Run: npm test
9import { test } from 'node:test';
10import assert from 'node:assert/strict';
11
12process.env.DATABASE_PATH = ':memory:';
13process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
14
15const dbMod = await import('../src/config/database.js');
16dbMod.initializeDatabase();
17const AP = await import('../src/services/ActivityPubService.js');
18
19test('attributedTo draagt de actor als object, zodat elke client een byline heeft', () => {
20 const uit = AP.actorObject('https://elders/u/tante', {
21 name: 'Tante Til', handle: '@til@elders', icon: 'https://elders/til.png',
22 url: 'https://elders/@til', emojis: { ':zwaai:': 'https://elders/zwaai.png' },
23 });
24 assert.equal(uit.id, 'https://elders/u/tante');
25 assert.equal(uit.type, 'Person');
26 assert.equal(uit.name, 'Tante Til');
27 // De LOKALE naam, zoals AS2 hem bedoelt; de handle leidt een lezer af uit
28 // preferredUsername plus de host van de id.
29 assert.equal(uit.preferredUsername, 'til');
30 assert.deepEqual(uit.icon, { type: 'Image', url: 'https://elders/til.png' });
31 // FEP-9098 hoort in tag, niet in een eigen emoji-kaart.
32 assert.equal(uit.tag[0].type, 'Emoji');
33 assert.equal(uit.tag[0].name, ':zwaai:');
34});
35
36test('weten we niets van de persoon, dan blijft attributedTo de kale URI', () => {
37 // Een leeg object zou beweren dat we hem kennen. Een URI is eerlijk en is
38 // bovendien wat elke AP-server verwacht.
39 assert.equal(AP.actorObject('https://elders/u/onbekend', undefined), 'https://elders/u/onbekend');
40 assert.equal(AP.actorObject('https://elders/u/onbekend', {}), 'https://elders/u/onbekend');
41});
42
43test('een linkkaart is een AS2 preview met url, name en image', () => {
44 const embed = JSON.stringify({
45 url: 'https://nieuws.example/artikel', title: 'Een mooi artikel',
46 author: { name: 'De Krant' }, media: [{ url: 'https://nieuws.example/p.jpg' }],
47 });
48 const p = AP.previewObject(embed, { playback: false });
49 assert.equal(p.type, 'Page');
50 assert.equal(p.url, 'https://nieuws.example/artikel');
51 assert.equal(p.name, 'Een mooi artikel');
52 assert.deepEqual(p.image, { type: 'Image', url: 'https://nieuws.example/p.jpg' });
53 assert.equal(p.attributedTo.name, 'De Krant');
54 // De spelerpagina blijft van ons: die hangt aan de guardian-poort en heeft
55 // geen AS2-tegenhanger. Dicht = niet aanwezig.
56 assert.ok(!p['shaer:playerUrl']);
57});
58
59test('een quote is de geciteerde Note zelf (FEP-044f), met ingesloten auteur', () => {
60 const snapshot = JSON.stringify({
61 url: 'https://elders/notes/1',
62 author: { name: 'Til', handle: '@til@elders', icon: 'https://elders/til.png' },
63 content: '<p>hallo</p>', published: '2026-08-01T10:00:00Z',
64 media: [{ url: 'https://elders/foto.jpg', type: 'image/jpeg' }],
65 });
66 const q = AP.quoteObject(snapshot);
67 assert.equal(q.type, 'Note');
68 assert.equal(q.id, 'https://elders/notes/1');
69 assert.equal(q.content, '<p>hallo</p>');
70 assert.equal(q.published, '2026-08-01T10:00:00Z');
71 // De auteur van het citaat is zelf ook een ingesloten actor, niet een
72 // losse naam-kaart.
73 assert.equal(q.attributedTo.type, 'Person');
74 assert.equal(q.attributedTo.name, 'Til');
75 assert.equal(q.attributedTo.preferredUsername, 'til');
76 assert.deepEqual(q.attachment, [{ type: 'Document', mediaType: 'image/jpeg', url: 'https://elders/foto.jpg' }]);
77});
78
79test('niets te tonen levert niets op, geen leeg omhulsel', () => {
80 assert.equal(AP.previewObject(null), undefined);
81 assert.equal(AP.previewObject('{}'), undefined);
82 assert.equal(AP.quoteObject(null), undefined);
83 assert.equal(AP.quoteObject('rommel'), undefined);
84});
85
86// ── En dan het weghalen (shaer-nmw, tweede helft) ────────────────────
87//
88// Bewijzen dat de standaardvorm ERBIJ staat is de helft; de andere helft is dat
89// het dialect er niet meer NAAST staat. Zonder deze wacht sluipt een
90// shaer:author zo weer een nieuwe leg in en spreken we stilletjes weer onze
91// eigen taal. Daarom door de echte route, want daar wordt de vorm beslist.
92test('de C2S-leg spreekt geen dialect meer waar de standaard het regelt', async (t) => {
93 const crypto = await import('crypto');
94 const express = (await import('express')).default;
95 const db = (await import('../src/config/database.js')).default;
96 const routes = (await import('../src/routes/activitypub.js')).default;
97
98 db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
99 .run('u1', 'u1', 'u1@t', 'x', 'god');
100 db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)')
101 .run('s1', 'kind', 'kind', 'u1');
102 db.prepare(`INSERT INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon,
103 reblog_name, reblog_handle, content)
104 VALUES ('https://elders/n1', 'kind', 'https://elders/u/til', 'Tante Til', '@til@elders',
105 'https://elders/til.png', 'Esmee', '@es@elders', '<p>hoi</p>')`).run();
106
107 const bearer = 'test-token-' + 'b'.repeat(24);
108 db.prepare('INSERT INTO oauth_tokens (token_hash, client_id, user_id, site_slug, scope) VALUES (?,?,?,?,?)')
109 .run(crypto.createHash('sha256').update(bearer).digest('base64url'), 'c', 'u1', 'kind', 'read write');
110
111 const app = express();
112 app.use(routes);
113 const server = app.listen(0);
114 t.after(() => server.close());
115 await new Promise((r) => server.once('listening', r));
116 const doc = await (await fetch(`http://127.0.0.1:${server.address().port}/ap/users/kind/inbox`,
117 { headers: { Authorization: `Bearer ${bearer}` } })).json();
118
119 const tekst = JSON.stringify(doc);
120 // Op de SLEUTEL, niet op een stuk tekst: 'shaer:quote' zit ook in
121 // shaer:quoteCards, en dat is een poortnaam die blijft. Een te grove zeef
122 // zou hier rood staan om iets dat klopt -- of erger, groen om iets dat niet
123 // klopt.
124 for (const weg of ['shaer:author', 'shaer:booster', 'shaer:quote', 'shaer:embed']) {
125 assert.ok(!tekst.includes(`"${weg}":`), `${weg} hoort weg te zijn`);
126 }
127 // Wat WEL mag blijven, en waarom: per-lezer-interactiestatus heeft in AS2
128 // geen tegenhanger (Mastodon lost het in zijn eigen REST-API op), en de
129 // FEP-633c-termen zijn bewust van ons en in standaardisatie.
130 assert.ok(tekst.includes('shaer:liked'), 'interactiestatus blijft, die heeft geen standaard');
131
132 const item = doc.orderedItems.find((i) => i.object.id === 'https://elders/n1');
133 assert.equal(item.type, 'Announce', 'een boost is de wrapper, niet een zijkanaal');
134 assert.equal(item.object.attributedTo.name, 'Tante Til', 'en de byline zit in attributedTo');
135 assert.equal(item.object.attributedTo.preferredUsername, 'til');
136});
Note: See TracBrowser for help on using the repository browser.