source: Klonkt/test/ap-fw-track.test.js@ 9a585bf

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

Track: published en artist_credit erbij

Twee van de vijf gaten naar Funkwhale's bibliotheek-ingest.

published was een omissie in de kleine Emissary-vorm, niet een keuze:
MusicEntitySerializer (regel 1278) eist het en we hebben created_at.

artist_credit leek de onmogelijke. Hun keten is Track -> ArtistCredit ->
Artist, en elke schakel wil id, name en published -- terwijl een artiest
bij ons een tekstkolom is. De MusicBrainz-koppeling van vorige week liet
zien dat dat niet klopt: de site-ACTOR is de artiest. Een echt,
opvraagbaar adres, met de sitetitel als naam en een musicbrainzId zodra
hij gekoppeld is. Er valt niets te verzinnen, en het is niet nieuw --
open.audio leidde op 13-8 al zelf een artist_credit af uit onze
attributedTo. We maken expliciet wat daar toch al gebeurde.

De artiestnaam van de track gaat naar credit en niet naar de entiteit.
Dat is waar hun model de credittekst verwacht, en er een id per
artiestnaam van maken zou identiteit uit een string zijn -- dezelfde fout
die we bij het album vermijden (shaer-756s).

@container: @list op artist_credit is geen opsmuk: ze lezen het veld met
first_attr(FW.artist_credit, "@list"), en zonder die declaratie
expandeert onze array er niet naar. Dan staat er iets dat er goed uitziet
en dat hun lezer niet vindt -- precies het soort stil gat waar deze week
al twee keer een dag in ging zitten.

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

  • Property mode set to 100644
File size: 6.9 KB
Line 
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.
13import { test } from 'node:test';
14import assert from 'node:assert/strict';
15
16process.env.DATABASE_PATH = ':memory:';
17process.env.PUBLIC_BASE_URL = 'https://ons.test';
18
19const dbMod = await import('../src/config/database.js');
20const db = dbMod.default;
21dbMod.initializeDatabase();
22const AP = await import('../src/services/ActivityPubService.js');
23
24const BASE = 'https://ons.test';
25db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
26 .run('u1', 'u1', 'u1@t', 'x', 'god');
27db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
28 .run('s1', 'band', 'De Band', 'u1');
29const site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
30
31db.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);
33db.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.
36db.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);
38db.prepare('INSERT INTO audio_tracks (id, site_id, title, media_id, fedi_open) VALUES (?,?,?,?,?)')
39 .run('t2', 's1', 'Zonder plek', 'm2', 1);
40
41const audio = (id) => AP.buildTrackAudio(BASE, site, AP.openTrack('s1', id), { hostPosts: null });
42
43test('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
51test('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
57test('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
62test('geen verzonnen positie', () => {
63 assert.equal(audio('t2').track.position, undefined);
64 assert.equal(audio('t2').track.name, 'Zonder plek');
65});
66
67test('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
77test('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});
86
87// ── artist_credit (shaer-3f8a, het gat naar hun ingest) ──────────────
88//
89// TrackSerializer (regel 1569) eist `artist_credit` met min_length=1, en elke
90// ArtistCredit eist een Artist die zelf id, name en published nodig heeft
91// (MusicEntitySerializer, regel 1278). Dat leek onmogelijk zolang een artiest
92// bij ons tekst was -- tot de MusicBrainz-koppeling liet zien dat de ACTOR de
93// artiest is.
94
95test('de track draagt een artist_credit met een echte Artist', () => {
96 const a = audio('t1');
97 assert.ok(Array.isArray(a.track.artist_credit), 'geen lijst');
98 assert.equal(a.track.artist_credit.length, 1);
99 const ac = a.track.artist_credit[0];
100 assert.equal(ac.type, 'ArtistCredit');
101 assert.ok(ac.id && ac.published, 'ArtistCredit mist id of published');
102 assert.equal(ac.artist.type, 'Artist');
103 assert.equal(ac.artist.id, `${BASE}/ap/users/band`, 'de actor IS de artiest');
104 assert.equal(ac.artist.name, 'De Band');
105 assert.ok(ac.artist.published, 'Artist mist published');
106});
107
108test('de artiestnaam van de track gaat naar credit, niet naar de entiteit', () => {
109 // De kolom is een credittekst, geen identiteit. Er een id van maken zou
110 // dezelfde fout zijn als bij het album (shaer-756s).
111 assert.equal(audio('t1').track.artist_credit[0].credit, 'De Band');
112 assert.equal(audio('t2').track.artist_credit[0].credit, undefined, 'geen artiest, geen credit');
113});
114
115test('de track heeft published -- MusicEntitySerializer eist het', () => {
116 assert.ok(audio('t1').track.published);
117});
118
119test('artist_credit is een @list in de context, anders vindt hun lezer niets', async () => {
120 // Ze lezen dit met first_attr(FW.artist_credit, "@list"). Zonder de
121 // container-declaratie expandeert onze array niet naar een @list en staat er
122 // iets dat er goed uitziet en niet gevonden wordt.
123 const core = await import('../src/services/ap-core.js');
124 const term = core.AP_CONTEXT.find((x) => x && typeof x === 'object' && x.artist_credit);
125 assert.deepEqual(term.artist_credit, { '@id': 'fw:artist_credit', '@type': '@id', '@container': '@list' });
126 assert.equal(term.ArtistCredit, 'fw:ArtistCredit');
127 assert.equal(term.Artist, 'fw:Artist');
128});
129
130test('musicbrainzId komt mee zodra de site gekoppeld is', () => {
131 assert.equal(audio('t1').track.artist_credit[0].artist.musicbrainzId, undefined);
132 db.prepare("UPDATE sites SET mb_artist_id = '5441c29d-3602-4898-b1a1-b77fa23b8e50' WHERE id = 's1'").run();
133 const s2 = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
134 const a = AP.buildTrackAudio(BASE, s2, AP.openTrack('s1', 't1'), { hostPosts: null });
135 assert.equal(a.track.artist_credit[0].artist.musicbrainzId, '5441c29d-3602-4898-b1a1-b77fa23b8e50');
136 db.prepare("UPDATE sites SET mb_artist_id = NULL WHERE id = 's1'").run();
137});
Note: See TracBrowser for help on using the repository browser.