| 1 | // `fw:track` op onze Audio-objecten (shaer-3f8a, spoor B stap 1).
|
|---|
| 2 | //
|
|---|
| 3 | // Twee onafhankelijke implementaties lezen dit veld: Funkwhale (verplicht in
|
|---|
| 4 | // UploadSerializer) en Emissary 0.9.0, gemeten op bandwagon.fm 16-8. Emissary
|
|---|
| 5 | // stuurt de kleine vorm -- type, id, name, album, position -- en die nemen we
|
|---|
| 6 | // over, zonder album zolang dat bij ons geen object is (shaer-k37k).
|
|---|
| 7 | //
|
|---|
| 8 | // Wat deze test vastlegt is vooral het ID: het nummer krijgt een EIGEN
|
|---|
| 9 | // identiteit met #track, niet die van het bestand. Emissary hergebruikt daar
|
|---|
| 10 | // het object-id, en dan zijn de Audio en de Track in JSON-LD een knoop met twee
|
|---|
| 11 | // typen. Wie dat 'opruimt' naar het kortere id haalt dat verschil weg, en dan
|
|---|
| 12 | // staat er dat een mp3 een muziekstuk IS.
|
|---|
| 13 | import { test } from 'node:test';
|
|---|
| 14 | import assert from 'node:assert/strict';
|
|---|
| 15 |
|
|---|
| 16 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 17 | process.env.PUBLIC_BASE_URL = 'https://ons.test';
|
|---|
| 18 |
|
|---|
| 19 | const dbMod = await import('../src/config/database.js');
|
|---|
| 20 | const db = dbMod.default;
|
|---|
| 21 | dbMod.initializeDatabase();
|
|---|
| 22 | const AP = await import('../src/services/ActivityPubService.js');
|
|---|
| 23 |
|
|---|
| 24 | const BASE = 'https://ons.test';
|
|---|
| 25 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 26 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 27 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
|
|---|
| 28 | .run('s1', 'band', 'De Band', 'u1');
|
|---|
| 29 | const site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
|
|---|
| 30 |
|
|---|
| 31 | db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)')
|
|---|
| 32 | .run('m1', 's1', 'een.mp3', 'a/een.mp3', 'audio/mpeg', 2480880);
|
|---|
| 33 | db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, duration, media_id, fedi_open, position) VALUES (?,?,?,?,?,?,?,?)')
|
|---|
| 34 | .run('t1', 's1', 'Het nummer', 'De Band', 103, 'm1', 1, 3);
|
|---|
| 35 | // Eentje zonder positie: die mag het veld niet verzinnen.
|
|---|
| 36 | db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)')
|
|---|
| 37 | .run('m2', 's1', 'twee.mp3', 'a/twee.mp3', 'audio/mpeg', 100);
|
|---|
| 38 | db.prepare('INSERT INTO audio_tracks (id, site_id, title, media_id, fedi_open) VALUES (?,?,?,?,?)')
|
|---|
| 39 | .run('t2', 's1', 'Zonder plek', 'm2', 1);
|
|---|
| 40 |
|
|---|
| 41 | const audio = (id) => AP.buildTrackAudio(BASE, site, AP.openTrack('s1', id), { hostPosts: null });
|
|---|
| 42 |
|
|---|
| 43 | test('elke Audio draagt een track met de kleine Emissary-vorm', () => {
|
|---|
| 44 | const a = audio('t1');
|
|---|
| 45 | assert.ok(a.track, 'geen track op het object');
|
|---|
| 46 | assert.equal(a.track.type, 'Track');
|
|---|
| 47 | assert.equal(a.track.name, 'Het nummer');
|
|---|
| 48 | assert.equal(a.track.position, 3);
|
|---|
| 49 | });
|
|---|
| 50 |
|
|---|
| 51 | test('de track heeft een EIGEN id, niet dat van het bestand', () => {
|
|---|
| 52 | const a = audio('t1');
|
|---|
| 53 | assert.equal(a.track.id, `${a.id}#track`);
|
|---|
| 54 | assert.notEqual(a.track.id, a.id, 'een bestand is geen werk');
|
|---|
| 55 | });
|
|---|
| 56 |
|
|---|
| 57 | test('geen album zolang het bij ons geen object is', () => {
|
|---|
| 58 | // Een tekstkolom als URI meesturen is een adres beloven dat niet bestaat.
|
|---|
| 59 | assert.equal(audio('t1').track.album, undefined);
|
|---|
| 60 | });
|
|---|
| 61 |
|
|---|
| 62 | test('geen verzonnen positie', () => {
|
|---|
| 63 | assert.equal(audio('t2').track.position, undefined);
|
|---|
| 64 | assert.equal(audio('t2').track.name, 'Zonder plek');
|
|---|
| 65 | });
|
|---|
| 66 |
|
|---|
| 67 | test('de termen staan in de context, anders valt het veld weg', async () => {
|
|---|
| 68 | const core = await import('../src/services/ap-core.js');
|
|---|
| 69 | const term = core.AP_CONTEXT.find((x) => x && typeof x === 'object' && x.track);
|
|---|
| 70 | assert.ok(term, 'track staat in de context');
|
|---|
| 71 | // Letterlijk de vorm uit hun contexts.py (regel 293-306), zodat een lezer die
|
|---|
| 72 | // hun context laadt op dezelfde IRI's uitkomt als een lezer die de onze leest.
|
|---|
| 73 | assert.deepEqual(term.track, { '@id': 'fw:track', '@type': '@id' });
|
|---|
| 74 | assert.equal(term.Track, 'fw:Track');
|
|---|
| 75 | });
|
|---|
| 76 |
|
|---|
| 77 | test('de track komt overal mee, niet alleen op de losse ophaal', () => {
|
|---|
| 78 | // Bibliotheek, trackcollectie en losse ophaal bouwen alle drie via
|
|---|
| 79 | // buildTrackAudio -- dit is de test die dat vasthoudt als iemand er een
|
|---|
| 80 | // eigen bouwertje naast zet.
|
|---|
| 81 | const lib = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'));
|
|---|
| 82 | for (const a of lib.items) assert.equal(a.track.type, 'Track', `${a.name} zonder track`);
|
|---|
| 83 | const col = AP.buildTrackCollection(BASE, site, AP.siteOpenTracks('s1'));
|
|---|
| 84 | for (const a of col.orderedItems) assert.equal(a.track.type, 'Track', `${a.name} zonder track`);
|
|---|
| 85 | });
|
|---|