source: Klonkt/test/ap-outbox-tracks.test.js@ fb22f78

main
Last change on this file since fb22f78 was fb22f78, checked in by Robin <roboburr@…>, 5 weeks ago

De outbox vertelt de discografie mee (shaer-0nh, stap 4)

Een track verschijnt nu als Create(Audio) in de outbox, door de posts heen op
datum. Daarmee is een Klonkt-actor als kanaal uit te lezen: precies zoals ik
Funkwhales kanaal uitlas -- hun achttien tracks stonden in hun outbox.

WAAROM GEEN BEZORGING NAAR VOLGERS, terwijl ik stap 4 eerder zo beschreef.
Mastodon neemt Audio aan als STATUSTYPE. Publiceer je een post met een album,
dan zouden volgers de Note met spelers krijgen EN N losse Audio-statussen:
dezelfde muziek twee keer in hun tijdlijn. Dat is geen spiegel afmaken maar
andermans tijdlijn vervuilen. Een kanaal-lezer HAALT op; hij hoeft niet
geduwd te worden. De post is het bericht, de outbox is de discografie.

DE DEUR BLIJFT DICHT. buildOutbox krijgt de tracks als ARGUMENT en haalt ze
uitdrukkelijk niet zelf op. De route beslist wie wat ziet, en de blocked-tak
daar levert een outbox zonder posts EN zonder tracks. Een bouwer die stiekem
zijn eigen database bevraagt zou dwars door die dichte deur heen leveren --
dat had ik bijna gebouwd, en de tak in de route ving het pas bij nalezen.

Het id van de activiteit is stabiel (<track>#create), zodat twee keer ophalen
niet twee keer nieuws is.

5 tests.

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

  • Property mode set to 100644
File size: 4.1 KB
Line 
1// De outbox vertelt de discografie mee (shaer-0nh, stap 4).
2//
3// Tracks verschijnen als Create(Audio) in de outbox, door de posts heen op
4// datum. NIET als bezorging naar volgers: Mastodon neemt Audio aan als
5// statustype, dus bij een album-post zou dezelfde muziek twee keer in hun
6// tijdlijn komen -- een keer als bijlage bij de Note, en dan nog los.
7//
8// En de deur blijft dicht: een geblokkeerde bezoeker krijgt een lege outbox,
9// ook nu daar tracks in kunnen zitten.
10import { test } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14process.env.PUBLIC_BASE_URL = 'https://test.example';
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
21const BASE = 'https://test.example';
22db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
23 .run('u1', 'u1', 'u1@t', 'x', 'god');
24db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public, is_primary) VALUES (?,?,?,?,1,1)')
25 .run('s1', 'band', 'De Band', 'u1');
26const site = () => db.prepare("SELECT s.*, (SELECT slug FROM sites WHERE is_primary = 1) AS primary_slug FROM sites s WHERE s.id = 's1'").get();
27
28db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1)')
29 .run('m1', 's1', 'open.mp3', 'audio/open.mp3', 'audio/mpeg');
30db.prepare('INSERT INTO audio_tracks (id, site_id, title, duration, media_id, fedi_open, created_at) VALUES (?,?,?,?,?,1,?)')
31 .run('t1', 's1', 'Het nummer', 100, 'm1', '2026-03-01 12:00:00');
32db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1)')
33 .run('m2', 's1', 'dicht.mp3', 'audio/dicht.mp3', 'audio/mpeg');
34db.prepare('INSERT INTO audio_tracks (id, site_id, title, duration, media_id, fedi_open) VALUES (?,?,?,?,?,0)')
35 .run('t2', 's1', 'Achter de poort', 90, 'm2');
36
37const POST_OUD = { id: 'p1', slug: 'p1', title: 'Oud', content: '<p>oud</p>', status: 'published', published_at: '2026-01-01T00:00:00Z', created_at: '2026-01-01T00:00:00Z' };
38const POST_NIEUW = { id: 'p2', slug: 'p2', title: 'Nieuw', content: '<p>nieuw</p>', status: 'published', published_at: '2026-06-01T00:00:00Z', created_at: '2026-06-01T00:00:00Z' };
39
40test('een track staat als Create(Audio) in de outbox', () => {
41 const ob = AP.buildOutbox(BASE, site(), [], AP.siteOpenTracks('s1'));
42 assert.equal(ob.totalItems, 1);
43 const c = ob.orderedItems[0];
44 assert.equal(c.type, 'Create');
45 assert.equal(c.object.type, 'Audio', 'het blijft een Audio, geen Note');
46 assert.equal(c.object.id, 'https://test.example/ap/users/band/tracks/t1');
47 assert.equal(c.id, 'https://test.example/ap/users/band/tracks/t1#create',
48 'stabiel id: twee keer ophalen is niet twee keer nieuws');
49 assert.deepEqual(c.to, ['https://www.w3.org/ns/activitystreams#Public']);
50});
51
52test('posts en tracks staan door elkaar op datum, nieuwste eerst', () => {
53 const ob = AP.buildOutbox(BASE, site(), [POST_NIEUW, POST_OUD], AP.siteOpenTracks('s1'));
54 assert.equal(ob.totalItems, 3);
55 assert.deepEqual(ob.orderedItems.map((c) => c.object.type), ['Note', 'Audio', 'Note'],
56 'juni-post, maart-track, januari-post');
57});
58
59test('een gesloten track staat er niet in, en lekt ook zijn titel niet', () => {
60 const ob = AP.buildOutbox(BASE, site(), [], AP.siteOpenTracks('s1'));
61 assert.ok(!JSON.stringify(ob).includes('Achter de poort'));
62 assert.ok(!JSON.stringify(ob).includes('dicht.mp3'));
63});
64
65test('zonder tracks-argument blijft de outbox precies zoals hij was', () => {
66 // De bouwer haalt NIETS zelf op: dat is wat de blocked-tak in de route
67 // beschermt. Zou hij dat wel doen, dan leverde hij dwars door een dichte
68 // deur heen.
69 const ob = AP.buildOutbox(BASE, site(), [POST_NIEUW]);
70 assert.equal(ob.totalItems, 1);
71 assert.equal(ob.orderedItems[0].object.type, 'Note');
72});
73
74test('de deur dicht: een lege outbox blijft leeg, ook met muziek in de kast', () => {
75 const ob = AP.buildOutbox(BASE, site(), [], []);
76 assert.equal(ob.totalItems, 0);
77 assert.deepEqual(ob.orderedItems, []);
78});
Note: See TracBrowser for help on using the repository browser.