source: Klonkt/test/feed-itunes.test.js@ 4d81de5

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

De feed vertelt ook WAT voor kanaal dit is

Funkwhale maakte van dev@… een kanaal en las de rss_url netjes uit
de Link-array op onze actor -- maar liet content_category leeg. Dat is de
aanwijzing: hij bouwt zo'n kanaal langs de RSS-kant op, en leest de categorie
daar. Niet uit category op de AP-actor.

Dus itunes-velden erbij op kanaalniveau: author, summary, image (de site-avatar,
absoluut gemaakt) en category. En per track de eigen artiest en hoes, naast de
duur die er al stond.

EN IK HEB MIJN EIGEN CATEGORIE-REGEL AANGESCHERPT. Die keek naar ELKE track,
ook een gated -- zodat een site met uitsluitend afgeschermde muziek toch "hier
is muziek" naar de hele fediverse riep. Dat botst met de regel die we overal
aanhouden: een gesloten track is AFWEZIG, niet stilletjes aanwezig. Nu tellen
alleen opengezette tracks, en zeggen de feed en de actor hetzelfde. Er staat
een test op die precies dat vastlegt: een gated track maakt er nog geen
muziekkanaal van.

Dat maakt de regel strenger dan hij vanmiddag was. Bewust: naar buiten toe is
een kanaal zonder publieke muziek geen muziekkanaal.

4 tests.

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

  • Property mode set to 100644
File size: 4.1 KB
Line 
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.
9import { test } from 'node:test';
10import assert from 'node:assert/strict';
11
12process.env.DATABASE_PATH = ':memory:';
13process.env.PUBLIC_BASE_URL = 'https://test.example';
14
15const dbMod = await import('../src/config/database.js');
16const db = dbMod.default;
17dbMod.initializeDatabase();
18const express = (await import('express')).default;
19const feed = (await import('../src/routes/feed.js')).default;
20const AP = (await import('../src/services/ActivityPubService.js')).default;
21
22db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
23 .run('u1', 'u1', 'u1@t', 'x', 'god');
24db.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');
27db.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
31const app = express();
32app.use((req, res, next) => {
33 res.locals.site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
34 res.locals.siteUrlBase = '';
35 next();
36});
37app.use(feed);
38const server = app.listen(0);
39await new Promise((r) => server.once('listening', r));
40const haal = async () => (await fetch(`http://127.0.0.1:${server.address().port}/feed.xml`)).text();
41
42const site = () => db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
43
44test('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
53test('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
64test('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
74test('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
81test.after(() => server.close());
Note: See TracBrowser for help on using the repository browser.