| 1 | // Een track verwijderen hoort de fediverse te bereiken.
|
|---|
| 2 | //
|
|---|
| 3 | // Tot 21-8 gebeurde dat niet. Een post kondigde zijn verwijdering aan
|
|---|
| 4 | // (posts.js roept deliverDelete), maar een track niet: de rij ging weg, het
|
|---|
| 5 | // Audio-object gaf 404, en elke server die hem had geindexeerd bleef ernaar
|
|---|
| 6 | // wijzen. Op de hub kwam dat boven als "ongbakbeat (1)", een kaartje met een
|
|---|
| 7 | // dode link, en het viel alleen op omdat robo het zag staan.
|
|---|
| 8 | //
|
|---|
| 9 | // De test gaat door de ECHTE route en kijkt in de bezorgwachtrij, want een
|
|---|
| 10 | // eigen testhaak op deliver() bestaat hier niet. De volger krijgt een inbox op
|
|---|
| 11 | // een dichte poort: de directe poging faalt meteen, en dan hoort de activiteit
|
|---|
| 12 | // in ap_delivery te belanden. Dat is precies het bewijs dat we zoeken, want die
|
|---|
| 13 | // tabel was in het echte geval leeg.
|
|---|
| 14 | import { test } from 'node:test';
|
|---|
| 15 | import assert from 'node:assert/strict';
|
|---|
| 16 |
|
|---|
| 17 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 18 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 19 | const dbMod = await import('../src/config/database.js');
|
|---|
| 20 | const db = dbMod.default;
|
|---|
| 21 | dbMod.initializeDatabase();
|
|---|
| 22 |
|
|---|
| 23 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 24 | .run('u1', 'baas', 'b@t.nl', 'x', 'god');
|
|---|
| 25 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
|
|---|
| 26 | .run('s1', 'robo', 'Soundfabrics', 'u1');
|
|---|
| 27 | // Een dichte poort: faalt onmiddellijk, geen DNS, geen wachttijd.
|
|---|
| 28 | db.prepare('INSERT INTO ap_followers (slug, actor_uri, inbox) VALUES (?,?,?)')
|
|---|
| 29 | .run('robo', 'https://elders.test/ap/users/x', 'http://127.0.0.1:1/inbox');
|
|---|
| 30 |
|
|---|
| 31 | const TRACK = 'trk-481fef66';
|
|---|
| 32 | db.prepare('INSERT INTO audio_tracks (id, site_id, title) VALUES (?,?,?)')
|
|---|
| 33 | .run(TRACK, 's1', 'ongbakbeat (1)');
|
|---|
| 34 |
|
|---|
| 35 | const express = (await import('express')).default;
|
|---|
| 36 | const router = (await import('../src/routes/admin-audio.js')).default;
|
|---|
| 37 | const app = express();
|
|---|
| 38 | app.use(express.urlencoded({ extended: true }));
|
|---|
| 39 | app.use((req, res, next) => {
|
|---|
| 40 | req.session = { user: { id: 'u1', role: 'god' } };
|
|---|
| 41 | res.locals.site = db.prepare('SELECT * FROM sites WHERE slug = ?').get('robo');
|
|---|
| 42 | res.locals.siteUrlBase = '';
|
|---|
| 43 | next();
|
|---|
| 44 | });
|
|---|
| 45 | app.use('/admin/audio', router);
|
|---|
| 46 |
|
|---|
| 47 | const server = app.listen(0);
|
|---|
| 48 | server.unref();
|
|---|
| 49 | const poort = server.address().port;
|
|---|
| 50 |
|
|---|
| 51 | const wacht = (ms) => new Promise((r) => setTimeout(r, ms));
|
|---|
| 52 |
|
|---|
| 53 | test('een verwijderde track kondigt zichzelf af bij de volgers', async () => {
|
|---|
| 54 | const r = await fetch(`http://127.0.0.1:${poort}/admin/audio/${TRACK}/delete`, {
|
|---|
| 55 | method: 'POST', redirect: 'manual', signal: AbortSignal.timeout(5000),
|
|---|
| 56 | }).catch((e) => assert.fail(`de route antwoordde niet (${e.name})`));
|
|---|
| 57 | assert.ok(r.status === 302 || r.status === 200, 'verwijderen mag niet stranden, kreeg ' + r.status);
|
|---|
| 58 |
|
|---|
| 59 | assert.equal(
|
|---|
| 60 | db.prepare('SELECT count(*) c FROM audio_tracks WHERE id = ?').get(TRACK).c, 0,
|
|---|
| 61 | 'de rij hoort weg te zijn',
|
|---|
| 62 | );
|
|---|
| 63 |
|
|---|
| 64 | // De bezorging is best-effort en async: even de mislukte poging laten landen.
|
|---|
| 65 | for (let i = 0; i < 20 && !db.prepare('SELECT count(*) c FROM ap_delivery').get().c; i++) await wacht(100);
|
|---|
| 66 |
|
|---|
| 67 | const rij = db.prepare('SELECT * FROM ap_delivery').get();
|
|---|
| 68 | assert.ok(rij, 'er hoort een bezorging in de wachtrij te staan; leeg betekent dat er niets is verstuurd');
|
|---|
| 69 |
|
|---|
| 70 | const act = JSON.parse(rij.body);
|
|---|
| 71 | assert.equal(act.type, 'Delete');
|
|---|
| 72 | assert.equal(act.object.type, 'Tombstone');
|
|---|
| 73 | // De identiteit moet dezelfde zijn als die het Audio-object droeg, anders
|
|---|
| 74 | // ruimt de ontvanger niets op: hij kent dat id niet.
|
|---|
| 75 | assert.equal(act.object.id, `https://test.example/ap/users/robo/tracks/${TRACK}`);
|
|---|
| 76 | assert.equal(act.actor, 'https://test.example/ap/users/robo');
|
|---|
| 77 | });
|
|---|
| 78 |
|
|---|
| 79 | test('het id in de Delete is exact het id waaronder de track de deur uit ging', async () => {
|
|---|
| 80 | // De echte valkuil bij dit soort fixes: een tweede plek die het id opnieuw
|
|---|
| 81 | // samenstelt en er net naast zit. Daarom bouwt trackUri() hem nu op een plek,
|
|---|
| 82 | // en vergelijken we hier met wat de bouwkant zelf produceert.
|
|---|
| 83 | const muziek = await import('../src/services/music/index.js');
|
|---|
| 84 | const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get('robo');
|
|---|
| 85 | const audio = muziek.buildTrackAudio('https://test.example', site, { id: 'abc-123', title: 'x' });
|
|---|
| 86 | assert.equal(audio.id, muziek.trackUri('https://test.example', site, 'abc-123'));
|
|---|
| 87 | });
|
|---|