source: Klonkt/test/feed-changes.test.js@ f0a33e1

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

De wachtende lezing antwoordt met het verschil, als je erom vraagt

shaer-pq4, tweede helft van stap 1. Samentrekken van de twee waiters bespaart
het RONDJE; dit bespaart de VRACHT. Beide paden eindigden tot nu toe in een
volledige inboxlezing -- vier legs van zestig met al hun media-, quote- en
embed-JSON -- voor een enkel nieuw bericht.

Het verschil lag al klaar en werd nooit uitgedeeld: ap_feed_state houdt per
object bij wat er wanneer veranderde, en feedChangesSince had geen enkele
aanroeper. Nu wel, achter ?changes=1.

OPT-IN, en dat is geen franje: een app in het veld stuurt since al mee en
vervangt haar hele feed door wat er terugkomt. Zou since opeens een verschil
betekenen, dan wist die app zichzelf leeg. Een toets bewaakt dat de oude lezing
zonder changes=1 exact hetzelfde blijft.

Het antwoord is een OrderedCollectionPage met partOf, want dat is wat het IS.
Een generieke lezer ziet dat verschil ook.

GRAFSTENEN GAAN MEE. Een verwijdering reisde tot nu toe als AFWEZIGHEID: de
volledige lezing bevatte hem simpelweg niet meer. Juist die volledigheid valt
hier weg, dus zonder Tombstone zou een weggehaalde post voor altijd in de app
blijven staan -- en dat faalt stil. Bart noemde de voorwaarde terecht niet
hard, maar de rijen lagen er al (kind='deleted') en AS2 heeft er een vorm voor.

Om de kaartvorm niet twee keer te beschrijven zijn ook de tijdlijn- en
antwoordvorm uit de handler gehesen (timelineItem/replyItem), net als eerder
messageItem/sentItem. Zelfde gedrag, 855/855 groen.

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

  • Property mode set to 100644
File size: 4.6 KB
Line 
1// Alleen het verschil, als de client erom vraagt (shaer-pq4).
2//
3// De wachtende lezing zei tot nu toe alleen DAT er iets veranderde, waarna de
4// client alles opnieuw las. Deze toetsen leggen drie dingen vast: dat het
5// verschil klopt, dat een verwijdering meereist (want de volledigheid die dat
6// tot nu toe deed valt hier weg), en dat de OUDE lezing niet verandert -- een
7// app in het veld stuurt `since` al mee en zou zichzelf leegwissen.
8//
9// In-memory SQLite. Run: npm test
10import { test } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
15
16const dbMod = await import('../src/config/database.js');
17const db = dbMod.default;
18dbMod.initializeDatabase();
19const AP = await import('../src/services/ActivityPubService.js');
20
21db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
22 .run('u1', 'u1', 'u1@t', 'x', 'god');
23db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kind', 'Kind', 'u1');
24
25const post = (id, tekst) => db.prepare(
26 `INSERT INTO ap_timeline (id, slug, author_uri, author_name, author_handle, content)
27 VALUES (?, 'kind', 'https://elders/u/til', 'Til', '@til@elders', ?)`).run(id, tekst);
28
29// De server leeft over de hele lezing heen: sluiten in een t.after van de
30// EERSTE test zou hem dichttrekken voordat de tweede eraan toe is.
31const crypto = await import('crypto');
32const express = (await import('express')).default;
33const routes = (await import('../src/routes/activitypub.js')).default;
34const bearer = 'test-token-' + 'f'.repeat(24);
35db.prepare('INSERT INTO oauth_tokens (token_hash, client_id, user_id, site_slug, scope) VALUES (?,?,?,?,?)')
36 .run(crypto.createHash('sha256').update(bearer).digest('base64url'), 'c', 'u1', 'kind', 'read write');
37const app = express();
38app.use(routes);
39const server = app.listen(0);
40await new Promise((r) => server.once('listening', r));
41const poort = server.address().port;
42// unref: een luisterende socket houdt de lus open, en dan wacht de testrunner
43// eeuwig op een proces dat nooit vanzelf afloopt.
44server.unref();
45
46const get = async (qs) => {
47 const r = await fetch(`http://127.0.0.1:${poort}/ap/users/kind/inbox${qs}`,
48 { headers: { Authorization: `Bearer ${bearer}` } });
49 return { status: r.status, body: r.status === 304 ? null : await r.json() };
50};
51
52test('het verschil draagt alleen wat er veranderde', async () => {
53 post('https://elders/n/1', '<p>eerste</p>');
54 const vol = await get('');
55 const cursor = vol.body['shaer:cursor'];
56 assert.equal(vol.body.orderedItems.length, 1);
57
58 post('https://elders/n/2', '<p>tweede</p>');
59 const delta = await get(`?changes=1&since=${encodeURIComponent(cursor)}`);
60 assert.equal(delta.body.type, 'OrderedCollectionPage', 'een deel, en dat zegt het ook');
61 assert.ok(delta.body.partOf.endsWith('/inbox'));
62 assert.equal(delta.body.orderedItems.length, 1, 'alleen de nieuwe');
63 assert.equal(delta.body.orderedItems[0].object.id, 'https://elders/n/2');
64 // En de byline staat er gewoon op: dezelfde kaartvorm als de volle lezing.
65 assert.equal(delta.body.orderedItems[0].object.attributedTo.name, 'Til');
66});
67
68test('een verwijdering reist mee als grafsteen', async () => {
69 const heen = await get('');
70 const cursor = heen.body['shaer:cursor'];
71 db.prepare("DELETE FROM ap_timeline WHERE id = ?").run('https://elders/n/1');
72 const delta = await get(`?changes=1&since=${encodeURIComponent(cursor)}`);
73 const weg = delta.body.orderedItems.find((i) => i.type === 'Delete');
74 assert.ok(weg, 'zonder dit zou de post voor altijd in de app blijven staan');
75 assert.equal(weg.object.id, 'https://elders/n/1');
76 assert.equal(weg.object.type, 'Tombstone');
77});
78
79test('zonder changes=1 verandert er NIETS aan de oude lezing', async () => {
80 // De belangrijkste van deze vier: een app in het veld stuurt since al mee en
81 // vervangt haar hele feed door wat er terugkomt.
82 const vol = await get('');
83 const cursor = vol.body['shaer:cursor'];
84 const oud = await get(`?since=${encodeURIComponent(cursor)}`);
85 assert.equal(oud.body.type, 'OrderedCollection', 'nog steeds de hele collectie');
86 assert.equal(oud.body.orderedItems.length, vol.body.orderedItems.length);
87});
88
89test('niets veranderd is een leeg verschil, niet een leeg antwoord', async () => {
90 const vol = await get('');
91 const delta = await get(`?changes=1&since=${encodeURIComponent(vol.body['shaer:cursor'])}`);
92 assert.equal(delta.status, 200);
93 assert.deepEqual(delta.body.orderedItems, []);
94 assert.equal(delta.body['shaer:cursor'], vol.body['shaer:cursor'], 'en de cursor staat stil');
95});
Note: See TracBrowser for help on using the repository browser.