source: Klonkt/test/ap-tracks.test.js@ cf5073b

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

Onze Audio inhoudelijk gelijk aan die van Funkwhale

Dezelfde track aan beide kanten naast elkaar gelegd -- 0METAL MARIO, bij hen op
the_ceeesg en bij ons op dev -- en de uitkomst was gunstiger dan verwacht.

OP ELK VELD WAAR AS2 IETS ZEGT ZIJN WIJ CONFORM EN ZIJ NIET, en dat is precies
een veld: duration. AS2 zegt MUST xsd:duration ("PT103S"), schema.org zegt
hetzelfde in ISO 8601, Funkwhale stuurt 103. Daar bewegen we dus niet.

Al hun EXTRA velden bleken geen eigen verzinsel maar termen die schema.org
gewoon heeft -- en schema.org stond al in onze context. Overgenomen zonder
concessie: de SLEUTELS zijn die van Funkwhale, want daar leest hij op, de
BETEKENIS komt van schema.org. Geen vreemd vocabulaire binnengehaald.

to op het OBJECT, niet alleen op de omhullende Create: een los

opgehaalde track moet zelf kunnen zeggen dat hij openbaar is

position het nummer op de plaat
license schema:license
size schema:contentSize, OP DE LINK -- het is die representatie die
bitrate schema:bitrate zoveel bytes is, niet het nummer
image naast icon; allebei AS2-kern, wij lazen icon, zij lezen image

BITRATE IS AFGELEID, niet gegokt: bytes maal acht gedeeld door seconden IS de
gemiddelde bitrate van een bestand. Alleen als we allebei de getallen hebben --
liever geen veld dan een verzonnen getal.

LICENTIE IS DE ENIGE MET EEN VERTAALSLAG. Bij ons is het vrije tekst uit een
keuzelijst ("CC BY 4.0"), bij hen een URI. Alleen de waarden die onze eigen
keuzelijst aanbiedt worden vertaald -- die kennen we exact. "Alle rechten
voorbehouden" heeft geen URI en levert dus niets op, en een al ingevulde URI
gaat ongemoeid door. Een zelfbedachte licentie-URI is erger dan geen: een lezer
gelooft hem.

Onderweg: t.position en t.license moesten alsnog in de trackquery, die
selecteerde ze niet.

7 tests.

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

  • Property mode set to 100644
File size: 6.0 KB
Line 
1// Onze tracks als eersterangs Audio-objecten (shaer-0nh, stap 3).
2//
3// De actor-collectie is de kanonieke plek: een playlist is een KEUZE daaruit.
4// Een track die in geen playlist staat hoort er dus wel in, en een track die in
5// twee playlists staat is twee keer HETZELFDE ding -- zelfde id.
6//
7// De poortregel blijft: een gesloten track is afwezig, niet leeg.
8import { test } from 'node:test';
9import assert from 'node:assert/strict';
10
11process.env.DATABASE_PATH = ':memory:';
12process.env.PUBLIC_BASE_URL = 'https://test.example';
13
14const dbMod = await import('../src/config/database.js');
15const db = dbMod.default;
16dbMod.initializeDatabase();
17const AP = (await import('../src/services/ActivityPubService.js')).default;
18const express = (await import('express')).default;
19const routes = (await import('../src/routes/activitypub.js')).default;
20
21db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
22 .run('u1', 'u1', 'u1@t', 'x', 'god');
23db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public, is_primary) VALUES (?,?,?,?,1,1)')
24 .run('s1', 'band', 'De Band', 'u1');
25
26const insM = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1)');
27const insT = db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, duration, media_id, cover_url, fedi_open, position) VALUES (?,?,?,?,?,?,?,?,?)');
28insM.run('m1', 's1', 'open.mp3', 'audio/open.mp3', 'audio/mpeg');
29insM.run('m2', 's1', 'dicht.mp3', 'audio/dicht.mp3', 'audio/mpeg');
30insM.run('m3', 's1', 'los.mp3', 'audio/los.mp3', 'audio/mpeg');
31insT.run('t-open', 's1', 'Open nummer', 'De Band', 203, 'm1', '/media/hoes.jpg', 1, 1);
32insT.run('t-dicht', 's1', 'Gesloten nummer', 'De Band', 100, 'm2', null, 0, 2);
33insT.run('t-los', 's1', 'Zonder playlist', 'De Band', 90, 'm3', null, 1, 3);
34
35db.prepare("INSERT INTO playlists (id, site_id, title, kind, cover_url) VALUES ('plaat','s1','De Plaat','album','/media/plaathoes.jpg')").run();
36db.prepare("INSERT INTO playlists (id, site_id, title, kind) VALUES ('mix','s1','De Mix','playlist')").run();
37const insPT = db.prepare('INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES (?,?,?)');
38insPT.run('plaat', 't-open', 1);
39insPT.run('plaat', 't-dicht', 2);
40insPT.run('mix', 't-open', 1);
41
42const app = express(); app.use(routes);
43const server = app.listen(0);
44await new Promise((r) => server.once('listening', r));
45const base = `http://127.0.0.1:${server.address().port}`;
46const haal = async (p) => {
47 const r = await fetch(base + p, { headers: { Accept: 'application/activity+json' } });
48 return { status: r.status, body: r.status === 200 ? await r.json() : null };
49};
50
51test('de actor wijst naar de tracks EN de playlists, tracks eerst', async () => {
52 const { body } = await haal('/ap/users/band');
53 assert.deepEqual(body.streams, [
54 'https://test.example/ap/users/band/tracks',
55 'https://test.example/ap/users/band/playlists',
56 ]);
57});
58
59test('de collectie draagt elke OPEN track, ook zonder playlist', async () => {
60 const { body } = await haal('/ap/users/band/tracks');
61 assert.equal(body.type, 'OrderedCollection');
62 assert.equal(body.totalItems, 2, 'twee open tracks; de gesloten telt niet mee');
63 const namen = body.orderedItems.map((a) => a.name);
64 assert.deepEqual(namen, ['Open nummer', 'Zonder playlist'],
65 'ook de track die in geen enkele playlist staat hoort hier');
66 assert.ok(!JSON.stringify(body).includes('Gesloten nummer'), 'een gated titel lekt niet');
67});
68
69test('een track heeft een eigen id en url als Link-array', async () => {
70 const { body } = await haal('/ap/users/band/tracks');
71 const a = body.orderedItems[0];
72 assert.equal(a.id, 'https://test.example/ap/users/band/tracks/t-open');
73 assert.equal(a.type, 'Audio');
74 assert.equal(a.attributedTo, 'https://test.example/ap/users/band');
75 assert.equal(a.duration, 'PT203S');
76 assert.equal(a.summary, 'De Band');
77 // De Link draagt sinds de veldvergelijking met Funkwhale ook de
78 // bestandsgegevens: die horen bij DEZE representatie, niet bij het nummer.
79 assert.equal(a.url.length, 1);
80 assert.equal(a.url[0].type, 'Link');
81 assert.equal(a.url[0].href, 'https://test.example/audio/stream/open.mp3');
82 assert.equal(a.url[0].mediaType, 'audio/mpeg', 'de mediaType hoort bij de link, niet bij het object');
83 assert.equal(a.mediaType, undefined, 'en dus NIET op het object');
84 assert.equal(a.icon.url, 'https://test.example/media/hoes.jpg');
85});
86
87test('los op te halen, met eigen @context', async () => {
88 const { status, body } = await haal('/ap/users/band/tracks/t-open');
89 assert.equal(status, 200);
90 assert.equal(body.id, 'https://test.example/ap/users/band/tracks/t-open');
91 assert.ok(body['@context'], 'standalone draagt zijn eigen context');
92});
93
94test('een gesloten track is AFWEZIG, niet leeg', async () => {
95 assert.equal((await haal('/ap/users/band/tracks/t-dicht')).status, 404);
96 assert.equal((await haal('/ap/users/band/tracks/bestaatniet')).status, 404);
97});
98
99test('dezelfde track in twee playlists is HETZELFDE ding', async () => {
100 const a = (await haal('/ap/users/band/playlists/plaat')).body.orderedItems[0];
101 const b = (await haal('/ap/users/band/playlists/mix')).body.orderedItems[0];
102 assert.equal(a.id, b.id);
103 assert.equal(a.id, 'https://test.example/ap/users/band/tracks/t-open');
104});
105
106test('de playlisthoes springt in voor een track zonder eigen hoes', async () => {
107 // t-los heeft geen cover_url; in de plaat-collectie zit hij niet, dus we
108 // toetsen de terugval via een playlist die hem wel bevat.
109 db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('plaat','t-los',3)").run();
110 const items = (await haal('/ap/users/band/playlists/plaat')).body.orderedItems;
111 const los = items.find((a) => a.name === 'Zonder playlist');
112 assert.equal(los.icon.url, 'https://test.example/media/plaathoes.jpg');
113 // En de track met een EIGEN hoes houdt die.
114 assert.equal(items.find((a) => a.name === 'Open nummer').icon.url, 'https://test.example/media/hoes.jpg');
115});
116
117test.after(() => server.close());
Note: See TracBrowser for help on using the repository browser.