source: Klonkt/test/conversations.test.js@ 3bbf73d

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

Alleen namen: het Nederlands van vandaag terug naar Engels

shaer-8ms zegt het al: Engelse namen, Nederlands commentaar waar dat helpt. Ik
ben er vandaag opnieuw van afgedreven en zet dat recht voordat er meer bovenop
komt -- gatesFor, messageItem, sentItem, conversationItems, ownHandle,
CONVERSATION_UNION, MESSAGE_COLUMNS, cursorOf, en de locals eromheen
(rows/more/oldest/direction/incoming/heads/self/size).

Geen gedrag, alleen namen, en daarom een eigen commit: die is na te lopen.
851/851 groen voor en na. Het commentaar blijft Nederlands.

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

  • Property mode set to 100644
File size: 6.9 KB
Line 
1// Gesprekken: eerst wie, dan pas wat (shaer-frontend-yso).
2//
3// De oude lezing gaf 60 berichten over ALLE gesprekken samen. Deze toetsen
4// leggen vast wat daaraan mis was en niet meer mag terugkomen: dat een druk
5// gesprek de rest wegdrukt, dat iemand daardoor uit de hemel verdwijnt, en dat
6// jouw kant en hun kant apart afgekapt werden.
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');
16const db = dbMod.default;
17dbMod.initializeDatabase();
18const AP = await import('../src/services/ActivityPubService.js');
19
20db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
21 .run('u1', 'u1', 'u1@t', 'x', 'god');
22db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'kind', 'Kind', 'u1');
23
24const TANTE = 'https://elders/u/tante';
25const OMA = 'https://elders/u/oma';
26const VREEMDE = 'https://elders/u/vreemde';
27
28const arrived = (uri, van, stamp) =>
29 db.prepare(`INSERT INTO ap_mentions (slug, object_uri, actor_uri, actor_name, content, published)
30 VALUES ('kind', ?, ?, 'iemand', '<p>hoi</p>', ?)`).run(uri, van, stamp);
31const sent = (id, naar, stamp, visibility = 'direct') =>
32 db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, to_actor, to_actors, content, visibility, created_at)
33 VALUES (?, 'kind', 'p1', ?, ?, '<p>terug</p>', ?, ?)`)
34 .run(id, naar, JSON.stringify([naar]), visibility, stamp);
35
36// Een DRUK gesprek met oma: genoeg om in de oude lezing de rest weg te drukken.
37for (let i = 0; i < 80; i++) arrived(`https://elders/n/oma-${i}`, OMA, `2026-08-09T10:${String(i % 60).padStart(2, '0')}:00Z`);
38// En een stil gesprek met tante, ouder dan al die drukte.
39arrived('https://elders/n/tante-1', TANTE, '2026-08-01T09:00:00Z');
40sent('mijn-1', TANTE, '2026-08-01T09:30:00Z');
41// Een PUBLIEK antwoord aan een vreemde is geen gesprek.
42sent('mijn-publiek', VREEMDE, '2026-08-09T12:00:00Z', 'public');
43
44test('de oude lezing verliest tante achter een druk gesprek -- dat is de bug', () => {
45 const oud = AP.getDirectMessages('kind', 60);
46 assert.equal(oud.length, 60);
47 assert.ok(!oud.some((m) => m.actor_uri === TANTE), 'tante valt buiten de 60');
48});
49
50test('een rij per tegenpartij, hoe druk de drukste ook is', () => {
51 const heads = AP.conversationHeads('kind');
52 assert.deepEqual(heads.map((k) => k.other), [OMA, TANTE], 'nieuwste gesprek eerst, allebei aanwezig');
53});
54
55test('een publiek antwoord is geen gesprek en wordt geen gezicht', () => {
56 assert.ok(!AP.conversationHeads('kind').some((k) => k.other === VREEMDE));
57});
58
59test('het gesprek draagt beide kanten onder EEN limiet', () => {
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});
65
66test('load more: er is een cursor en een eerlijk antwoord op "is er meer"', () => {
67 // De 80 berichten van oma delen stempels (i % 60), en dat is met opzet:
68 // twee berichten in dezelfde seconde is bij DM's een gesprek, geen randgeval.
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
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
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 // Alles bij elkaar: precies 80, geen dubbele en niets overgeslagen. Met een
83 // cursor op alleen de stempel zou de grensseconde hier stil wegvallen.
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});
87
88// ── Door de routes heen, want daar wordt de vorm beslist ────────────
89test('de gesprekslezingen: een rij per persoon, en een gesprek met next', async (t) => {
90 const crypto = await import('crypto');
91 const express = (await import('express')).default;
92 const routes = (await import('../src/routes/activitypub.js')).default;
93
94 const bearer = 'test-token-' + 'e'.repeat(24);
95 db.prepare('INSERT INTO oauth_tokens (token_hash, client_id, user_id, site_slug, scope) VALUES (?,?,?,?,?)')
96 .run(crypto.createHash('sha256').update(bearer).digest('base64url'), 'c', 'u1', 'kind', 'read write');
97
98 const app = express();
99 app.use(routes);
100 const server = app.listen(0);
101 t.after(() => server.close());
102 await new Promise((r) => server.once('listening', r));
103 const get = async (pad) => {
104 const r = await fetch(`http://127.0.0.1:${server.address().port}${pad}`,
105 { headers: { Authorization: `Bearer ${bearer}` } });
106 return { status: r.status, body: await r.json() };
107 };
108 const slug = '/ap/users/kind';
109
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 // De byline zit in de ingesloten actor, net als overal sinds shaer-nmw.
114 assert.ok(list.body.orderedItems.every((i) => i.object.attributedTo));
115
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
121 // De next-link volgen doet wat hij belooft: geen dubbele, en uiteindelijk op.
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
130 // Een gesprek draagt beide kanten; het eigen antwoord aan tante hoort erbij.
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
135 const broken = await get(`${slug}/messages?with=nonsens`);
136 assert.equal(broken.status, 400, 'geen actor-uri, geen gesprek');
137});
Note: See TracBrowser for help on using the repository browser.