| 1 | // De itunes-velden waaraan een podcast-app een kanaal herkent (shaer-0nh).
|
|---|
| 2 | //
|
|---|
| 3 | // Aanleiding: Funkwhale maakte van dev@dev.klonkt.com een kanaal en las de
|
|---|
| 4 | // rss_url uit onze actor, maar liet content_category leeg. Die leest hij hier,
|
|---|
| 5 | // in de feed -- niet uit `category` op de AP-actor.
|
|---|
| 6 | //
|
|---|
| 7 | // De categorie volgt DEZELFDE regel als op de actor: alleen Music als er ook
|
|---|
| 8 | // werkelijk publieke muziek is. Gated telt niet mee.
|
|---|
| 9 | import { test } from 'node:test';
|
|---|
| 10 | import assert from 'node:assert/strict';
|
|---|
| 11 |
|
|---|
| 12 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 13 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 14 |
|
|---|
| 15 | const dbMod = await import('../src/config/database.js');
|
|---|
| 16 | const db = dbMod.default;
|
|---|
| 17 | dbMod.initializeDatabase();
|
|---|
| 18 | const express = (await import('express')).default;
|
|---|
| 19 | const feed = (await import('../src/routes/feed.js')).default;
|
|---|
| 20 | const AP = (await import('../src/services/ActivityPubService.js')).default;
|
|---|
| 21 |
|
|---|
| 22 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 23 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 24 | db.prepare(`INSERT INTO sites (id, slug, title, tagline, author, profile_photo, owner_id, is_public, is_primary)
|
|---|
| 25 | VALUES (?,?,?,?,?,?,?,1,1)`)
|
|---|
| 26 | .run('s1', 'band', 'De Band', 'Wij maken lawaai', 'De Band zelf', '/media/avatar.webp', 'u1');
|
|---|
| 27 | db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, excerpt, content, status, published_at)
|
|---|
| 28 | VALUES (?,?,?,?,?,?,?,'published',?)`)
|
|---|
| 29 | .run('p1', 's1', 'u1', 'bericht', 'Bericht', 'kort', '<p>x</p>', '2026-01-01T00:00:00Z');
|
|---|
| 30 |
|
|---|
| 31 | const app = express();
|
|---|
| 32 | app.use((req, res, next) => {
|
|---|
| 33 | res.locals.site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
|
|---|
| 34 | res.locals.siteUrlBase = '';
|
|---|
| 35 | next();
|
|---|
| 36 | });
|
|---|
| 37 | app.use(feed);
|
|---|
| 38 | const server = app.listen(0);
|
|---|
| 39 | await new Promise((r) => server.once('listening', r));
|
|---|
| 40 | const haal = async () => (await fetch(`http://127.0.0.1:${server.address().port}/feed.xml`)).text();
|
|---|
| 41 |
|
|---|
| 42 | const site = () => db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
|
|---|
| 43 |
|
|---|
| 44 | test('zonder publieke muziek: wel auteur, samenvatting en plaat, GEEN categorie', async () => {
|
|---|
| 45 | const xml = await haal();
|
|---|
| 46 | assert.match(xml, /<itunes:author>De Band zelf<\/itunes:author>/);
|
|---|
| 47 | assert.match(xml, /<itunes:summary>Wij maken lawaai<\/itunes:summary>/);
|
|---|
| 48 | assert.match(xml, /<itunes:image href="[^"]*\/media\/avatar\.webp" \/>/, 'de plaat wordt absoluut gemaakt');
|
|---|
| 49 | assert.ok(!xml.includes('<itunes:category'), 'een blog zonder open track is geen muziekkanaal');
|
|---|
| 50 | assert.equal(AP.channelCategory(site()), null, 'en de actor zegt hetzelfde');
|
|---|
| 51 | });
|
|---|
| 52 |
|
|---|
| 53 | test('een GATED track maakt er nog geen muziekkanaal van', async () => {
|
|---|
| 54 | db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)')
|
|---|
| 55 | .run('m0', 's1', 'dicht.mp3', 'audio/dicht.mp3', 'audio/mpeg', 10);
|
|---|
| 56 | db.prepare('INSERT INTO audio_tracks (id, site_id, title, media_id, fedi_open) VALUES (?,?,?,?,0)')
|
|---|
| 57 | .run('t0', 's1', 'Achter de poort', 'm0');
|
|---|
| 58 | const xml = await haal();
|
|---|
| 59 | assert.ok(!xml.includes('<itunes:category'), 'afwezig is afwezig, ook in een categorie');
|
|---|
| 60 | assert.equal(AP.channelCategory(site()), null);
|
|---|
| 61 | assert.ok(!xml.includes('Achter de poort'));
|
|---|
| 62 | });
|
|---|
| 63 |
|
|---|
| 64 | test('met een OPEN track wordt het Music, aan beide kanten', async () => {
|
|---|
| 65 | db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)')
|
|---|
| 66 | .run('m1', 's1', 'open.mp3', 'audio/open.mp3', 'audio/mpeg', 4210688);
|
|---|
| 67 | db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, duration, media_id, cover_url, fedi_open, created_at) VALUES (?,?,?,?,?,?,?,1,?)')
|
|---|
| 68 | .run('t1', 's1', 'Het nummer', 'De Band', 203, 'm1', '/media/hoes.jpg', '2026-03-01 12:00:00');
|
|---|
| 69 | const xml = await haal();
|
|---|
| 70 | assert.match(xml, /<itunes:category text="Music" \/>/);
|
|---|
| 71 | assert.equal(AP.channelCategory(site()), 'music');
|
|---|
| 72 | });
|
|---|
| 73 |
|
|---|
| 74 | test('een track draagt zijn eigen artiest en hoes mee', async () => {
|
|---|
| 75 | const xml = await haal();
|
|---|
| 76 | assert.match(xml, /<itunes:author>De Band<\/itunes:author>/, 'de artiest van de track, naast die van het kanaal');
|
|---|
| 77 | assert.match(xml, /<itunes:image href="[^"]*\/media\/hoes\.jpg" \/>/);
|
|---|
| 78 | assert.match(xml, /<itunes:duration>203<\/itunes:duration>/);
|
|---|
| 79 | });
|
|---|
| 80 |
|
|---|
| 81 | test.after(() => server.close());
|
|---|