source: Klonkt/test/fedi-open-per-track.test.js@ ca260d1

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

Fedi open audio per track, vanuit de mediamanager

Robin, 30-9: "Fedi open audio ook per track aan kunnen zetten in de media
manager."

De vlag was altijd al per track (audio_tracks.fedi_open): hij opent het
bestand en maakt er een echte Audio-bijlage van, met een speler in Mastodon en
consorten. Aanzetten kon alleen per POST, met het vinkje in de editor, dat elke
track in die post opende. Nu staat er in de mediamanager, tab Audio, per track
een wereldbol.

EENRICHTINGS, net als in de editor. Dicht is het een gedimde knop die eerst
vraagt of het zeker is, want terug kan niet; open is het een slotje in de
accentkleur en geen knop meer. Een bestand dat al gefedereerd is staat op andere
servers, en het hier weer afschermen zou schijnveiligheid zijn. Ook de algemene
bijwerk-API van een track neemt fedi_open niet aan; een toets bewaakt dat.

Alleen bij een track met een eigen bestand: een track die alleen een link is,
heeft niets om te openen. Een formulier en geen fetch, dus het werkt zonder
JavaScript, en data-confirm uit shell.ejs stelt de vraag.

EN DE POSTS GAAN OPNIEUW DE DEUR UIT. Het vinkje in de editor werkt zichtbaar
omdat het opslaan van de post een Update stuurt, met de audio erin. Een knop
die alleen de vlag zet, laat elke post die al verstuurd is zonder speler staan
tot iemand hem bewerkt. Dus hier dezelfde Update, voor elke gepubliceerde post
van deze site waar de track in staat: rechtstreeks, via een playlist of via zijn
album. postsEmbeddingTrack in music/index.js zoekt die op; trackHostPosts kiest
per track maar een post, voor de bibliotheek. De bibliotheek zelf hoeft niets,
die wordt opgehaald en een open track staat er meteen in.

Getoetst: openen zet de vlag en stuurt precies de drie posts (geen concept,
geen post zonder de track, geen post van een andere site); nog eens openen doet
niets; een link-track en een track van een andere site blijven dicht; de route;
geen weg terug via de API; de knop alleen waar het kan, met bevestiging; en de
hulpfunctie rechtstreeks. Tegenproef per zaak. Eentje ving eerst niets: het
sitefilter zit dubbel, want openen haalt elke post nog eens op met site_id. De
hulpfunctie is los te gebruiken, dus die wordt nu ook rechtstreeks getoetst.
Volledige suite 1301 groen.

Nagekeken in de gerenderde mediamanager met de echte stylesheet: open in de
accentkleur, dicht gedimd, geen wereldbol bij een link-track, dezelfde maat als
de andere knoppen in de rij.

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

  • Property mode set to 100644
File size: 7.8 KB
RevLine 
[ca260d1]1// Fedi open per track, vanuit de mediamanager (Robin, 30-9).
2//
3// De vlag was altijd al per track (audio_tracks.fedi_open), maar aanzetten kon
4// alleen per POST, met het vinkje in de editor. Nu ook per track. Wat hier
5// bewaakt wordt:
6//
7// 1. openen zet de vlag en stuurt een Update voor ELKE gepubliceerde post van
8// deze site waar de track in staat -- rechtstreeks, via album, via playlist.
9// Anders krijgt een volger de speler pas bij de volgende bewerking.
10// 2. EENRICHTINGS: er is geen weg terug, ook niet via de algemene bijwerk-API.
11// 3. alleen een track met een eigen bestand, en alleen van deze site.
12import { test } from 'node:test';
13import assert from 'node:assert/strict';
14import os from 'os';
15import path from 'path';
16
17process.env.DATABASE_PATH = ':memory:';
18process.env.PUBLIC_BASE_URL = 'https://test.example';
19process.env.MEDIA_PATH = path.join(os.tmpdir(), 'klonkt-test-media');
20
21const dbMod = await import('../src/config/database.js');
22const db = dbMod.default;
23{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
24const audioMod = await import('../src/routes/admin-audio.js');
25const { openTrackOnFediverse } = audioMod;
26
27db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@t', 'x', 'god');
28db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)').run('s1', 'band', 'De Band', 'u1');
29db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)').run('s2', 'ander', 'Andere', 'u1');
30const site = (id) => db.prepare('SELECT * FROM sites WHERE id = ?').get(id);
31
32const insMedia = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1000)');
33const insTrack = db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, album, duration, media_id, fedi_open) VALUES (?,?,?,?,?,?,?,0)');
34insMedia.run('m1', 's1', 'zwaluwen.mp3', 'audio/zwaluwen.mp3', 'audio/mpeg');
35insMedia.run('m2', 's1', 'stil.mp3', 'audio/stil.mp3', 'audio/mpeg');
36insMedia.run('mb', 's2', 'buur.mp3', 'audio/buur.mp3', 'audio/mpeg');
37insTrack.run('ta1', 's1', 'Zwaluwen', 'De Band', 'Voorjaar', 200, 'm1');
38insTrack.run('ta3', 's1', 'Stil', 'De Band', null, 90, 'm2'); // hangt in geen enkele post
39insTrack.run('link', 's1', 'Alleen een link', 'De Band', null, 0, null);
40insTrack.run('tb1', 's2', 'Van de buurman', 'Andere', null, 210, 'mb');
41
42db.prepare('INSERT INTO playlists (id, site_id, title) VALUES (?,?,?)').run('pl1', 's1', 'Lente');
43db.prepare('INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES (?,?,0)').run('pl1', 'ta1');
44
45const insPost = db.prepare('INSERT INTO posts (id, site_id, slug, title, content, status, author_id) VALUES (?,?,?,?,?,?,?)');
46insPost.run('p-direct', 's1', 'direct', 'Direct', 'Luister: [[track:ta1]]', 'published', 'u1');
47insPost.run('p-album', 's1', 'album', 'Album', '[[album:Voorjaar]]', 'published', 'u1');
48insPost.run('p-playlist', 's1', 'lijst', 'Lijst', '[[playlist:pl1]]', 'published', 'u1');
49insPost.run('p-concept', 's1', 'concept', 'Concept', '[[track:ta1]]', 'draft', 'u1');
50insPost.run('p-anders', 's1', 'anders', 'Anders', 'Geen muziek', 'published', 'u1');
51// Een post op een ANDERE site met het id van onze track: die is niet van ons.
52insPost.run('q-buur', 's2', 'buur', 'Buur', '[[track:ta1]]', 'published', 'u1');
53
54const open = (id) => db.prepare('SELECT fedi_open FROM audio_tracks WHERE id = ?').get(id).fedi_open;
55
56const { postsEmbeddingTrack } = await import('../src/services/music/index.js');
57
58test('de posts met een track: direct, via album, via playlist, en alleen van deze site', () => {
59 // Rechtstreeks getoetst, en niet alleen via het openen: daar vangt een tweede
60 // controle (post opnieuw ophalen met site_id) een fout hier nog op, maar deze
61 // functie is los te gebruiken, en een volgende aanroeper heeft die tweede
62 // wacht misschien niet.
63 assert.deepEqual(postsEmbeddingTrack('s1', 'ta1').sort(), ['p-album', 'p-direct', 'p-playlist']);
64 assert.deepEqual(postsEmbeddingTrack('s2', 'ta1'), [], 'een track van een andere site hoort bij niemand hier');
65});
66
67test('openen zet de vlag en stuurt elke post met de track opnieuw de deur uit', async () => {
68 const verstuurd = [];
69 const uit = openTrackOnFediverse(site('s1'), 'ta1', { deliver: (s, p) => { verstuurd.push(p.id); } });
70 await new Promise((r) => setTimeout(r, 20));
71 assert.equal(uit.ok, true);
72 assert.equal(uit.changed, true);
73 assert.equal(open('ta1'), 1);
74 assert.deepEqual(verstuurd.sort(), ['p-album', 'p-direct', 'p-playlist'],
75 'direct, via album en via playlist; geen concept, geen post zonder de track, geen post van een andere site');
76});
77
78test('nog een keer openen verandert niets en verstuurt niets', async () => {
79 const verstuurd = [];
80 const uit = openTrackOnFediverse(site('s1'), 'ta1', { deliver: (s, p) => { verstuurd.push(p.id); } });
81 await new Promise((r) => setTimeout(r, 20));
82 assert.equal(uit.changed, false);
83 assert.deepEqual(verstuurd, []);
84});
85
86test('een track die alleen een link is, heeft niets om te openen', () => {
87 const uit = openTrackOnFediverse(site('s1'), 'link', { deliver: () => {} });
88 assert.deepEqual([uit.ok, uit.reason], [false, 'no_file']);
89 assert.equal(open('link'), 0);
90});
91
92test('de track van een andere site blijft dicht', () => {
93 const uit = openTrackOnFediverse(site('s1'), 'tb1', { deliver: () => {} });
94 assert.deepEqual([uit.ok, uit.reason], [false, 'not_found']);
95 assert.equal(open('tb1'), 0, 'fedi_open is eenrichtings: een verkeerd geopend bestand haal je niet terug');
96});
97
98// ── Via de routes ────────────────────────────────────────────────────────────
99const express = (await import('express')).default;
100const app = express();
101app.use((req, res, next) => {
102 res.locals.site = site('s1');
103 res.locals.siteUrlBase = '';
104 req.session = { user: { id: 'u1', role: 'god', username: 'u1' } };
105 next();
106});
107app.use('/admin/audio', audioMod.default);
108const server = app.listen(0);
109server.unref();
110const base = `http://127.0.0.1:${server.address().port}`;
111test.after(() => server.close());
112
113test('de knop in de mediamanager opent de track', async () => {
114 const r = await fetch(`${base}/admin/audio/ta3/fedi-open`, { method: 'POST', redirect: 'manual' });
115 assert.equal(r.status, 302);
116 assert.match(r.headers.get('location') || '', /^\/admin\/audio\?success=/);
117 assert.equal(open('ta3'), 1);
118});
119
120test('er is geen weg terug, ook niet via de algemene bijwerk-API', async () => {
121 const r = await fetch(`${base}/admin/audio/api/ta1`, {
122 method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fedi_open: false }),
123 });
124 assert.notEqual(r.status, 200, 'de API neemt fedi_open niet aan');
125 assert.equal(open('ta1'), 1, 'en de track blijft open');
126});
127
128test('de lijst biedt openen alleen aan waar het kan', async () => {
129 const html = await (await fetch(`${base}/admin/audio`)).text();
130 assert.ok(!html.includes('/admin/audio/link/fedi-open'), 'een link-track krijgt geen knop');
131 assert.ok(!html.includes('/admin/audio/ta1/fedi-open'), 'een open track krijgt geen knop meer, alleen het slotje');
132 assert.ok(html.includes('ax-fedi-on'), 'het slotje staat er wel');
133});
134
135test('een dichte track met een bestand krijgt de knop, met bevestiging', async () => {
136 insMedia.run('m4', 's1', 'nieuw.mp3', 'audio/nieuw.mp3', 'audio/mpeg');
137 insTrack.run('ta4', 's1', 'Nieuw', 'De Band', null, 120, 'm4');
138 const html = await (await fetch(`${base}/admin/audio`)).text();
139 const i = html.indexOf('action="/admin/audio/ta4/fedi-open"');
140 assert.ok(i >= 0, 'de knop hoort er te staan');
141 assert.ok(html.slice(i, i + 400).includes('data-confirm='), 'en eerst te vragen, want terug kan niet');
142});
Note: See TracBrowser for help on using the repository browser.