source: Klonkt/test/track-delete-federates.test.js

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

Een verwijderde track kondigt zichzelf aan, net als een post

Een post stuurt bij verwijderen een Delete(Tombstone) naar zijn volgers; een
track deed dat niet. De rij ging weg, het Audio-object gaf 404, en elke server
die hem had geindexeerd bleef ernaar wijzen. Op de hub stond het er vandaag als
een kaartje met een dode link, en het viel alleen op doordat robo het zag.

Sinds shaer-0nh is een track een eersterangs Audio-object met een eigen id, dus
hij hoort ook een eigen afmelding te krijgen.

  • deliverObjectDelete(site, objectId) is de gedeelde romp; deliverDelete en het nieuwe deliverTrackDelete leunen er allebei op. Het object-id komt van de aanroeper, want bij verwijderen is de rij vaak al weg.
  • trackUri() staat nu op EEN plek in music/index.js. Het formaat werd eerder alleen door de bouwkant gekend, en een tweede plek die het opnieuw in elkaar zet is precies hoe je een Delete stuurt die de ontvanger niet herkent.
  • Beide verwijderroutes in admin-audio melden af: de losse verwijdering en de opruiming van wezen. Een wees is voor ONS een track zonder bestand, maar de buitenwereld heeft een gewoon Audio-object opgeslagen.

Twee tests door de echte route. Een testhaak op deliver() bestaat niet, dus de
proef kijkt in ap_delivery: de volger krijgt een inbox op een dichte poort, de
directe poging faalt, en de activiteit hoort in de wachtrij te belanden. Die
tabel was in het echte geval leeg, dus dat is precies het bewijs. Controleproef
gedraaid: zonder de aankondiging valt de test om.

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

  • Property mode set to 100644
File size: 4.1 KB
Line 
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.
14import { test } from 'node:test';
15import assert from 'node:assert/strict';
16
17process.env.DATABASE_PATH = ':memory:';
18process.env.PUBLIC_BASE_URL = 'https://test.example';
19const dbMod = await import('../src/config/database.js');
20const db = dbMod.default;
21dbMod.initializeDatabase();
22
23db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
24 .run('u1', 'baas', 'b@t.nl', 'x', 'god');
25db.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.
28db.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
31const TRACK = 'trk-481fef66';
32db.prepare('INSERT INTO audio_tracks (id, site_id, title) VALUES (?,?,?)')
33 .run(TRACK, 's1', 'ongbakbeat (1)');
34
35const express = (await import('express')).default;
36const router = (await import('../src/routes/admin-audio.js')).default;
37const app = express();
38app.use(express.urlencoded({ extended: true }));
39app.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});
45app.use('/admin/audio', router);
46
47const server = app.listen(0);
48server.unref();
49const poort = server.address().port;
50
51const wacht = (ms) => new Promise((r) => setTimeout(r, ms));
52
53test('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
79test('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});
Note: See TracBrowser for help on using the repository browser.