| 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.
|
|---|
| 12 | import { test } from 'node:test';
|
|---|
| 13 | import assert from 'node:assert/strict';
|
|---|
| 14 | import os from 'os';
|
|---|
| 15 | import path from 'path';
|
|---|
| 16 |
|
|---|
| 17 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 18 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 19 | process.env.MEDIA_PATH = path.join(os.tmpdir(), 'klonkt-test-media');
|
|---|
| 20 |
|
|---|
| 21 | const dbMod = await import('../src/config/database.js');
|
|---|
| 22 | const db = dbMod.default;
|
|---|
| 23 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 24 | const audioMod = await import('../src/routes/admin-audio.js');
|
|---|
| 25 | const { openTrackOnFediverse } = audioMod;
|
|---|
| 26 |
|
|---|
| 27 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 28 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)').run('s1', 'band', 'De Band', 'u1');
|
|---|
| 29 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)').run('s2', 'ander', 'Andere', 'u1');
|
|---|
| 30 | const site = (id) => db.prepare('SELECT * FROM sites WHERE id = ?').get(id);
|
|---|
| 31 |
|
|---|
| 32 | const insMedia = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1000)');
|
|---|
| 33 | const insTrack = db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, album, duration, media_id, fedi_open) VALUES (?,?,?,?,?,?,?,0)');
|
|---|
| 34 | insMedia.run('m1', 's1', 'zwaluwen.mp3', 'audio/zwaluwen.mp3', 'audio/mpeg');
|
|---|
| 35 | insMedia.run('m2', 's1', 'stil.mp3', 'audio/stil.mp3', 'audio/mpeg');
|
|---|
| 36 | insMedia.run('mb', 's2', 'buur.mp3', 'audio/buur.mp3', 'audio/mpeg');
|
|---|
| 37 | insTrack.run('ta1', 's1', 'Zwaluwen', 'De Band', 'Voorjaar', 200, 'm1');
|
|---|
| 38 | insTrack.run('ta3', 's1', 'Stil', 'De Band', null, 90, 'm2'); // hangt in geen enkele post
|
|---|
| 39 | insTrack.run('link', 's1', 'Alleen een link', 'De Band', null, 0, null);
|
|---|
| 40 | insTrack.run('tb1', 's2', 'Van de buurman', 'Andere', null, 210, 'mb');
|
|---|
| 41 |
|
|---|
| 42 | db.prepare('INSERT INTO playlists (id, site_id, title) VALUES (?,?,?)').run('pl1', 's1', 'Lente');
|
|---|
| 43 | db.prepare('INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES (?,?,0)').run('pl1', 'ta1');
|
|---|
| 44 |
|
|---|
| 45 | const insPost = db.prepare('INSERT INTO posts (id, site_id, slug, title, content, status, author_id) VALUES (?,?,?,?,?,?,?)');
|
|---|
| 46 | insPost.run('p-direct', 's1', 'direct', 'Direct', 'Luister: [[track:ta1]]', 'published', 'u1');
|
|---|
| 47 | insPost.run('p-album', 's1', 'album', 'Album', '[[album:Voorjaar]]', 'published', 'u1');
|
|---|
| 48 | insPost.run('p-playlist', 's1', 'lijst', 'Lijst', '[[playlist:pl1]]', 'published', 'u1');
|
|---|
| 49 | insPost.run('p-concept', 's1', 'concept', 'Concept', '[[track:ta1]]', 'draft', 'u1');
|
|---|
| 50 | insPost.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.
|
|---|
| 52 | insPost.run('q-buur', 's2', 'buur', 'Buur', '[[track:ta1]]', 'published', 'u1');
|
|---|
| 53 |
|
|---|
| 54 | const open = (id) => db.prepare('SELECT fedi_open FROM audio_tracks WHERE id = ?').get(id).fedi_open;
|
|---|
| 55 |
|
|---|
| 56 | const { postsEmbeddingTrack } = await import('../src/services/music/index.js');
|
|---|
| 57 |
|
|---|
| 58 | test('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 |
|
|---|
| 67 | test('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 |
|
|---|
| 78 | test('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 |
|
|---|
| 86 | test('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 |
|
|---|
| 92 | test('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 ────────────────────────────────────────────────────────────
|
|---|
| 99 | const express = (await import('express')).default;
|
|---|
| 100 | const app = express();
|
|---|
| 101 | app.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 | });
|
|---|
| 107 | app.use('/admin/audio', audioMod.default);
|
|---|
| 108 | const server = app.listen(0);
|
|---|
| 109 | server.unref();
|
|---|
| 110 | const base = `http://127.0.0.1:${server.address().port}`;
|
|---|
| 111 | test.after(() => server.close());
|
|---|
| 112 |
|
|---|
| 113 | test('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 |
|
|---|
| 120 | test('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 |
|
|---|
| 128 | test('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 |
|
|---|
| 135 | test('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 | });
|
|---|