| 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.
|
|---|
| 10 | import { test, after } 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')).default;
|
|---|
| 20 |
|
|---|
| 21 | db.prepare("INSERT INTO users (id, username, email, password_hash, role) VALUES ('u1','u1','u1@t','x','god')").run();
|
|---|
| 22 | db.prepare("INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES ('s1','dev','Dev','u1',1)").run();
|
|---|
| 23 | const 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.
|
|---|
| 26 | const DOEL = 'https://203.0.113.80/ap/actor';
|
|---|
| 27 | const bezorgd = [];
|
|---|
| 28 | const echteFetch = globalThis.fetch;
|
|---|
| 29 | globalThis.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 | };
|
|---|
| 39 | after(() => { globalThis.fetch = echteFetch; });
|
|---|
| 40 |
|
|---|
| 41 | test('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 |
|
|---|
| 53 | test('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 |
|
|---|
| 63 | test('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 |
|
|---|
| 70 | test('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 |
|
|---|
| 79 | test('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 | });
|
|---|