source: Klonkt/test/feed-wait.test.js@ 4f322bc

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

Wachten op nieuws, als uitbreiding van de inbox (shaer-n05)

Geen tweede endpoint. Geef since (de shaer:cursor uit je vorige antwoord) en
wait mee aan de bestaande inbox-lezing, en het antwoord blijft hangen tot er
iets is of de tijd om is. Zonder die twee gedraagt de route zich exact zoals
altijd.

Bewust hetzelfde antwoord in plaats van een 'er is nieuws'-seintje: dan hoeft de
client niets nieuws te parsen, is er geen tweede beschrijving van de kaartvorm
die uit de pas kan lopen met de eerste, en scheelt het een tweede ronde.

De merksteen telt ALLE VIER de poten die de inbox samenvoegt: tijdlijn,
berichten, antwoorden op je eigen posts, en wat je zelf verstuurde. Ontbreekt er
een, dan blijft een wachtende client slapen terwijl er wel degelijk nieuws is --
erger dan niet wachten, want het lijkt te werken. rowid en geen tijdstempel: die
loopt strikt op, terwijl twee dingen in dezelfde seconde kunnen aankomen en een
published van een andere server niet te vertrouwen is.

Wachten gebeurt met een interne tik en niet met een gebeurtenis-emitter. Een
emitter moet op ELKE plek worden aangeroepen waar er iets bijkomt, en de plek die
je vergeet is precies de melding die nooit aankomt. Vier MAX(rowid)-queries per
seconde is niets, en dit kan niets missen. Prijs: hooguit een tik vertraging.

Grenzen: hooguit 50 seconden wachten (ruim onder wat een proxy toestaat), vier
gelijktijdige wachters per account, en ophangen breekt het wachten af. Een client
met een kapotte herverbind-lus krijgt gewoon de huidige stand in plaats van een
fout.

9 tests, waaronder de vier poten apart en de vraag of hij wakker wordt in plaats
van de tijd vol te maken. Suite 537/537.

  • Property mode set to 100644
File size: 5.8 KB
Line 
1// Wachten op nieuws, als uitbreiding van de inbox-lezing (shaer-n05).
2//
3// Geen tweede endpoint en geen seintje-formaat: dezelfde lezing, die desgevraagd
4// even blijft hangen. Wat hier bewaakt wordt is de MERKSTEEN -- als die niet
5// beweegt bij iets dat de inbox wel zou tonen, blijft een wachtende client
6// slapen terwijl er nieuws is. Dat is erger dan niet wachten, want het lijkt te
7// werken.
8//
9// Run: npm test
10
11import { test } from 'node:test';
12import assert from 'node:assert/strict';
13
14process.env.DATABASE_PATH = ':memory:';
15process.env.PUBLIC_BASE_URL = 'https://klonkt.test';
16
17const dbMod = await import('../src/config/database.js');
18const db = dbMod.default;
19dbMod.initializeDatabase();
20const AP = await import('../src/services/ActivityPubService.js');
21
22db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
23 .run('u1', 'u1', 'u1@t', 'x', 'god');
24db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'me', 'Me', 'u1');
25db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s2', 'buur', 'Buur', 'u1');
26db.prepare('INSERT INTO posts (id, site_id, slug, author_id, title, content, status) VALUES (?,?,?,?,?,?,?)')
27 .run('p1', 's1', 'mijn-post', 'u1', 'Post', '<p>x</p>', 'published');
28
29let n = 0;
30const nieuwePost = (slug = 'me') => db.prepare(
31 "INSERT INTO ap_timeline (id, slug, author_uri, content) VALUES (?,?,'https://r.test/u/a','<p>x</p>')",
32).run(`https://r.test/n/${++n}`, slug);
33
34test('een verse tijdlijnpost beweegt de merksteen', () => {
35 const voor = AP.feedCursor('me');
36 nieuwePost();
37 assert.notEqual(AP.feedCursor('me'), voor);
38});
39
40test('en zo ook de andere drie poten van de inbox', () => {
41 // De inbox voegt vier bronnen samen. Ontbreekt er een in de merksteen, dan
42 // wordt een client niet wakker van precies dat soort nieuws.
43 const bericht = () => db.prepare(
44 "INSERT INTO ap_mentions (slug, object_uri, actor_uri, content) VALUES ('me',?, 'https://r.test/u/a','<p>hoi</p>')",
45 ).run(`https://r.test/m/${++n}`);
46 const antwoord = () => db.prepare(
47 `INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content)
48 VALUES ('reply','p1',?, 'https://r.test/u/a','<p>hoi</p>')`,
49 ).run(`https://r.test/r/${++n}`);
50 const verstuurd = () => db.prepare(
51 "INSERT INTO ap_outbox (id, site_slug, post_id, content) VALUES (?,'me','p1','<p>x</p>')",
52 ).run(`o${++n}`);
53
54 for (const [naam, doen] of [['bericht', bericht], ['antwoord', antwoord], ['verstuurd', verstuurd]]) {
55 const voor = AP.feedCursor('me');
56 doen();
57 assert.notEqual(AP.feedCursor('me'), voor, `${naam} hoort de merksteen te bewegen`);
58 }
59});
60
61test('nieuws van een ANDER account laat je merksteen staan', () => {
62 const voor = AP.feedCursor('me');
63 nieuwePost('buur');
64 assert.equal(AP.feedCursor('me'), voor, 'anders wordt iedereen wakker van andermans post');
65});
66
67test('is er al iets veranderd, dan wordt er niet gewacht', async () => {
68 const oud = AP.feedCursor('me');
69 nieuwePost();
70 const begin = Date.now();
71 const uit = await AP.waitForFeedChange('me', { since: oud, waitMs: 5000, tickMs: 20 });
72 assert.equal(uit.changed, true);
73 assert.equal(uit.waited, false, 'meteen antwoorden, niet eerst de tijd volmaken');
74 assert.ok(Date.now() - begin < 500);
75});
76
77test('zonder sinds is het gewoon de huidige stand', async () => {
78 const uit = await AP.waitForFeedChange('me', { waitMs: 5000, tickMs: 20 });
79 assert.equal(uit.changed, false);
80 assert.equal(uit.waited, false);
81 assert.equal(uit.cursor, AP.feedCursor('me'));
82});
83
84test('wachten wordt afgebroken zodra er iets binnenkomt', async () => {
85 const oud = AP.feedCursor('me');
86 const begin = Date.now();
87 setTimeout(() => nieuwePost(), 60);
88 const uit = await AP.waitForFeedChange('me', { since: oud, waitMs: 4000, tickMs: 20 });
89 assert.equal(uit.changed, true);
90 assert.equal(uit.waited, true);
91 assert.ok(Date.now() - begin < 2000, 'hij hoort wakker te worden, niet de tijd vol te maken');
92 assert.notEqual(uit.cursor, oud, 'en de nieuwe merksteen komt mee');
93});
94
95test('gebeurt er niets, dan komt hij leeg terug binnen de tijd', async () => {
96 const oud = AP.feedCursor('me');
97 const begin = Date.now();
98 const uit = await AP.waitForFeedChange('me', { since: oud, waitMs: 150, tickMs: 20 });
99 assert.equal(uit.changed, false);
100 assert.equal(uit.waited, true);
101 assert.equal(uit.cursor, oud);
102 assert.ok(Date.now() - begin >= 100, 'hij hoort wel echt gewacht te hebben');
103 assert.ok(Date.now() - begin < 3000, 'en niet langer dan gevraagd');
104});
105
106test('ophangen breekt het wachten af', async () => {
107 // Zonder dit blijft er een timer draaien voor een client die er niet meer is.
108 const oud = AP.feedCursor('me');
109 const ac = new AbortController();
110 const begin = Date.now();
111 setTimeout(() => ac.abort(), 60);
112 const uit = await AP.waitForFeedChange('me', { since: oud, waitMs: 4000, tickMs: 20, signal: ac.signal });
113 assert.equal(uit.changed, false);
114 assert.ok(Date.now() - begin < 2000);
115});
116
117test('meer dan vier wachters tegelijk: de rest krijgt meteen antwoord', async () => {
118 // Een client met een kapotte herverbind-lus mag de instance niet vastzetten.
119 // De overtolligen krijgen geen fout maar gewoon de huidige stand.
120 const oud = AP.feedCursor('me');
121 const lopend = [];
122 for (let i = 0; i < 4; i++) lopend.push(AP.waitForFeedChange('me', { since: oud, waitMs: 300, tickMs: 20 }));
123 await new Promise((r) => setTimeout(r, 30));
124 const vijfde = await AP.waitForFeedChange('me', { since: oud, waitMs: 4000, tickMs: 20 });
125 assert.equal(vijfde.busy, true);
126 assert.equal(vijfde.waited, false);
127 await Promise.all(lopend);
128 // En daarna is er weer plek: de teller moet netjes teruglopen.
129 const daarna = await AP.waitForFeedChange('me', { since: oud, waitMs: 60, tickMs: 20 });
130 assert.notEqual(daarna.busy, true);
131});
Note: See TracBrowser for help on using the repository browser.