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

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

Stap 3: de koppeling gaat de draad op als schema:sameAs (shaer-mbz)

Alleen als de artiest ZELF gekozen heeft. Staat er niets, dan staat er niets --
een lege of geraden verwijzing is erger dan geen.

WAAROM sameAs EN NIET alsoKnownAs, en dat is geen smaakkwestie. alsoKnownAs is
in AS2 gereserveerd voor vroegere IDENTITEITEN van dezelfde actor, en FEP-7628
leunt erop bij een verhuizing: het oude adres controleert of het nieuwe hem daar
noemt voordat het volgers meegeeft. Een MBID daartussen zetten zou een
verhuizing kunnen laten mislukken. schema:sameAs zegt precies het goede: dit
ding is ook bekend onder die URI.

Geen nieuwe namespace: schema stond al in onze context, naast license, position
en bitrate. Dit is er een termregel bij.

EEN KRINGLOOP VERMEDEN. MusicBrainzService leent safeFetch uit
ActivityPubService, dus die er andersom uit laten importeren zou een cirkel
maken. isMbid en artiestUrl zijn pure vormcontroles zonder afhankelijkheden en
wonen nu in ap-core -- dezelfde plek en dezelfde reden als de tagparsers van
gisteren. MusicBrainzService exporteert ze her, zodat een aanroeper er niet over
hoeft na te denken.

DE ACTOR IS DE LAATSTE DEUR. Het scherm zeeft al op een echte MBID, maar
artiestUrl doet het nog een keer: wat hier langskomt gaat naar iedereen, en een
half adres is erger dan geen. Een test zet met opzet rommel in de kolom en eist
dat er niets naar buiten gaat.

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

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