source: Klonkt/test/block-federeert.test.js

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

Tell an ex-follower they are one: Reject(Follow) on removal

Removing a follower was silent, so the other side kept us in its books
as a live relationship while nothing was ever delivered again — the hub
sat there 'following' dev.klonkt.com for days. Reject(Follow) is the
standard signal for exactly this, and the removal itself never waits on
it: the row is gone first, the notice is a courtesy that may fail.

Blocking already carries the stronger Block signal, so this covers the
other door: the owner removing someone by hand.

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

  • Property mode set to 100644
File size: 4.5 KB
Line 
1// Een blokkade wordt ook VERSTUURD (Robin, 21-8).
2//
3// Aanleiding: dev.klonkt.com blokkeerde de hub, en op de hub bleef het kanaal
4// met alle berichten gewoon staan. De blokkade bleef namelijk binnenshuis --
5// rij in ap_blocks, inhoud opruimen, volger eruit -- en de andere kant hoorde
6// er nooit van. Nu gaat er een Block naar de inbox van wie je blokkeert, en
7// bij opheffen een Undo(Block), zodat de weg terug openligt.
8//
9// De fetch is gestubd: het gaat om WAT er de deur uit gaat, niet om echte HTTP.
10import { test, after } 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')).default;
20
21db.prepare("INSERT INTO users (id, username, email, password_hash, role) VALUES ('u1','u1','u1@t','x','god')").run();
22db.prepare("INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES ('s1','dev','Dev','u1',1)").run();
23const site = () => db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
24
25// IP-literal: safeFetch slaat de DNS-lookup over en de stub vangt de rest.
26const DOEL = 'https://203.0.113.80/ap/actor';
27const bezorgd = [];
28const echteFetch = globalThis.fetch;
29globalThis.fetch = async (url, opts = {}) => {
30 const u = String(url);
31 if (opts.method === 'POST') {
32 try { bezorgd.push({ naar: u, activiteit: JSON.parse(opts.body) }); } catch { /* niet-JSON telt niet mee */ }
33 return new Response('', { status: 202 });
34 }
35 return new Response(JSON.stringify({
36 id: DOEL, type: 'Application', preferredUsername: 'hub', inbox: `${DOEL}/inbox`,
37 }), { status: 200, headers: { 'content-type': 'application/activity+json' } });
38};
39after(() => { globalThis.fetch = echteFetch; });
40
41test('blokkeren stuurt een Block naar de geblokkeerde', async () => {
42 bezorgd.length = 0;
43 const r = await AP.blockTarget(site(), DOEL);
44 assert.equal(r.ok, true);
45 assert.equal(db.prepare('SELECT count(*) c FROM ap_blocks WHERE target = ?').get(DOEL).c, 1, 'de blokkade staat vast');
46 const blok = bezorgd.find((b) => b.activiteit.type === 'Block');
47 assert.ok(blok, 'er is een Block bezorgd');
48 assert.equal(blok.naar, `${DOEL}/inbox`);
49 assert.equal(blok.activiteit.object, DOEL);
50 assert.equal(blok.activiteit.actor, 'https://klonkt.test/ap/users/dev');
51});
52
53test('opheffen stuurt een Undo(Block), anders komt de ander nooit terug', async () => {
54 bezorgd.length = 0;
55 await AP.unblock(site(), DOEL);
56 assert.equal(db.prepare('SELECT count(*) c FROM ap_blocks WHERE target = ?').get(DOEL).c, 0, 'de blokkade is weg');
57 const undo = bezorgd.find((b) => b.activiteit.type === 'Undo');
58 assert.ok(undo, 'er is een Undo bezorgd');
59 assert.equal(undo.activiteit.object.type, 'Block');
60 assert.equal(undo.activiteit.object.object, DOEL);
61});
62
63test('een DOMEIN-blokkade verstuurt niets: daar is geen inbox voor', async () => {
64 bezorgd.length = 0;
65 await AP.blockTarget(site(), 'spam.example');
66 assert.equal(db.prepare("SELECT count(*) c FROM ap_blocks WHERE kind = 'domain'").get().c, 1);
67 assert.equal(bezorgd.length, 0);
68});
69
70test('een onbereikbare tegenpartij houdt de blokkade niet tegen', async () => {
71 const stuk = globalThis.fetch;
72 globalThis.fetch = async () => { throw new Error('down'); };
73 const r = await AP.blockTarget(site(), 'https://203.0.113.99/ap/actor');
74 globalThis.fetch = stuk;
75 assert.equal(r.ok, true, 'de blokkade staat, ook zonder bezorging');
76 assert.equal(db.prepare('SELECT count(*) c FROM ap_blocks WHERE target = ?').get('https://203.0.113.99/ap/actor').c, 1);
77});
78
79test('een verwijderde volger krijgt een Reject(Follow)', async () => {
80 bezorgd.length = 0;
81 const VOLGER = 'https://203.0.113.81/ap/actor';
82 db.prepare(`INSERT INTO ap_followers (slug, actor_uri, inbox, created_at)
83 VALUES ('dev', ?, ?, CURRENT_TIMESTAMP)`).run(VOLGER, `${VOLGER}/inbox`);
84 const rij = db.prepare('SELECT id FROM ap_followers WHERE actor_uri = ?').get(VOLGER);
85 assert.equal(AP.removeFollower('dev', rij.id), true);
86 await new Promise((r) => setTimeout(r, 150)); // bezorging is fire-and-forget
87 const rej = bezorgd.find((b) => b.activiteit.type === 'Reject');
88 assert.ok(rej, 'er is een Reject bezorgd');
89 assert.equal(rej.activiteit.object.type, 'Follow');
90 assert.equal(rej.activiteit.object.actor, VOLGER, 'het gaat om ZIJN follow op ons');
91 assert.equal(db.prepare('SELECT count(*) c FROM ap_followers WHERE actor_uri = ?').get(VOLGER).c, 0);
92});
Note: See TracBrowser for help on using the repository browser.