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

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

Stap 1: een artiest kan zichzelf opzoeken in MusicBrainz (shaer-mbz)

Waarom dit geen dialect is en Funkwhale's Track/ArtistCredit wel: een MBID is
geen vocabulaire maar een REGISTER. Ernaar verwijzen is als een ISBN noemen --
je neemt niemands model over en je wijst naar iets dat al bestaat. En het is de
brug die Funkwhale zelf al kent, want hun Track draagt musicbrainzId.

WAT ER STAAT

  • MusicBrainzService: zoeken op artiestennaam, kandidaten met hun disambiguatie, soort, land en jaren erbij. De naam alleen is niet genoeg om te kiezen -- er zijn drie bands die Nirvana heten, en dat is precies waar hun disambiguation-veld voor is.
  • mb_artist_id en mb_artist_name op sites. De naam erbij zodat het scherm kan tonen WAT er gekoppeld is zonder ervoor te netwerken, en zodat een verkeerde koppeling opvalt.
  • GET /admin/audio/api/musicbrainz, dat standaard zoekt op de artiestennaam die al in de site staat.

HUN TWEE HARDE REGELS, INGEBAKKEN EN GETEST. Overtreden leidt tot een BLOKKADE
en niet tot een foutmelding, dus "het werkte toen ik het probeerde" is er geen
bewijs voor:

  • hoogstens EEN verzoek per seconde, over de hele applicatie. Daarom draait de zoekopdracht server-side: die regel geldt per applicatie en niet per bezoeker, dus twee tabbladen zouden hem samen overtreden. Een test doet twee zoekopdrachten tegelijk en eist een gat van een seconde.
  • een echte User-Agent met contactgegevens. Een test leest hem uit het verzoek.

DE KEUZE BLIJFT VAN DE ARTIEST. We tonen kandidaten; we kiezen er niet zelf een,
ook niet als er maar een treffer is. Een verkeerd geraden MBID koppelt iemand aan
het werk van een ander, en dat is erger dan geen koppeling.

WAT ER NIET IN ZIT: schrijven naar MusicBrainz. Kan niet via hun API voor
artiesten -- alleen tags, ratings, ISRC's en barcodes -- en zou ook niet moeten.

Zonder netwerk getest: globalThis.fetch vervangen en musicbrainz.org in
AP_ALLOW_HOSTS, zodat er geen DNS aan te pas komt. Anders test het of deze
machine internet heeft.

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

  • Property mode set to 100644
File size: 4.4 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; });
Note: See TracBrowser for help on using the repository browser.