| 1 | // Een verhuisd account gaat op slot aan de UITGAANDE kant.
|
|---|
| 2 | //
|
|---|
| 3 | // Het serveerde wel `movedTo`, maar je kon er gewoon op posten, volgen, liken en
|
|---|
| 4 | // reageren, en dat federeerde de wereld in. Drie dingen gingen daar mis: nieuwe
|
|---|
| 5 | // posts kregen een object-URI op een adres dat je hebt opgezegd, je volgers waren
|
|---|
| 6 | // al verhuisd dus je postte in het niets, en een server die je movedTo ziet én
|
|---|
| 7 | // verse activiteit van dat adres krijgt, krijgt tegenstrijdige signalen.
|
|---|
| 8 | //
|
|---|
| 9 | // De poort staat in de SERVICE en niet op de knoppen: een C2S-client praat
|
|---|
| 10 | // rechtstreeks met deze functies en zou anders langs een verborgen knop lopen.
|
|---|
| 11 | // Deze tests leggen per soort vast wat dicht gaat en wat open blijft, want dat
|
|---|
| 12 | // onderscheid is het hele ontwerp.
|
|---|
| 13 | import { test, beforeEach } from 'node:test';
|
|---|
| 14 | import assert from 'node:assert/strict';
|
|---|
| 15 |
|
|---|
| 16 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 17 | process.env.PUBLIC_BASE_URL = 'https://oud.example';
|
|---|
| 18 |
|
|---|
| 19 | const dbMod = await import('../src/config/database.js');
|
|---|
| 20 | const db = dbMod.default;
|
|---|
| 21 | {
|
|---|
| 22 | const stil = console.log;
|
|---|
| 23 | console.log = () => {};
|
|---|
| 24 | try { dbMod.initializeDatabase(); } finally { console.log = stil; }
|
|---|
| 25 | }
|
|---|
| 26 | const AP = await import('../src/services/ActivityPubService.js');
|
|---|
| 27 |
|
|---|
| 28 | const NIEUW = 'https://nieuw.example/ap/users/robo';
|
|---|
| 29 |
|
|---|
| 30 | function site(moved) {
|
|---|
| 31 | db.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 32 | .run('u1', 'u1', 'u1@test', 'x', 'god');
|
|---|
| 33 | db.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
|
|---|
| 34 | .run('s1', 'ik', 'Mijn site', 'u1');
|
|---|
| 35 | db.prepare('UPDATE sites SET moved_to = ? WHERE slug = ?').run(moved || null, 'ik');
|
|---|
| 36 | return db.prepare('SELECT * FROM sites WHERE slug = ?').get('ik');
|
|---|
| 37 | }
|
|---|
| 38 |
|
|---|
| 39 | // Alles hier stil: de poort logt met opzet een waarschuwing per weigering.
|
|---|
| 40 | async function stil(fn) {
|
|---|
| 41 | const w = console.warn; const l = console.log;
|
|---|
| 42 | console.warn = () => {}; console.log = () => {};
|
|---|
| 43 | try { return await fn(); } finally { console.warn = w; console.log = l; }
|
|---|
| 44 | }
|
|---|
| 45 |
|
|---|
| 46 | beforeEach(() => { db.prepare('DELETE FROM ap_my_reactions').run(); });
|
|---|
| 47 |
|
|---|
| 48 | test('movedLock leest de verhuizing', () => {
|
|---|
| 49 | assert.equal(AP.movedLock(site(null)).locked, false);
|
|---|
| 50 | const l = AP.movedLock(site(NIEUW));
|
|---|
| 51 | assert.equal(l.locked, true);
|
|---|
| 52 | assert.equal(l.movedTo, NIEUW);
|
|---|
| 53 | });
|
|---|
| 54 |
|
|---|
| 55 | test('een onzinnige moved_to zet niets op slot', () => {
|
|---|
| 56 | // Alleen een echte http(s)-URI telt; een half ingevuld veld mag je site niet
|
|---|
| 57 | // stilleggen zonder dat er een wegwijzer tegenover staat.
|
|---|
| 58 | assert.equal(AP.movedLock(site('nogniet')).locked, false);
|
|---|
| 59 | });
|
|---|
| 60 |
|
|---|
| 61 | // ── Wat DICHT gaat ────────────────────────────────────────────────
|
|---|
| 62 |
|
|---|
| 63 | test('volgen wordt geweigerd', async () => {
|
|---|
| 64 | const r = await stil(() => AP.followActor(site(NIEUW), '@iemand@elders.example'));
|
|---|
| 65 | assert.equal(r.error, 'moved');
|
|---|
| 66 | assert.equal(r.movedTo, NIEUW, 'de weigering zegt ook waarheen, zodat de UI dat kan tonen');
|
|---|
| 67 | });
|
|---|
| 68 |
|
|---|
| 69 | test('liken en boosten worden geweigerd', async () => {
|
|---|
| 70 | for (const kind of ['like', 'boost']) {
|
|---|
| 71 | const r = await stil(() => AP.sendInteraction(site(NIEUW), kind, 'https://elders.example/notes/1', 'https://elders.example/users/a'));
|
|---|
| 72 | assert.equal(r.error, 'moved', `${kind} hoort dicht te zijn`);
|
|---|
| 73 | }
|
|---|
| 74 | });
|
|---|
| 75 |
|
|---|
| 76 | test('reageren wordt geweigerd', async () => {
|
|---|
| 77 | const r = await stil(() => AP.deliverReply(site(NIEUW), { postId: 'p1', parent: 'https://elders.example/notes/1', text: 'hoi' }));
|
|---|
| 78 | assert.equal(r.error, 'moved');
|
|---|
| 79 | });
|
|---|
| 80 |
|
|---|
| 81 | test('stemmen in een peiling wordt geweigerd', async () => {
|
|---|
| 82 | const r = await stil(() => AP.voteOnRemotePoll(site(NIEUW), 'https://elders.example/notes/9', [0]));
|
|---|
| 83 | assert.equal(r.error, 'moved');
|
|---|
| 84 | });
|
|---|
| 85 |
|
|---|
| 86 | test('een tweede verhuizing wordt geweigerd', async () => {
|
|---|
| 87 | const r = await stil(() => AP.moveAccount(site(NIEUW), '@nog1@ergens.example'));
|
|---|
| 88 | assert.equal(r.error, 'already_moved',
|
|---|
| 89 | 'anders stapel je wegwijzers en weet niemand waar de keten eindigt');
|
|---|
| 90 | });
|
|---|
| 91 |
|
|---|
| 92 | test('de lokale like-vlag wordt OOK niet gezet', async () => {
|
|---|
| 93 | // Anders zie je een like staan die nooit de deur uit is gegaan: de halve
|
|---|
| 94 | // toestand die erger is dan een duidelijke weigering.
|
|---|
| 95 | site(NIEUW);
|
|---|
| 96 | await stil(() => AP.setReaction('ik', 'https://elders.example/notes/1', 'like', true));
|
|---|
| 97 | const n = db.prepare('SELECT count(*) c FROM ap_my_reactions').get().c;
|
|---|
| 98 | assert.equal(n, 0);
|
|---|
| 99 | });
|
|---|
| 100 |
|
|---|
| 101 | // ── Wat OPEN blijft ───────────────────────────────────────────────
|
|---|
| 102 |
|
|---|
| 103 | test('zonder verhuizing werkt alles gewoon', async () => {
|
|---|
| 104 | const s = site(null);
|
|---|
| 105 | // followActor gaat het netwerk op, dus we kijken alleen dat hij NIET op de
|
|---|
| 106 | // verhuis-poort strandt. Elke andere fout is hier prima.
|
|---|
| 107 | const r = await stil(() => AP.followActor(s, '@iemand@elders.example'));
|
|---|
| 108 | assert.notEqual(r && r.error, 'moved');
|
|---|
| 109 |
|
|---|
| 110 | await stil(() => AP.setReaction('ik', 'https://elders.example/notes/1', 'like', true));
|
|---|
| 111 | assert.equal(db.prepare('SELECT count(*) c FROM ap_my_reactions').get().c, 1,
|
|---|
| 112 | 'de lokale vlag hoort gewoon gezet te worden als je niet verhuisd bent');
|
|---|
| 113 | });
|
|---|
| 114 |
|
|---|
| 115 | test('ontvolgen blijft mogen, ook na een verhuizing', async () => {
|
|---|
| 116 | // Opruimen mag altijd: het maakt niets nieuws aan en het laat je je oude
|
|---|
| 117 | // account netjes achterlaten.
|
|---|
| 118 | const r = await stil(() => AP.unfollowActor(site(NIEUW), 'https://elders.example/users/a'));
|
|---|
| 119 | assert.notEqual(r && r.error, 'moved');
|
|---|
| 120 | });
|
|---|
| 121 |
|
|---|
| 122 | test('de wegwijzer blijft staan, dat is het hele punt van het domein aanhouden', () => {
|
|---|
| 123 | const doc = AP.buildActor('https://oud.example', site(NIEUW));
|
|---|
| 124 | assert.equal(doc.movedTo, NIEUW,
|
|---|
| 125 | 'zonder movedTo weet niemand die de Move miste waar je heen bent');
|
|---|
| 126 | assert.ok(doc.inbox, 'en de inbox blijft, want reacties op oude posts moeten binnen kunnen komen');
|
|---|
| 127 | });
|
|---|