Changeset 3bbf73d in Klonkt
- Timestamp:
- 08/10/2026 02:43:16 PM (4 weeks ago)
- Branches:
- main
- Children:
- f0a33e1
- Parents:
- f549cf5
- Files:
-
- 5 edited
-
src/routes/activitypub.js (modified) (9 diffs)
-
src/services/ActivityPubService.js (modified) (5 diffs)
-
test/conversations.test.js (modified) (7 diffs)
-
test/standard-shapes.test.js (modified) (1 diff)
-
test/thread.test.js (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/activitypub.js
rf549cf5 r3bbf73d 289 289 // dat evenveel kopieen worden -- en een poort die op een van die plekken 290 290 // vergeten wordt, levert stil iets uit dat dicht hoorde te staan. 291 function leesPoorten(site) {291 function gatesFor(site) { 292 292 const isWard = (() => { try { return Guardianship.listGuardians(site.slug).length > 0; } catch { return false; } })(); 293 293 const embeds = Guardianship.externalEmbedsAllowed(site.external_embeds, isWard); … … 314 314 315 315 /** De naam waaronder deze lezer zichzelf herkent in een Mention. */ 316 function eigenHandle(base, slug) {316 function ownHandle(base, slug) { 317 317 try { return `@${slug}@${new URL(base).host}`; } catch { return `@${slug}`; } 318 318 } … … 323 323 // opschrijven is twee vormen die uit de pas kunnen lopen, en dat merk je pas 324 324 // als een kaart ergens anders rendert dan waar je keek. 325 function berichtItem(m, { base, me, myHandle, p }) {325 function messageItem(m, { base, me, myHandle, p }) { 326 326 return { 327 327 id: `${m.object_uri}#create`, … … 359 359 360 360 /** Een eigen verzonden note als AS2-item, zelfde vorm als de inbox-leg. */ 361 function verzondenItem(n, { me, mine }) {361 function sentItem(n, { me, mine }) { 362 362 return { 363 363 id: `${n.id}#create`, … … 383 383 // ophoudt. 384 384 // 385 // Beide lopen langs dezelfde poorten als de inbox-lezing ( leesPoorten) en386 // dezelfde kaartvorm ( berichtItem/verzondenItem). Messages dicht sluit ook385 // Beide lopen langs dezelfde poorten als de inbox-lezing (gatesFor) en 386 // dezelfde kaartvorm (messageItem/sentItem). Messages dicht sluit ook 387 387 // hier vreemden en vrienden, maar nooit het guardian-kanaal en nooit de boei. 388 function gesprekItems(req, auth, refs) {388 function conversationItems(req, auth, refs) { 389 389 const base = baseUrl(req); 390 const P = leesPoorten(auth.site);390 const P = gatesFor(auth.site); 391 391 const me = AP.actorId(base, auth.site.slug); 392 const ctx = { base, me, myHandle: eigenHandle(base, auth.site.slug), p: P };392 const ctx = { base, me, myHandle: ownHandle(base, auth.site.slug), p: P }; 393 393 const mine = AP.selfAuthor(base, auth.site); 394 394 const guardianUris = (() => { try { return new Set(Guardianship.listGuardians(auth.site.slug).map((g) => g.other_uri)); } catch { return new Set(); } })(); 395 395 396 const binnen = new Map(AP.messageRowsByUri(auth.site.slug, refs.filter((r) => r.richting=== 'in').map((r) => r.ref))396 const incoming = new Map(AP.messageRowsByUri(auth.site.slug, refs.filter((r) => r.direction === 'in').map((r) => r.ref)) 397 397 .map((m) => [m.object_uri, m])); 398 const uit= [];398 const items = []; 399 399 for (const r of refs) { 400 if (r. richting=== 'in') {401 const m = binnen.get(r.ref);400 if (r.direction === 'in') { 401 const m = incoming.get(r.ref); 402 402 if (!m) continue; 403 403 if (!(P.messagesAllowed || m.help_request || guardianUris.has(m.actor_uri))) continue; 404 uit.push(berichtItem(m, ctx));404 items.push(messageItem(m, ctx)); 405 405 } else { 406 406 const n = AP.getOutboxNote(base, r.ref); 407 407 // Je eigen woorden blijven van jou: een dichte messages-poort verbergt 408 408 // niet wat je zelf gezegd hebt. 409 if (n) uit.push(verzondenItem(n, { me, mine }));409 if (n) items.push(sentItem(n, { me, mine })); 410 410 } 411 411 } 412 return uit;412 return items; 413 413 } 414 414 … … 416 416 const auth = OAuth.verifyBearer(req.headers.authorization); 417 417 if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end(); 418 const koppen= AP.conversationHeads(auth.site.slug);419 const items = gesprekItems(req, auth, koppen);418 const heads = AP.conversationHeads(auth.site.slug); 419 const items = conversationItems(req, auth, heads); 420 420 AP.sendAP(res, { 421 421 '@context': AP.AP_CONTEXT, … … 433 433 const other = String(req.query.with || ''); 434 434 if (!/^https?:\/\//i.test(other)) return res.status(400).json({ error: 'with must be an actor URI' }); 435 const uit= AP.conversationHistory(auth.site.slug, other, {435 const page = AP.conversationHistory(auth.site.slug, other, { 436 436 before: req.query.before ? String(req.query.before) : null, 437 437 limit: req.query.limit, 438 438 }); 439 const items = gesprekItems(req, auth, uit.rijen);439 const items = conversationItems(req, auth, page.rows); 440 440 // De paginagrootte reist mee in next: vroeg je om 30, dan hoort de volgende 441 441 // pagina er ook 30 te zijn. Zonder dit wordt hij stilletjes de standaard, en 442 442 // dan klopt het ritme van een 'load more' niet meer met wat de gebruiker ziet. 443 const maat= req.query.limit ? `&limit=${encodeURIComponent(String(req.query.limit))}` : '';444 const zelf = `${baseUrl(req)}/ap/users/${encodeURIComponent(auth.site.slug)}/messages?with=${encodeURIComponent(other)}`;443 const size = req.query.limit ? `&limit=${encodeURIComponent(String(req.query.limit))}` : ''; 444 const self = `${baseUrl(req)}/ap/users/${encodeURIComponent(auth.site.slug)}/messages?with=${encodeURIComponent(other)}`; 445 445 AP.sendAP(res, { 446 446 '@context': AP.AP_CONTEXT, 447 id: req.query.before ? `${ zelf}${maat}&before=${encodeURIComponent(String(req.query.before))}` : `${zelf}${maat}`,447 id: req.query.before ? `${self}${size}&before=${encodeURIComponent(String(req.query.before))}` : `${self}${size}`, 448 448 type: 'OrderedCollectionPage', 449 partOf: zelf,449 partOf: self, 450 450 orderedItems: items, 451 451 // De volgende pagina is de standaardvorm van 'er is meer' (AS2). Ontbreekt 452 452 // hij, dan is het gesprek op -- en dat mag de client weten zonder gokken, 453 453 // want anders kan een 'load more' niet eerlijk verschijnen. 454 next: uit.meer && uit.oudste ? `${zelf}${maat}&before=${encodeURIComponent(uit.oudste)}` : undefined,454 next: page.more && page.oldest ? `${self}${size}&before=${encodeURIComponent(page.oldest)}` : undefined, 455 455 }, 'private, no-store'); 456 456 }); … … 587 587 // here, at serialisation: a blocked embed is never sent, because an embed the 588 588 // client merely hides has still been delivered to the device. 589 // De poorten van deze lezer ( leesPoorten): een plek waar ze berekend worden,589 // De poorten van deze lezer (gatesFor): een plek waar ze berekend worden, 590 590 // zodat de gesprekslezingen dezelfde stand eerbiedigen en niet hun eigen 591 591 // kopie krijgen die kan gaan afwijken. 592 const P = leesPoorten(auth.site);592 const P = gatesFor(auth.site); 593 593 const { 594 594 embedsAllowed, playbackAllowed, imagesAllowed, musicAllowed, quotesAllowed, … … 676 676 // beschermt niemand. De hulpvraag zelf gaat aan de innamekant al altijd voor. 677 677 const guardianUris = (() => { try { return new Set(Guardianship.listGuardians(auth.site.slug).map((g) => g.other_uri)); } catch { return new Set(); } })(); 678 const berichtCtx = { base, me, myHandle, p: P };678 const messageCtx = { base, me, myHandle, p: P }; 679 679 const messages = AP.getDirectMessages(auth.site.slug, 60) 680 680 .filter((m) => messagesAllowed || m.help_request || guardianUris.has(m.actor_uri)) 681 .map((m) => berichtItem(m, berichtCtx));681 .map((m) => messageItem(m, messageCtx)); 682 682 // Inbound REPLIES on your own posts: stored as interactions (the web's 683 683 // comment machinery), never as mentions, so this read missed them and a -
src/services/ActivityPubService.js
rf549cf5 r3bbf73d 4210 4210 // visibility 'direct'. Een publiek antwoord is geen gesprek en hoort niet als 4211 4211 // gezicht in de hemel. 4212 const GESPREK_UNIE= `4212 const CONVERSATION_UNION = ` 4213 4213 SELECT m.actor_uri AS other, COALESCE(m.published, m.created_at) AS stamp, 4214 'in' AS richting, m.object_uri AS ref4214 'in' AS direction, m.object_uri AS ref 4215 4215 FROM ap_mentions m 4216 4216 WHERE m.slug = @slug AND m.actor_uri IS NOT NULL AND m.actor_uri <> '' 4217 4217 UNION ALL 4218 4218 SELECT j.value AS other, o.created_at AS stamp, 4219 ' uit' AS richting, o.id AS ref4219 'out' AS direction, o.id AS ref 4220 4220 FROM ap_outbox o 4221 4221 JOIN json_each(COALESCE(NULLIF(o.to_actors, ''), json_array(o.to_actor))) j … … 4233 4233 try { 4234 4234 return db.prepare(` 4235 SELECT other, stamp, richting, ref FROM (4235 SELECT other, stamp, direction, ref FROM ( 4236 4236 SELECT *, ROW_NUMBER() OVER (PARTITION BY other ORDER BY stamp DESC, ref DESC) AS rn 4237 FROM (${ GESPREK_UNIE})4237 FROM (${CONVERSATION_UNION}) 4238 4238 ) WHERE rn = 1 4239 4239 ORDER BY stamp DESC, ref DESC`).all({ slug }); … … 4260 4260 * ontbreekt alleen iets in het midden. 4261 4261 */ 4262 const cursor Van= (r) => (r ? `${r.stamp}|${r.ref}` : null);4262 const cursorOf = (r) => (r ? `${r.stamp}|${r.ref}` : null); 4263 4263 4264 4264 export function conversationHistory(slug, other, { before = null, limit = 60 } = {}) { 4265 4265 try { 4266 4266 const n = Math.min(Math.max(parseInt(limit, 10) || 60, 1), 200); 4267 const knip = String(before || '').indexOf('|');4268 const bStamp = before && knip > 0 ? String(before).slice(0, knip) : null;4269 const bRef = before && knip > 0 ? String(before).slice(knip + 1) : null;4270 const r ijen= db.prepare(`4271 SELECT other, stamp, richting, ref FROM (${GESPREK_UNIE})4267 const sep = String(before || '').indexOf('|'); 4268 const bStamp = before && sep > 0 ? String(before).slice(0, sep) : null; 4269 const bRef = before && sep > 0 ? String(before).slice(sep + 1) : null; 4270 const rows = db.prepare(` 4271 SELECT other, stamp, direction, ref FROM (${CONVERSATION_UNION}) 4272 4272 WHERE other = @other 4273 4273 AND (@bStamp IS NULL OR stamp < @bStamp OR (stamp = @bStamp AND ref < @bRef)) 4274 4274 ORDER BY stamp DESC, ref DESC LIMIT @n`).all({ slug, other, bStamp, bRef, n: n + 1 }); 4275 const m eer = rijen.length > n;4276 const uit = meer ? rijen.slice(0, n) : rijen;4277 return { r ijen: uit, meer, oudste: cursorVan(uit[uit.length - 1]) };4278 } catch { return { r ijen: [], meer: false, oudste: null }; }4275 const more = rows.length > n; 4276 const page = more ? rows.slice(0, n) : rows; 4277 return { rows: page, more, oldest: cursorOf(page[page.length - 1]) }; 4278 } catch { return { rows: [], more: false, oldest: null }; } 4279 4279 } 4280 4280 4281 4281 // De kolommen die een bericht tot kaart maken. Een constante, want de 4282 // gesprekslezing haalt dezelfde r ijenop: twee lijsten die uiteenlopen leveren4282 // gesprekslezing haalt dezelfde rows op: twee lijsten die uiteenlopen leveren 4283 4283 // een kaart die op de ene plek een plaatje heeft en op de andere niet. 4284 const BERICHT_KOLOMMEN= `4284 const MESSAGE_COLUMNS = ` 4285 4285 m.object_uri, m.note_url, m.actor_uri, m.actor_name, m.actor_handle, m.actor_icon, m.actor_url, 4286 4286 m.content, m.published, m.created_at, m.wave, m.help_request, … … 4293 4293 try { 4294 4294 const gaten = lijst.map(() => '?').join(','); 4295 return db.prepare(`SELECT ${ BERICHT_KOLOMMEN} FROM ap_mentions m4295 return db.prepare(`SELECT ${MESSAGE_COLUMNS} FROM ap_mentions m 4296 4296 WHERE m.slug = ? AND m.object_uri IN (${gaten})`).all(slug, ...lijst); 4297 4297 } catch { return []; } … … 4301 4301 try { 4302 4302 return db.prepare(` 4303 SELECT ${ BERICHT_KOLOMMEN}4303 SELECT ${MESSAGE_COLUMNS} 4304 4304 FROM ap_mentions m 4305 4305 WHERE m.slug = ? -
test/conversations.test.js
rf549cf5 r3bbf73d 26 26 const VREEMDE = 'https://elders/u/vreemde'; 27 27 28 const binnen= (uri, van, stamp) =>28 const arrived = (uri, van, stamp) => 29 29 db.prepare(`INSERT INTO ap_mentions (slug, object_uri, actor_uri, actor_name, content, published) 30 30 VALUES ('kind', ?, ?, 'iemand', '<p>hoi</p>', ?)`).run(uri, van, stamp); 31 const verstuurd= (id, naar, stamp, visibility = 'direct') =>31 const sent = (id, naar, stamp, visibility = 'direct') => 32 32 db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, to_actor, to_actors, content, visibility, created_at) 33 33 VALUES (?, 'kind', 'p1', ?, ?, '<p>terug</p>', ?, ?)`) … … 35 35 36 36 // Een DRUK gesprek met oma: genoeg om in de oude lezing de rest weg te drukken. 37 for (let i = 0; i < 80; i++) binnen(`https://elders/n/oma-${i}`, OMA, `2026-08-09T10:${String(i % 60).padStart(2, '0')}:00Z`);37 for (let i = 0; i < 80; i++) arrived(`https://elders/n/oma-${i}`, OMA, `2026-08-09T10:${String(i % 60).padStart(2, '0')}:00Z`); 38 38 // En een stil gesprek met tante, ouder dan al die drukte. 39 binnen('https://elders/n/tante-1', TANTE, '2026-08-01T09:00:00Z');40 verstuurd('mijn-1', TANTE, '2026-08-01T09:30:00Z');39 arrived('https://elders/n/tante-1', TANTE, '2026-08-01T09:00:00Z'); 40 sent('mijn-1', TANTE, '2026-08-01T09:30:00Z'); 41 41 // Een PUBLIEK antwoord aan een vreemde is geen gesprek. 42 verstuurd('mijn-publiek', VREEMDE, '2026-08-09T12:00:00Z', 'public');42 sent('mijn-publiek', VREEMDE, '2026-08-09T12:00:00Z', 'public'); 43 43 44 44 test('de oude lezing verliest tante achter een druk gesprek -- dat is de bug', () => { … … 49 49 50 50 test('een rij per tegenpartij, hoe druk de drukste ook is', () => { 51 const koppen= AP.conversationHeads('kind');52 assert.deepEqual( koppen.map((k) => k.other), [OMA, TANTE], 'nieuwste gesprek eerst, allebei aanwezig');51 const heads = AP.conversationHeads('kind'); 52 assert.deepEqual(heads.map((k) => k.other), [OMA, TANTE], 'nieuwste gesprek eerst, allebei aanwezig'); 53 53 }); 54 54 … … 58 58 59 59 test('het gesprek draagt beide kanten onder EEN limiet', () => { 60 const g= AP.conversationHistory('kind', TANTE, { limit: 60 });61 assert.equal( g.rijen.length, 2);62 assert.deepEqual( g.rijen.map((r) => r.richting), ['uit', 'in'], 'nieuwste eerst, mijn antwoord bovenaan');63 assert.equal( g.meer, false);60 const talk = AP.conversationHistory('kind', TANTE, { limit: 60 }); 61 assert.equal(talk.rows.length, 2); 62 assert.deepEqual(talk.rows.map((r) => r.direction), ['out', 'in'], 'nieuwste eerst, mijn antwoord bovenaan'); 63 assert.equal(talk.more, false); 64 64 }); 65 65 … … 67 67 // De 80 berichten van oma delen stempels (i % 60), en dat is met opzet: 68 68 // twee berichten in dezelfde seconde is bij DM's een gesprek, geen randgeval. 69 const eerste= AP.conversationHistory('kind', OMA, { limit: 30 });70 assert.equal( eerste.rijen.length, 30);71 assert.equal( eerste.meer, true, 'er is meer, en dat mag de client weten');69 const first = AP.conversationHistory('kind', OMA, { limit: 30 }); 70 assert.equal(first.rows.length, 30); 71 assert.equal(first.more, true, 'er is meer, en dat mag de client weten'); 72 72 73 const tweede = AP.conversationHistory('kind', OMA, { limit: 30, before: eerste.oudste});74 assert.equal( tweede.rijen.length, 30);75 // Geen overlap en geen gat: de tweede pagina begint waar de eersteophield.76 const ids = new Set( eerste.rijen.map((r) => r.ref));77 assert.ok( tweede.rijen.every((r) => !ids.has(r.ref)), 'geen dubbele');73 const second = AP.conversationHistory('kind', OMA, { limit: 30, before: first.oldest }); 74 assert.equal(second.rows.length, 30); 75 // Geen overlap en geen gat: de second pagina begint waar de first ophield. 76 const ids = new Set(first.rows.map((r) => r.ref)); 77 assert.ok(second.rows.every((r) => !ids.has(r.ref)), 'geen dubbele'); 78 78 79 const derde = AP.conversationHistory('kind', OMA, { limit: 30, before: tweede.oudste});80 assert.equal( derde.rijen.length, 20, 'de staart');81 assert.equal( derde.meer, false, 'en dan is het op');79 const third = AP.conversationHistory('kind', OMA, { limit: 30, before: second.oldest }); 80 assert.equal(third.rows.length, 20, 'de staart'); 81 assert.equal(third.more, false, 'en dan is het op'); 82 82 // Alles bij elkaar: precies 80, geen dubbele en niets overgeslagen. Met een 83 83 // cursor op alleen de stempel zou de grensseconde hier stil wegvallen. 84 const all es = new Set([...eerste.rijen, ...tweede.rijen, ...derde.rijen].map((r) => r.ref));85 assert.equal(all es.size, 80, 'elke berichtje precies een keer');84 const allRefs = new Set([...first.rows, ...second.rows, ...third.rows].map((r) => r.ref)); 85 assert.equal(allRefs.size, 80, 'elke berichtje precies een keer'); 86 86 }); 87 87 … … 101 101 t.after(() => server.close()); 102 102 await new Promise((r) => server.once('listening', r)); 103 const haal= async (pad) => {103 const get = async (pad) => { 104 104 const r = await fetch(`http://127.0.0.1:${server.address().port}${pad}`, 105 105 { headers: { Authorization: `Bearer ${bearer}` } }); … … 108 108 const slug = '/ap/users/kind'; 109 109 110 const g = await haal(`${slug}/conversations`);111 assert.equal( g.status, 200);112 assert.equal( g.body.totalItems, 2, 'oma en tante, en niet de publieke vreemde');110 const list = await get(`${slug}/conversations`); 111 assert.equal(list.status, 200); 112 assert.equal(list.body.totalItems, 2, 'oma en tante, en niet de publieke vreemde'); 113 113 // De byline zit in de ingesloten actor, net als overal sinds shaer-nmw. 114 assert.ok( g.body.orderedItems.every((i) => i.object.attributedTo));114 assert.ok(list.body.orderedItems.every((i) => i.object.attributedTo)); 115 115 116 const eerste = await haal(`${slug}/messages?with=${encodeURIComponent(OMA)}&limit=30`);117 assert.equal( eerste.body.orderedItems.length, 30);118 assert.ok( eerste.body.next, 'er is meer, en de standaardvorm zegt het');119 assert.ok( eerste.body.next.includes('limit=30'), 'de paginagrootte reist mee');116 const first = await get(`${slug}/messages?with=${encodeURIComponent(OMA)}&limit=30`); 117 assert.equal(first.body.orderedItems.length, 30); 118 assert.ok(first.body.next, 'er is meer, en de standaardvorm zegt het'); 119 assert.ok(first.body.next.includes('limit=30'), 'de paginagrootte reist mee'); 120 120 121 121 // De next-link volgen doet wat hij belooft: geen dubbele, en uiteindelijk op. 122 const pa d2 = eerste.body.next.replace(/^https?:\/\/[^/]+/, '');123 const tweede = await haal(pad2);124 const eersteIds = new Set(eerste.body.orderedItems.map((i) => i.object.id));125 assert.ok( tweede.body.orderedItems.every((i) => !eersteIds.has(i.object.id)), 'geen overlap');126 const derde = await haal(tweede.body.next.replace(/^https?:\/\/[^/]+/, ''));127 assert.equal( derde.body.orderedItems.length, 20);128 assert.equal( derde.body.next, undefined, 'op is op, en dat staat er ook');122 const path2 = first.body.next.replace(/^https?:\/\/[^/]+/, ''); 123 const second = await get(path2); 124 const firstIds = new Set(first.body.orderedItems.map((i) => i.object.id)); 125 assert.ok(second.body.orderedItems.every((i) => !firstIds.has(i.object.id)), 'geen overlap'); 126 const third = await get(second.body.next.replace(/^https?:\/\/[^/]+/, '')); 127 assert.equal(third.body.orderedItems.length, 20); 128 assert.equal(third.body.next, undefined, 'op is op, en dat staat er ook'); 129 129 130 130 // Een gesprek draagt beide kanten; het eigen antwoord aan tante hoort erbij. 131 const tante = await haal(`${slug}/messages?with=${encodeURIComponent(TANTE)}`);132 assert.equal( tante.body.orderedItems.length, 2);133 assert.ok( tante.body.orderedItems.some((i) => i.actor === 'https://klonkt.test/ap/users/kind'), 'mijn eigen kant zit erin');131 const withAunt = await get(`${slug}/messages?with=${encodeURIComponent(TANTE)}`); 132 assert.equal(withAunt.body.orderedItems.length, 2); 133 assert.ok(withAunt.body.orderedItems.some((i) => i.actor === 'https://klonkt.test/ap/users/kind'), 'mijn eigen kant zit erin'); 134 134 135 const stuk = await haal(`${slug}/messages?with=nonsens`);136 assert.equal( stuk.status, 400, 'geen actor-uri, geen gesprek');135 const broken = await get(`${slug}/messages?with=nonsens`); 136 assert.equal(broken.status, 400, 'geen actor-uri, geen gesprek'); 137 137 }); -
test/standard-shapes.test.js
rf549cf5 r3bbf73d 117 117 { headers: { Authorization: `Bearer ${bearer}` } })).json(); 118 118 119 const te kst = JSON.stringify(doc);120 // Op de SLEUTEL, niet op een stuk te kst: 'shaer:quote' zit ook in119 const text = JSON.stringify(doc); 120 // Op de SLEUTEL, niet op een stuk text: 'shaer:quote' zit ook in 121 121 // shaer:quoteCards, en dat is een poortnaam die blijft. Een te grove zeef 122 122 // zou hier rood staan om iets dat klopt -- of erger, groen om iets dat niet 123 123 // klopt. 124 for (const wegof ['shaer:author', 'shaer:booster', 'shaer:quote', 'shaer:embed']) {125 assert.ok(!te kst.includes(`"${weg}":`), `${weg} hoort weg te zijn`);124 for (const gone of ['shaer:author', 'shaer:booster', 'shaer:quote', 'shaer:embed']) { 125 assert.ok(!text.includes(`"${gone}":`), `${gone} hoort weg te zijn`); 126 126 } 127 127 // Wat WEL mag blijven, en waarom: per-lezer-interactiestatus heeft in AS2 128 128 // geen tegenhanger (Mastodon lost het in zijn eigen REST-API op), en de 129 129 // FEP-633c-termen zijn bewust van ons en in standaardisatie. 130 assert.ok(te kst.includes('shaer:liked'), 'interactiestatus blijft, die heeft geen standaard');130 assert.ok(text.includes('shaer:liked'), 'interactiestatus blijft, die heeft geen standaard'); 131 131 132 132 const item = doc.orderedItems.find((i) => i.object.id === 'https://elders/n1'); -
test/thread.test.js
rf549cf5 r3bbf73d 55 55 // een ECHT verzoek aan een testserver op localhost, en dat mag de stub niet 56 56 // beantwoorden met zijn 404. 57 const echteFetch = globalThis.fetch;57 const realFetch = globalThis.fetch; 58 58 59 59 globalThis.fetch = async (url) => { … … 171 171 t.after(() => server.close()); 172 172 await new Promise((r) => server.once('listening', r)); 173 const haal = async () => (await (await echteFetch(173 const get = async () => (await (await realFetch( 174 174 `http://127.0.0.1:${server.address().port}/ap/users/kind/thread?object=${encodeURIComponent(BRON)}`, 175 175 { headers: { Authorization: `Bearer ${bearer}` } })).json()); 176 176 177 const doc = await haal();177 const doc = await get(); 178 178 assert.ok(!JSON.stringify(doc).includes('"shaer:author":'), 'geen dialect meer op deze leg'); 179 const vanTante= doc.orderedItems.find((n) => n.id.endsWith('/replies/2'));180 assert.equal( vanTante.attributedTo.name, 'tante', 'de byline zit in attributedTo');181 assert.equal( vanTante.attributedTo.preferredUsername, 'tante');182 assert.ok(( vanTante.tag || []).some((x) => x.type === 'Emoji'), 'open poort: de emoji van het antwoord blijft');179 const fromAunt = doc.orderedItems.find((n) => n.id.endsWith('/replies/2')); 180 assert.equal(fromAunt.attributedTo.name, 'tante', 'de byline zit in attributedTo'); 181 assert.equal(fromAunt.attributedTo.preferredUsername, 'tante'); 182 assert.ok((fromAunt.tag || []).some((x) => x.type === 'Emoji'), 'open poort: de emoji van het antwoord blijft'); 183 183 184 184 // Poort dicht: FEP-9098 zit in de tag van de ingesloten actor, dus daar … … 188 188 db.prepare("INSERT INTO ap_guardianships (slug, other_uri, role, status) VALUES ('kind', ?, 'guardian', 'accepted')") 189 189 .run('https://203.0.113.20/users/tante'); 190 const dicht = await haal();191 const na = dicht.orderedItems.find((n) => n.id.endsWith('/replies/2'));192 assert.ok(!( na.tag || []).some((x) => x.type === 'Emoji'), 'dichte poort: geen emoji meer');193 assert.ok(! na.attributedTo.tag, 'ook niet in de byline');190 const closed = await get(); 191 const after = closed.orderedItems.find((n) => n.id.endsWith('/replies/2')); 192 assert.ok(!(after.tag || []).some((x) => x.type === 'Emoji'), 'dichte poort: geen emoji meer'); 193 assert.ok(!after.attributedTo.tag, 'ook niet in de byline'); 194 194 }); 195 195 … … 217 217 const vorige = globalThis.fetch; 218 218 t.after(() => { globalThis.fetch = vorige; }); 219 const vraag= async (uri) => {220 const r = await echteFetch(219 const ask = async (uri) => { 220 const r = await realFetch( 221 221 `http://127.0.0.1:${server.address().port}/ap/users/kind/thread?object=${encodeURIComponent(uri)}`, 222 222 { headers: { Authorization: `Bearer ${bearer}` } }); … … 226 226 // De bron zegt nee -- precies wat boiert.eu doet met een friends-only post 227 227 // voor iemand die de auteur niet (meer) volgt. 228 const DICHT = 'https://203.0.113.50/notes/dicht';228 const SHUT = 'https://203.0.113.50/notes/dicht'; 229 229 globalThis.fetch = async () => new Response('nope', { status: 404 }); 230 const nee = await vraag(DICHT);231 assert.equal( nee.status, 404);232 assert.equal( nee.body.error, 'not shared by source');233 assert.equal( nee.body.sourceStatus, 404);230 const refused = await ask(SHUT); 231 assert.equal(refused.status, 404); 232 assert.equal(refused.body.error, 'not shared by source'); 233 assert.equal(refused.body.sourceStatus, 404); 234 234 235 235 // De bron ligt eruit. Dezelfde lege uitkomst, een andere waarheid. 236 const STUK= 'https://203.0.113.51/notes/stuk';236 const BROKEN = 'https://203.0.113.51/notes/stuk'; 237 237 globalThis.fetch = async () => new Response('boem', { status: 500 }); 238 const stuk = await vraag(STUK);239 assert.equal( stuk.status, 502);240 assert.equal( stuk.body.error, 'source unreachable');241 }); 238 const broken = await ask(BROKEN); 239 assert.equal(broken.status, 502); 240 assert.equal(broken.body.error, 'source unreachable'); 241 });
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)