| 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
|
|---|
| 10 | import { test } from 'node:test';
|
|---|
| 11 | import assert from 'node:assert/strict';
|
|---|
| 12 |
|
|---|
| 13 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 14 | process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
|
|---|
| 15 |
|
|---|
| 16 | const dbMod = await import('../src/config/database.js');
|
|---|
| 17 | const db = dbMod.default;
|
|---|
| 18 | dbMod.initializeDatabase();
|
|---|
| 19 | const AP = await import('../src/services/ActivityPubService.js');
|
|---|
| 20 |
|
|---|
| 21 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 22 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 23 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kind', 'Kind', 'u1');
|
|---|
| 24 |
|
|---|
| 25 | const 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.
|
|---|
| 31 | const crypto = await import('crypto');
|
|---|
| 32 | const express = (await import('express')).default;
|
|---|
| 33 | const routes = (await import('../src/routes/activitypub.js')).default;
|
|---|
| 34 | const bearer = 'test-token-' + 'f'.repeat(24);
|
|---|
| 35 | db.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');
|
|---|
| 37 | const app = express();
|
|---|
| 38 | app.use(routes);
|
|---|
| 39 | const server = app.listen(0);
|
|---|
| 40 | await new Promise((r) => server.once('listening', r));
|
|---|
| 41 | const 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.
|
|---|
| 44 | server.unref();
|
|---|
| 45 |
|
|---|
| 46 | const 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 |
|
|---|
| 52 | test('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 |
|
|---|
| 68 | test('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 |
|
|---|
| 79 | test('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 |
|
|---|
| 89 | test('het verschil draagt de rechten mee', async () => {
|
|---|
| 90 | // Zonder dit valt de client terug op zijn standaard, en die standaard is
|
|---|
| 91 | // 'alles mag': een gesloten poort zou zichzelf stil openzetten bij elke
|
|---|
| 92 | // verschil-lezing.
|
|---|
| 93 | const vol = await get('');
|
|---|
| 94 | db.prepare("UPDATE sites SET gate_images = 0 WHERE slug = 'kind'").run();
|
|---|
| 95 | db.prepare("INSERT INTO ap_guardianships (slug, other_uri, role, status) VALUES ('kind', 'https://elders/u/oma', 'guardian', 'accepted')").run();
|
|---|
| 96 | post('https://elders/n/9', '<p>na de poort</p>');
|
|---|
| 97 | const delta = await get(`?changes=1&since=${encodeURIComponent(vol.body['shaer:cursor'])}`);
|
|---|
| 98 | assert.equal(delta.body['shaer:capabilities']['shaer:images'], false, 'de dichte poort reist mee');
|
|---|
| 99 | });
|
|---|
| 100 |
|
|---|
| 101 | test('niets veranderd is een leeg verschil, niet een leeg antwoord', async () => {
|
|---|
| 102 | const vol = await get('');
|
|---|
| 103 | const delta = await get(`?changes=1&since=${encodeURIComponent(vol.body['shaer:cursor'])}`);
|
|---|
| 104 | assert.equal(delta.status, 200);
|
|---|
| 105 | assert.deepEqual(delta.body.orderedItems, []);
|
|---|
| 106 | assert.equal(delta.body['shaer:cursor'], vol.body['shaer:cursor'], 'en de cursor staat stil');
|
|---|
| 107 | });
|
|---|