Index: test/ap-fw-track.test.js
===================================================================
--- test/ap-fw-track.test.js	(revision a982e7eea932c92389eb43741ea6f66181756dd3)
+++ test/ap-fw-track.test.js	(revision 21257e41df74e4553f4fb556b7e9fa030bffb2a3)
@@ -84,2 +84,54 @@
   for (const a of col.orderedItems) assert.equal(a.track.type, 'Track', `${a.name} zonder track`);
 });
+
+// ── artist_credit (shaer-3f8a, het gat naar hun ingest) ──────────────
+//
+// TrackSerializer (regel 1569) eist `artist_credit` met min_length=1, en elke
+// ArtistCredit eist een Artist die zelf id, name en published nodig heeft
+// (MusicEntitySerializer, regel 1278). Dat leek onmogelijk zolang een artiest
+// bij ons tekst was -- tot de MusicBrainz-koppeling liet zien dat de ACTOR de
+// artiest is.
+
+test('de track draagt een artist_credit met een echte Artist', () => {
+  const a = audio('t1');
+  assert.ok(Array.isArray(a.track.artist_credit), 'geen lijst');
+  assert.equal(a.track.artist_credit.length, 1);
+  const ac = a.track.artist_credit[0];
+  assert.equal(ac.type, 'ArtistCredit');
+  assert.ok(ac.id && ac.published, 'ArtistCredit mist id of published');
+  assert.equal(ac.artist.type, 'Artist');
+  assert.equal(ac.artist.id, `${BASE}/ap/users/band`, 'de actor IS de artiest');
+  assert.equal(ac.artist.name, 'De Band');
+  assert.ok(ac.artist.published, 'Artist mist published');
+});
+
+test('de artiestnaam van de track gaat naar credit, niet naar de entiteit', () => {
+  // De kolom is een credittekst, geen identiteit. Er een id van maken zou
+  // dezelfde fout zijn als bij het album (shaer-756s).
+  assert.equal(audio('t1').track.artist_credit[0].credit, 'De Band');
+  assert.equal(audio('t2').track.artist_credit[0].credit, undefined, 'geen artiest, geen credit');
+});
+
+test('de track heeft published -- MusicEntitySerializer eist het', () => {
+  assert.ok(audio('t1').track.published);
+});
+
+test('artist_credit is een @list in de context, anders vindt hun lezer niets', async () => {
+  // Ze lezen dit met first_attr(FW.artist_credit, "@list"). Zonder de
+  // container-declaratie expandeert onze array niet naar een @list en staat er
+  // iets dat er goed uitziet en niet gevonden wordt.
+  const core = await import('../src/services/ap-core.js');
+  const term = core.AP_CONTEXT.find((x) => x && typeof x === 'object' && x.artist_credit);
+  assert.deepEqual(term.artist_credit, { '@id': 'fw:artist_credit', '@type': '@id', '@container': '@list' });
+  assert.equal(term.ArtistCredit, 'fw:ArtistCredit');
+  assert.equal(term.Artist, 'fw:Artist');
+});
+
+test('musicbrainzId komt mee zodra de site gekoppeld is', () => {
+  assert.equal(audio('t1').track.artist_credit[0].artist.musicbrainzId, undefined);
+  db.prepare("UPDATE sites SET mb_artist_id = '5441c29d-3602-4898-b1a1-b77fa23b8e50' WHERE id = 's1'").run();
+  const s2 = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
+  const a = AP.buildTrackAudio(BASE, s2, AP.openTrack('s1', 't1'), { hostPosts: null });
+  assert.equal(a.track.artist_credit[0].artist.musicbrainzId, '5441c29d-3602-4898-b1a1-b77fa23b8e50');
+  db.prepare("UPDATE sites SET mb_artist_id = NULL WHERE id = 's1'").run();
+});
