source: Klonkt/test/music-uitgave.test.js@ a5d14c7

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

De post is de uitgave: losse tracks worden een collectie (shaer-38y)

Losse tracks gingen tot nu toe los de deur uit -- Audio-objecten die een lezer
nergens kon plaatsen. Ze horen bij elkaar omdat ze in dezelfde post staan, en
dat is wat /ap/users/<slug>/posts/<id>/tracks nu zegt. De post wijst er langs
dezelfde weg naartoe als naar een playlist: een Link-tag in de Note, want zonder
die link bestaat de collectie wel maar vindt niemand hem.

PUNT 1, DE VOLGORDE. De tracks staan in de volgorde van de POST en niet die van
de tabel. Zoals iemand ze heeft neergezet is de volgorde waarin ze bedoeld zijn.

PUNT 3, DE METADATA. Een playlist heeft een titel en soms een hoes; een post
heeft een titel, een tekst, een hoes, tags en een datum. Voor audio-gebaseerde
inhoud is de post de uitgave -- dat is waar iemand hem heeft uitgebracht en waar
het verhaal erbij staat. Zowel de collectie van losse tracks als een
playlist-collectie leent nu van de post die haar uitbrengt.

De posttekst wordt content en niet summary: in AS2 is summary de korte
samenvatting en content het lijf. Artiest en jaar blijven dus in summary staan,
want dat IS een samenvatting en de posttekst is dat niet. De eigen naam van de
playlist gaat niet verloren maar verhuist naar alsoKnownAs.

WIE LEENT ER UIT: alleen een post die er EEN muzikale eenheid van maakt. Staan
er twee collecties in, dan is de post niet meer de drager van een identiteit en
houdt de playlist de zijne. Dezelfde regel als in de afleiding, hier alleen
toegepast.

Een test die eerder vastlegde dat losse tracks GEEN Link-tag kregen is
omgedraaid, met de reden erbij: dat klopte zolang alleen een playlist een
collectie had.

Punt 2 van de bead (type afleiden) stond al in postMusicType.

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

  • Property mode set to 100644
File size: 7.2 KB
Line 
1// De post als uitgave (shaer-38y).
2//
3// Drie punten uit de bead: de volgorde van de post behouden, de lijst als
4// geheel overnemen, en de metadata van de post lenen. Het derde is waar het om
5// draait -- een collectie met alleen een naam is wat een Funkwhale-achtige
6// lezer te mager vindt, en de post heeft wel een titel, tekst, hoes en tags.
7import { test } from 'node:test';
8import assert from 'node:assert/strict';
9
10process.env.DATABASE_PATH = ':memory:';
11process.env.PUBLIC_BASE_URL = 'https://test.example';
12
13const dbMod = await import('../src/config/database.js');
14dbMod.initializeDatabase();
15const db = dbMod.default;
16const M = await import('../src/services/music/index.js');
17
18const BASE = 'https://test.example';
19const SITE = 'site-1';
20const site = { id: SITE, slug: 'muziek' };
21
22db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
23 .run('u1', 'robin', 'r@test', 'x', 'god');
24db.prepare('INSERT INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)')
25 .run(SITE, 'muziek', 'Muziek', 'u1');
26
27let n = 0;
28function maakTrack(id, titel) {
29 const mediaId = 'm-' + id;
30 db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)')
31 .run(mediaId, SITE, id + '.mp3', '/x/' + id + '.mp3', 'audio/mpeg', 1000 + (n++));
32 db.prepare('INSERT INTO audio_tracks (id, site_id, media_id, title, duration, fedi_open, position) VALUES (?,?,?,?,?,1,?)')
33 .run(id, SITE, mediaId, titel, 100, 0);
34}
35
36function maakPost({ id, slug, titel, content, excerpt = '', cover = '', tags = '' }) {
37 db.prepare(`INSERT INTO posts (id, site_id, slug, author_id, title, excerpt, content, cover_image_url, tags, status, type, created_at, updated_at, published_at)
38 VALUES (?,?,?,'u1',?,?,?,?,?,'published','playlist',CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP)`)
39 .run(id, SITE, slug, titel, excerpt, content, cover, tags);
40 return db.prepare('SELECT id, slug, title, excerpt, content, cover_image_url, tags FROM posts WHERE id = ?').get(id);
41}
42
43// Drie tracks, bewust in een andere vololgorde ingevoerd dan ze in de post staan.
44maakTrack('t-een', 'Een');
45maakTrack('t-twee', 'Twee');
46maakTrack('t-drie', 'Drie');
47
48const losse = maakPost({
49 id: 'p-los', slug: 'drie-nieuwe', titel: 'Drie nieuwe nummers',
50 content: 'Hier: [[track:t-drie]] dan [[track:t-een]] en tot slot [[track:t-twee]]',
51 excerpt: 'Opgenomen op zolder, in een week.',
52 cover: '/media/hoes.jpg',
53 tags: 'lofi, zolder',
54});
55
56test('losse tracks in een post worden EEN collectie', () => {
57 const col = M.buildPostTrackCollection(BASE, site, losse);
58 assert.ok(col, 'er is een uitgave');
59 assert.equal(col.type, 'OrderedCollection');
60 assert.equal(col.id, `${BASE}/ap/users/muziek/posts/p-los/tracks`);
61 assert.equal(col.totalItems, 3);
62});
63
64test('in de volgorde van de POST, niet van de tabel', () => {
65 const col = M.buildPostTrackCollection(BASE, site, losse);
66 assert.deepEqual(col.orderedItems.map((a) => a.name), ['Drie', 'Een', 'Twee'],
67 'zoals iemand ze heeft neergezet is de volgorde waarin ze bedoeld zijn');
68});
69
70test('de post leent zijn titel, tekst, hoes en tags uit', () => {
71 const col = M.buildPostTrackCollection(BASE, site, losse);
72 assert.equal(col.name, 'Drie nieuwe nummers');
73 assert.equal(col.content, 'Opgenomen op zolder, in een week.');
74 assert.equal(col.image.type, 'Image');
75 assert.equal(col.image.url, `${BASE}/media/hoes.jpg`);
76 assert.deepEqual(col.tag.map((t) => t.name), ['#lofi', '#zolder']);
77 assert.equal(col.url, `${BASE}/drie-nieuwe`);
78 assert.equal(col.context, `${BASE}/ap/notes/p-los`);
79});
80
81test('en de tracks erin wijzen terug naar diezelfde post', () => {
82 const col = M.buildPostTrackCollection(BASE, site, losse);
83 for (const a of col.orderedItems) {
84 assert.equal(a.context, `${BASE}/ap/notes/p-los`);
85 assert.ok(a.url.some((u) => u.mediaType === 'text/html' && u.href === `${BASE}/drie-nieuwe`));
86 }
87});
88
89test('de post wijst met een Link-tag naar zijn eigen uitgave', () => {
90 const tags = M.playlistLinkTags(BASE, site, losse.content, losse);
91 const link = tags.find((t) => t.href === `${BASE}/ap/users/muziek/posts/p-los/tracks`);
92 assert.ok(link, 'zonder deze link bestaat de collectie wel maar vindt niemand hem');
93 assert.equal(link.mediaType, 'application/activity+json');
94});
95
96test('een post zonder losse tracks heeft geen eigen uitgave', () => {
97 const geen = maakPost({ id: 'p-leeg', slug: 'niets', titel: 'Niets', content: '<p>gewoon tekst</p>' });
98 assert.equal(M.buildPostTrackCollection(BASE, site, geen), null);
99});
100
101test('een gesloten track telt niet mee, ook niet voor het bestaan', () => {
102 maakTrack('t-dicht', 'Dicht');
103 db.prepare('UPDATE audio_tracks SET fedi_open = 0 WHERE id = ?').run('t-dicht');
104 const p = maakPost({ id: 'p-dicht', slug: 'dicht', titel: 'Dicht', content: '[[track:t-dicht]]' });
105 assert.equal(M.buildPostTrackCollection(BASE, site, p), null,
106 'geen lege collectie maar een collectie die niet bestaat');
107});
108
109// ── De playlist-collectie leent van de post die haar uitbrengt ────────────
110
111db.prepare('INSERT INTO playlists (id, site_id, title, artist, year, kind) VALUES (?,?,?,?,?,?)')
112 .run('nachtlicht', SITE, 'Nachtlicht', 'Robin', 2026, 'album');
113db.prepare('INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES (?,?,?)')
114 .run('nachtlicht', 't-een', 0);
115
116maakPost({
117 id: 'p-plaat', slug: 'de-plaat-is-er', titel: 'De plaat is er',
118 content: 'Eindelijk: [[playlist:nachtlicht]]',
119 excerpt: 'Anderhalf jaar aan gewerkt.',
120 cover: '/media/plaat.jpg', tags: 'album',
121});
122
123test('een playlist-collectie leent van de post die haar uitbrengt', () => {
124 const pl = db.prepare('SELECT id, title, artist, year, cover_url, kind FROM playlists WHERE id = ?').get('nachtlicht');
125 const col = M.buildPlaylistCollection(BASE, site, pl, M.playlistOpenTracks('nachtlicht'));
126 assert.equal(col.name, 'De plaat is er', 'de posttitel wordt de titel');
127 assert.equal(col.alsoKnownAs, 'Nachtlicht', 'de eigen naam gaat niet verloren');
128 assert.equal(col.content, 'Anderhalf jaar aan gewerkt.');
129 assert.equal(col.image.url, `${BASE}/media/plaat.jpg`);
130 assert.equal(col.summary, 'Robin · 2026', 'artiest en jaar blijven de samenvatting');
131 assert.equal(col.context, `${BASE}/ap/notes/p-plaat`);
132});
133
134test('een playlist zonder post leent niets', () => {
135 db.prepare('INSERT INTO playlists (id, site_id, title, kind) VALUES (?,?,?,?)')
136 .run('los', SITE, 'Los', 'playlist');
137 const pl = db.prepare('SELECT id, title, artist, year, cover_url, kind FROM playlists WHERE id = ?').get('los');
138 const col = M.buildPlaylistCollection(BASE, site, pl, []);
139 assert.equal(col.name, 'Los');
140 assert.equal(col.content, undefined);
141 assert.equal(M.uitgavePost(SITE, 'los'), null);
142});
143
144test('twee collecties in een post: dan leent de playlist niets', () => {
145 db.prepare('INSERT INTO playlists (id, site_id, title, kind) VALUES (?,?,?,?)')
146 .run('tweede', SITE, 'Tweede', 'playlist');
147 maakPost({
148 id: 'p-twee', slug: 'twee-lijsten', titel: 'Twee lijsten',
149 content: '[[playlist:tweede]] en [[playlist:nachtlicht]]',
150 });
151 // De post is dan geen drager van EEN identiteit meer -- dezelfde regel als in
152 // de afleiding, hier alleen toegepast op de lening.
153 assert.equal(M.uitgavePost(SITE, 'tweede'), null);
154});
Note: See TracBrowser for help on using the repository browser.