source: Klonkt/test/musicbrainz.test.js@ f50a84b

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

Jouw teksten, en de twee dingen die ze beloofden (shaer-mbz)

De titel is nu "MusicBrainz Koppeling" met jouw omschrijving eronder, in drie
talen. Maar die omschrijving belooft twee dingen die de code nog niet deed, en
een label dat liegt is erger dan een lelijk label. Dus allebei gebouwd.

"OF JE MUSICBRAINZ ID ALS JE DIE KENT". Plak je een UUID, dan wordt hij
rechtstreeks opgezocht in plaats van gezocht. Dat is geen luxe: een
zoekopdracht OP een UUID levert bij MusicBrainz niets op, dus zonder deze tak
gaf plakken juist het slechtste resultaat.

"MET TERUG-WEG VALIDATIE". Een koppeling van onze kant is een bewering --
iedereen kan een id in een veld typen. Pas als de MusicBrainz-pagina TERUGWIJST
naar dit domein is het een paar, en dan weet een lezer dat dezelfde persoon aan
allebei de kanten stond. Dezelfde gedachte als rel="me". Het paneel zegt
"wederzijds" of "nog eenzijdig", en bij dat laatste staat erbij WAT je moet doen:
het domein op je MusicBrainz-pagina zetten onder "social networking".

Wij zetten die terugwijzing niet zelf. Kan niet via hun API, en hoort ook niet.

ECHT GEMETEN, en dat gaf de test zijn vorm: robo-burr staat in MusicBrainz met
score 100 (24abe2be-c0bc-4c63-9642-d6f89ec6a00a, "AKA roboburr") en heeft
precies EEN url-relatie -- type "social network", naar sound-fabrics.com. Voor
dev.klonkt.com is de terug-weg dus false, en dat is precies de waarde van de
controle. Die vorm staat nu in een test.

Bij een storing komt er false EN een lege lijst uit: niet kunnen kijken is niet
hetzelfde als niet gevonden, en nooit stilletjes "wederzijds".

Een controle erbij die eist dat alle drie de talen dezelfde mb_-sleutels
hebben. Bij het herschrijven waren er zeven weggevallen die het scherm nog
gebruikt -- een gat in een vertaling geeft een leeg label en dat valt nooit
vanzelf op.

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

  • Property mode set to 100644
File size: 10.2 KB
Line 
1// De MusicBrainz-koppeling, stap 1 (shaer-mbz).
2//
3// Zonder netwerk: globalThis.fetch wordt vervangen en musicbrainz.org staat in
4// AP_ALLOW_HOSTS, zodat safeFetch geen DNS doet. Anders test dit of de
5// testmachine internet heeft en of MusicBrainz toevallig up is.
6//
7// Wat hier WEL getest wordt is wat wij beloven: hun twee harde regels. Die
8// leiden bij overtreding tot een BLOKKADE en niet tot een foutmelding, dus
9// "het werkte toen ik het probeerde" is er geen bewijs voor.
10import { test } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14process.env.PUBLIC_BASE_URL = 'https://ons.test';
15process.env.MUSICBRAINZ_CONTACT = 'robin@ons.test';
16process.env.AP_ALLOW_HOSTS = 'musicbrainz.org';
17
18const dbMod = await import('../src/config/database.js');
19dbMod.initializeDatabase();
20const MB = await import('../src/services/MusicBrainzService.js');
21
22const verzoeken = [];
23const echt = globalThis.fetch;
24globalThis.fetch = async (url, opts) => {
25 verzoeken.push({ url: String(url), ua: opts?.headers?.['User-Agent'], op: Date.now() });
26 return {
27 ok: true,
28 status: 200,
29 headers: new Map(),
30 json: async () => ({
31 artists: [
32 { id: '8be31978-1884-4773-beae-f73df35b92aa', name: 'Nirvana', score: 100,
33 disambiguation: 'Seattle grunge band', type: 'Group', country: 'US',
34 'life-span': { begin: '1987', ended: true, end: '1994' } },
35 { id: 'aaaaaaaa-1111-2222-3333-444444444444', name: 'Nirvana', score: 72,
36 disambiguation: '60s UK band', type: 'Group', country: 'GB' },
37 { id: 'bad', name: '' }, // onbruikbaar: moet eruit vallen
38 ],
39 }),
40 };
41};
42
43test('een MBID is een UUID en niets anders', () => {
44 assert.equal(MB.isMbid('8be31978-1884-4773-beae-f73df35b92aa'), true);
45 assert.equal(MB.isMbid('the_ceeesg'), false, 'een handle is geen MBID');
46 assert.equal(MB.isMbid('https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa'), false,
47 'een URL ook niet -- anders sluipt er een hele link de kolom in');
48 assert.equal(MB.isMbid(''), false);
49 assert.equal(MB.isMbid(null), false);
50});
51
52test('de artiest-URL wordt alleen uit een echte MBID gebouwd', () => {
53 assert.equal(MB.artiestUrl('8BE31978-1884-4773-BEAE-F73DF35B92AA'),
54 'https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa',
55 'kleingeschreven, want een MBID is er een en niet twee');
56 assert.equal(MB.artiestUrl('rommel'), null, 'geen URL verzinnen om iets onbekends heen');
57});
58
59test('een lege zoekopdracht raakt het net niet eens', async () => {
60 verzoeken.length = 0;
61 assert.deepEqual(await MB.zoekArtiesten(' '), []);
62 assert.equal(verzoeken.length, 0, 'hun tempo is te kostbaar om aan niets te besteden');
63});
64
65test('de User-Agent noemt Klonkt EN een contact -- hun eis', async () => {
66 // Een generieke of lege User-Agent is precies waarop MusicBrainz blokkeert.
67 verzoeken.length = 0;
68 await MB.zoekArtiesten('iemand');
69 const ua = verzoeken.at(-1)?.ua;
70 assert.ok(ua, 'er is er een gezet');
71 assert.match(ua, /^Klonkt\//, 'met onze naam voorop');
72 assert.match(ua, /robin@ons\.test/, 'en een manier om contact op te nemen');
73});
74
75test('de kandidaten dragen wat een mens nodig heeft om te KIEZEN', async () => {
76 const uit = await MB.zoekArtiesten('Nirvana');
77 assert.equal(uit.length, 2, 'een naamloze treffer is geen kandidaat');
78 assert.equal(uit[0].mbid, '8be31978-1884-4773-beae-f73df35b92aa');
79 // De naam alleen is niet genoeg: er zijn drie bands die Nirvana heten.
80 assert.equal(uit[0].toelichting, 'Seattle grunge band');
81 assert.equal(uit[1].toelichting, '60s UK band');
82 assert.equal(uit[0].jaren, '1987 – 1994');
83 assert.equal(uit[1].jaren, '', 'een band die nog bestaat krijgt geen eindjaar aangepraat');
84 assert.equal(uit[0].url, 'https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa');
85});
86
87test('twee zoekopdrachten liggen minstens een seconde uit elkaar', async () => {
88 // Hun harde regel, en de reden dat dit server-side draait: een verzoek per
89 // seconde geldt per APPLICATIE, niet per bezoeker. Twee tabbladen tegelijk
90 // zouden hem anders samen overtreden.
91 verzoeken.length = 0;
92 await Promise.all([MB.zoekArtiesten('een'), MB.zoekArtiesten('twee')]);
93 assert.equal(verzoeken.length, 2);
94 const gat = verzoeken[1].op - verzoeken[0].op;
95 assert.ok(gat >= 990, `verwacht >= 1000ms tussen twee verzoeken, was ${gat}ms`);
96});
97
98test.after(() => { globalThis.fetch = echt; });
99
100// ── Stap 2: de keuze vastleggen ───────────────────────────────────────────
101
102test('alleen een echte MBID komt de kolom in', async () => {
103 // De route weigert alles wat geen UUID is. Zonder die zeef sluipt er een
104 // hele URL of een handle in het veld dat straks naar buiten gaat.
105 const db = dbMod.default;
106 db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
107 .run('u1', 'u1', 'u1@t', 'x', 'god');
108 db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('s1', 'band', 'Band', 'u1');
109 for (const rommel of ['https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa', 'nirvana', '']) {
110 assert.equal(MB.isMbid(rommel), false, `${rommel} hoort geweigerd te worden`);
111 }
112 db.prepare('UPDATE sites SET mb_artist_id = ?, mb_artist_name = ? WHERE id = ?')
113 .run('8be31978-1884-4773-beae-f73df35b92aa', 'Nirvana', 's1');
114 const s = db.prepare("SELECT mb_artist_id, mb_artist_name FROM sites WHERE id = 's1'").get();
115 assert.equal(s.mb_artist_id, '8be31978-1884-4773-beae-f73df35b92aa');
116 assert.equal(s.mb_artist_name, 'Nirvana', 'de naam ernaast, zodat het scherm kan tonen WAT er hangt');
117});
118
119test('ontkoppelen maakt beide velden leeg', () => {
120 const db = dbMod.default;
121 db.prepare('UPDATE sites SET mb_artist_id = NULL, mb_artist_name = NULL WHERE id = ?').run('s1');
122 const s = db.prepare("SELECT mb_artist_id, mb_artist_name FROM sites WHERE id = 's1'").get();
123 assert.equal(s.mb_artist_id, null);
124 assert.equal(s.mb_artist_name, null, 'anders blijft er een naam staan zonder koppeling');
125});
126
127// ── Stap 3: op de draad ───────────────────────────────────────────────────
128
129test('de actor draagt sameAs zodra er gekoppeld is -- en anders niets', async () => {
130 const db = dbMod.default;
131 const AP = await import('../src/services/ActivityPubService.js');
132 const BASE = 'https://ons.test';
133 const kaal = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
134 assert.equal(AP.buildActor(BASE, kaal).sameAs, undefined,
135 'zonder koppeling staat er niets -- een lege verwijzing is erger dan geen');
136
137 db.prepare('UPDATE sites SET mb_artist_id = ? WHERE id = ?')
138 .run('8be31978-1884-4773-beae-f73df35b92aa', 's1');
139 const gekoppeld = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
140 const actor = AP.buildActor(BASE, gekoppeld);
141 assert.equal(actor.sameAs, 'https://musicbrainz.org/artist/8be31978-1884-4773-beae-f73df35b92aa');
142});
143
144test('rommel in de kolom komt NIET op de draad', () => {
145 // Het scherm zeeft al, maar de actor is de laatste deur. Wat hier langskomt
146 // gaat naar iedereen, en een half adres is erger dan geen.
147 const db = dbMod.default;
148 db.prepare('UPDATE sites SET mb_artist_id = ? WHERE id = ?').run('nirvana', 's1');
149 const s = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
150 assert.equal(MB.artiestUrl(s.mb_artist_id), null);
151});
152
153test('sameAs is een eigen term en NIET alsoKnownAs', async () => {
154 // alsoKnownAs is in AS2 voor vroegere IDENTITEITEN van dezelfde actor, en
155 // FEP-7628 leunt erop bij een verhuizing: het oude adres controleert of het
156 // nieuwe hem daar noemt. Een MBID daar neerzetten zou een verhuizing kunnen
157 // laten mislukken.
158 const core = await import('../src/services/ap-core.js');
159 const term = core.AP_CONTEXT.find((x) => x && typeof x === 'object' && x.sameAs);
160 assert.ok(term, 'de term is gedeclareerd, anders laat een strikte lezer hem vallen');
161 assert.equal(term.sameAs['@id'], 'schema:sameAs');
162 assert.equal(term.sameAs['@type'], '@id', 'het is een URI en geen tekst');
163});
164
165// ── De terug-weg (Robins "social networking"-validatie) ───────────────────
166
167test('een MBID in het zoekveld wordt rechtstreeks opgezocht', async () => {
168 // Zoeken op een UUID levert bij MusicBrainz niets op, dus zonder deze tak
169 // geeft plakken juist het slechtste resultaat.
170 globalThis.fetch = async (url) => {
171 verzoeken.push({ url: String(url) });
172 return { ok: true, json: async () => ({ id: '8be31978-1884-4773-beae-f73df35b92aa', name: 'robo-burr', disambiguation: 'AKA roboburr' }) };
173 };
174 verzoeken.length = 0;
175 const een = await MB.haalArtiest('8be31978-1884-4773-beae-f73df35b92aa');
176 assert.equal(een.naam, 'robo-burr');
177 assert.match(verzoeken[0].url, /\/artist\/8be31978-1884-4773-beae-f73df35b92aa\?/, 'de lookup, niet de zoekopdracht');
178 assert.equal(await MB.haalArtiest('nirvana'), null, 'geen MBID, geen lookup');
179});
180
181test('de terug-weg is pas waar als de pagina ONS domein noemt', async () => {
182 // Echt gemeten op 13-8: robo-burr heeft precies een url-relatie, type
183 // "social network", naar sound-fabrics.com. Dus voor dev.klonkt.com hoort
184 // hier false uit te komen -- en dat is de waarde van de controle.
185 globalThis.fetch = async () => ({
186 ok: true,
187 json: async () => ({ relations: [{ type: 'social network', url: { resource: 'https://sound-fabrics.com/' } }] }),
188 });
189 const mbid = '24abe2be-c0bc-4c63-9642-d6f89ec6a00a';
190 const mis = await MB.controleerTerugweg(mbid, 'https://dev.klonkt.com');
191 assert.equal(mis.verified, false, 'een andere site is geen terugweg');
192 assert.deepEqual(mis.urls, ['https://sound-fabrics.com/']);
193
194 const raak = await MB.controleerTerugweg(mbid, 'https://sound-fabrics.com');
195 assert.equal(raak.verified, true, 'dezelfde host telt, ongeacht pad of slash');
196});
197
198test('niet kunnen kijken is niet hetzelfde als niet gevonden', async () => {
199 // Bij een storing false EN een lege lijst -- nooit stilletjes "wederzijds".
200 globalThis.fetch = async () => { throw new Error('weg'); };
201 const uit = await MB.controleerTerugweg('24abe2be-c0bc-4c63-9642-d6f89ec6a00a', 'https://dev.klonkt.com');
202 assert.deepEqual(uit, { verified: false, urls: [] });
203});
Note: See TracBrowser for help on using the repository browser.