| 1 | // De mixtape: een bandje als eigen soort (Robins idee, 21-8).
|
|---|
| 2 | //
|
|---|
| 3 | // Een playlist kon album of playlist zijn. Die keuze stond vijf keer als
|
|---|
| 4 | // `x === 'playlist' ? 'playlist' : 'album'` verspreid over drie bestanden, en
|
|---|
| 5 | // zo'n vorm valt bij een derde soort niet om -- hij SLIKT hem. Een mixtape zou
|
|---|
| 6 | // stilzwijgend een album worden en als Album de deur uit gaan. Die tests staan
|
|---|
| 7 | // hier dus voorop.
|
|---|
| 8 | import { test } from 'node:test';
|
|---|
| 9 | import assert from 'node:assert/strict';
|
|---|
| 10 |
|
|---|
| 11 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 12 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 13 | const dbMod = await import('../src/config/database.js');
|
|---|
| 14 | const db = dbMod.default;
|
|---|
| 15 | dbMod.initializeDatabase();
|
|---|
| 16 |
|
|---|
| 17 | const { normKind, isUitgave } = await import('../src/services/PlaylistService.js');
|
|---|
| 18 | const PlaylistService = (await import('../src/services/PlaylistService.js')).default;
|
|---|
| 19 | const { afleidenUitInsluitingen, SOORTEN } = await import('../src/assets/js/shared/post-music-type.js');
|
|---|
| 20 | const muziek = await import('../src/services/music/index.js');
|
|---|
| 21 |
|
|---|
| 22 | const BASE = 'https://test.example';
|
|---|
| 23 | const site = { id: 's1', slug: 'robo', title: 'Soundfabrics' };
|
|---|
| 24 |
|
|---|
| 25 | test('mixtape is een echte soort, en onbekend blijft album', () => {
|
|---|
| 26 | assert.equal(normKind('mixtape'), 'mixtape');
|
|---|
| 27 | assert.equal(normKind('MIXTAPE '), 'mixtape', 'invoer uit een formulier is niet genormaliseerd');
|
|---|
| 28 | assert.equal(normKind('cassette'), 'album', 'iets onbekends wordt geen nieuwe soort');
|
|---|
| 29 | assert.equal(normKind(undefined), 'album');
|
|---|
| 30 | assert.deepEqual(SOORTEN, ['album', 'playlist', 'mixtape']);
|
|---|
| 31 | });
|
|---|
| 32 |
|
|---|
| 33 | test('een bandje is geen uitgave: geen uitgavedatum, geen release-id', () => {
|
|---|
| 34 | assert.equal(isUitgave('album'), true);
|
|---|
| 35 | assert.equal(isUitgave('mixtape'), false);
|
|---|
| 36 | assert.equal(isUitgave('playlist'), false);
|
|---|
| 37 | });
|
|---|
| 38 |
|
|---|
| 39 | test('een post om een mixtape IS een mixtape', () => {
|
|---|
| 40 | const kindVan = (id) => (id === 'de-mixtape' ? 'mixtape' : null);
|
|---|
| 41 | const r = afleidenUitInsluitingen('<p>hier</p>[[playlist:de-mixtape]]', kindVan);
|
|---|
| 42 | assert.equal(r.type, 'mixtape');
|
|---|
| 43 | assert.equal(r.leentMetadata, true, 'de post leent zijn titel en hoes uit');
|
|---|
| 44 | });
|
|---|
| 45 |
|
|---|
| 46 | test('een mixtape met losse tracks erbij blijft een mixtape', () => {
|
|---|
| 47 | // De oude regel maakte van collectie + losse tracks een album. Voor een
|
|---|
| 48 | // uitgave klopt dat (bonustracks), voor een bandje niet.
|
|---|
| 49 | const kindVan = (id) => (id === 'tape' ? 'mixtape' : 'album');
|
|---|
| 50 | const tape = afleidenUitInsluitingen('[[playlist:tape]][[track:abc]]', kindVan);
|
|---|
| 51 | assert.equal(tape.type, 'mixtape');
|
|---|
| 52 | assert.deepEqual(tape.bonus, ['abc']);
|
|---|
| 53 |
|
|---|
| 54 | // En het oude gedrag voor een album blijft staan: bestaande posts mogen niet
|
|---|
| 55 | // stilletjes van soort wisselen.
|
|---|
| 56 | const plaat = afleidenUitInsluitingen('[[playlist:plaat]][[track:abc]]', kindVan);
|
|---|
| 57 | assert.equal(plaat.type, 'album');
|
|---|
| 58 | });
|
|---|
| 59 |
|
|---|
| 60 | test('het Mixtape-object is een enkel object met de nummers erin', () => {
|
|---|
| 61 | const rows = [
|
|---|
| 62 | { id: 't1', title: 'Kant A', duration: 100, filename: 'a.mp3', mime_type: 'audio/mpeg', size: 1 },
|
|---|
| 63 | { id: 't2', title: 'Kant B', duration: 44, filename: 'b.mp3', mime_type: 'audio/mpeg', size: 1 },
|
|---|
| 64 | ];
|
|---|
| 65 | const pl = { id: 'tape', title: 'De Mixtape', artist: 'robo', created_at: '2026-08-21T10:00:00Z' };
|
|---|
| 66 | const tape = muziek.buildMixtapeObject(BASE, site, pl, rows);
|
|---|
| 67 |
|
|---|
| 68 | // type is een STRING. Een array zou geldig AS2 zijn en toch fout: een lezer
|
|---|
| 69 | // die het als tekst uitpakt (Shaer) verliest dan stil het hele object.
|
|---|
| 70 | assert.equal(typeof tape.type, 'string');
|
|---|
| 71 | assert.equal(tape.type, 'Mixtape');
|
|---|
| 72 | assert.equal(tape.id, `${BASE}/ap/users/robo/playlists/tape`);
|
|---|
| 73 |
|
|---|
| 74 | // Een enkel object, samengesteld uit playlistelementen.
|
|---|
| 75 | assert.equal(tape.totalItems, 2);
|
|---|
| 76 | assert.equal(tape.orderedItems.length, 2);
|
|---|
| 77 | assert.equal(tape.orderedItems[0].type, 'Audio');
|
|---|
| 78 | assert.equal(tape.orderedItems[0].id, `${BASE}/ap/users/robo/tracks/t1`);
|
|---|
| 79 | assert.equal(tape.orderedItems[1].name, 'Kant B');
|
|---|
| 80 |
|
|---|
| 81 | // De lengte van het bandje: 100 + 44.
|
|---|
| 82 | assert.equal(tape.duration, 'PT144S');
|
|---|
| 83 |
|
|---|
| 84 | // Geen uitgavevelden. Die zouden beweren dat dit een plaat is.
|
|---|
| 85 | assert.equal(tape.released, undefined);
|
|---|
| 86 | assert.equal(tape.musicbrainzId, undefined);
|
|---|
| 87 | // En geen eigen stream: het is een omhulling, niet een gerenderd bestand.
|
|---|
| 88 | assert.equal(tape.url, undefined);
|
|---|
| 89 | });
|
|---|
| 90 |
|
|---|
| 91 | test('een gat in de duur levert geen verzonnen totaal op', () => {
|
|---|
| 92 | const rows = [
|
|---|
| 93 | { id: 't1', title: 'A', duration: 100, filename: 'a.mp3', mime_type: 'audio/mpeg', size: 1 },
|
|---|
| 94 | { id: 't2', title: 'B', duration: 0, filename: 'b.mp3', mime_type: 'audio/mpeg', size: 1 },
|
|---|
| 95 | ];
|
|---|
| 96 | const tape = muziek.buildMixtapeObject(BASE, site, { id: 'x', title: 'X' }, rows);
|
|---|
| 97 | assert.equal(tape.duration, undefined, 'liever geen veld dan een som met gaten');
|
|---|
| 98 | });
|
|---|
| 99 |
|
|---|
| 100 | test('de collectie van een mixtape draagt zijn velden, en blijft OrderedCollection', () => {
|
|---|
| 101 | const rows = [{ id: 't1', title: 'A', duration: 60, filename: 'a.mp3', mime_type: 'audio/mpeg', size: 1 }];
|
|---|
| 102 | const uit = muziek.buildPlaylistCollection(BASE, site, { id: 'tape', title: 'De Mixtape', kind: 'mixtape' }, rows);
|
|---|
| 103 | assert.equal(uit.type, 'OrderedCollection', 'het adres blijft een collectie voor wie hem zo ophaalt');
|
|---|
| 104 | assert.equal(typeof uit.type, 'string');
|
|---|
| 105 | assert.equal(uit.duration, 'PT60S', 'de mixtape-velden horen hier ook te staan');
|
|---|
| 106 | // Uitgavevelden horen er NIET op: dit is geen plaat.
|
|---|
| 107 | assert.equal(uit.released, undefined);
|
|---|
| 108 | assert.equal(uit.musicbrainzId, undefined);
|
|---|
| 109 | });
|
|---|
| 110 |
|
|---|
| 111 | test('opslaan en teruglezen houdt de soort vast', () => {
|
|---|
| 112 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 113 | .run('u1', 'baas', 'b@t.nl', 'x', 'god');
|
|---|
| 114 | db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
|
|---|
| 115 | .run('s1', 'robo', 'Soundfabrics', 'u1');
|
|---|
| 116 |
|
|---|
| 117 | const id = PlaylistService.create('s1', { title: 'De Mixtape', kind: 'mixtape', tracks: [] });
|
|---|
| 118 | assert.ok(id, 'aanmaken hoort te lukken');
|
|---|
| 119 | assert.equal(PlaylistService.get('s1', id).kind, 'mixtape', 'de soort mag niet terugvallen op album');
|
|---|
| 120 | assert.equal(PlaylistService.list('s1').find((p) => p.id === id).kind, 'mixtape');
|
|---|
| 121 |
|
|---|
| 122 | // En de server leidt het posttype er ook echt uit af, via de opzoeker die de
|
|---|
| 123 | // database raadpleegt -- niet via de meegegeven testfunctie hierboven.
|
|---|
| 124 | const r = muziek.postMusicType(`[[playlist:${id}]]`, 's1');
|
|---|
| 125 | assert.equal(r.type, 'mixtape');
|
|---|
| 126 | });
|
|---|