source: Klonkt/test/ap-library.test.js

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

Library: audience meesturen en de ongeordende collectievorm gebruiken

De 500 op /ap/users/dev/library kwam van open.audio, niet van ons, en de
oorzaak staat in hun bron (federation/serializers.py op develop):

LibrarySerializer.create() regel 1147

"privacy_level": privacy[validated_dataaudience]

audience staat als required=False in de serializer, dus als wij het
weglaten zit de sleutel niet in validated_data en is dat een KeyError --
een 500 op elke poging onze bibliotheek op te halen. Wij stuurden hem
niet.

Het veld is bovendien gewoon waar: alles in de bak is fedi_open. En bij
hen is het precies het verschil tussen privacy_level 'everyone' en 'me',
oftewel of onze nummers daar afspeelbaar zijn.

Daarnaast de vorm. Hun CollectionPageSerializer (regel 1157) accepteert
alleen CollectionPage met items; wij stuurden OrderedCollectionPage
met orderedItems, en dan komt er van de inhoud niets binnen. Dat is geen
dialect maar de andere helft van AS2: een platenkast heeft geen volgorde
die iets betekent, en hun LibrarySerializer accepteert als type ook alleen
as:Collection of fw:Library. De outbox is wél geordend en blijft zoals hij
was.

De eisen staan nu met bronverwijzing in test/ap-library.test.js, want ze
stonden nergens bij ons opgeschreven -- en het volgende dat gebeurt is
anders dat iemand dit 'opruimt' naar de gewone vorm.

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

  • Property mode set to 100644
File size: 5.9 KB
Line 
1// De site als Funkwhale-Library, skelet (shaer-0nh).
2//
3// AANLEIDING, gemeten op 13-8 tegen open.audio: onze vier tracks waren daar
4// binnengehaald langs de AP-weg -- met ONZE track-id's als fid, en met een
5// artist_credit dat Funkwhale zelf uit onze attributedTo had afgeleid. Maar
6// `uploads` was leeg en `is_playable` false. Bij hen hangt een upload aan een
7// library; zonder die bak blijft een track een naam zonder geluid. Het
8// audiobestand zelf is gewoon anoniem op te halen -- ze hadden het niet
9// geprobeerd.
10import { test } from 'node:test';
11import assert from 'node:assert/strict';
12
13process.env.DATABASE_PATH = ':memory:';
14process.env.PUBLIC_BASE_URL = 'https://ons.test';
15
16const dbMod = await import('../src/config/database.js');
17const db = dbMod.default;
18dbMod.initializeDatabase();
19const AP = await import('../src/services/ActivityPubService.js');
20
21const BASE = 'https://ons.test';
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, description, owner_id) VALUES (?,?,?,?,?)')
25 .run('s1', 'band', 'De Band', 'Onze plaatjes', 'u1');
26const site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
27
28const insM = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,?)');
29const insT = db.prepare('INSERT INTO audio_tracks (id, site_id, title, duration, media_id, fedi_open) VALUES (?,?,?,?,?,?)');
30insM.run('m1', 's1', 'open.mp3', 'a/open.mp3', 'audio/mpeg', 4210688);
31insM.run('m2', 's1', 'dicht.mp3', 'a/dicht.mp3', 'audio/mpeg', 999);
32insT.run('t1', 's1', 'Het open nummer', 203, 'm1', 1);
33insT.run('t2', 's1', 'Achter de poort', 100, 'm2', 0);
34
35const LIB = `${BASE}/ap/users/band/library`;
36
37test('de bibliotheek heeft de vorm die hun docs voorschrijven', () => {
38 const lib = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'));
39 assert.equal(lib.type, 'Library', 'niet OrderedCollection -- daar zoeken ze op');
40 assert.equal(lib.id, LIB);
41 assert.equal(lib.name, 'De Band');
42 assert.equal(lib.attributedTo, `${BASE}/ap/users/band`);
43 assert.equal(lib.followers, `${LIB}/followers`, 'vereist volgens hun docs');
44 assert.equal(lib.first, `${LIB}?page=1`);
45 assert.equal(lib.last, `${LIB}?page=1`);
46 assert.equal(lib.totalItems, 1);
47 assert.equal(lib.summary, 'Onze plaatjes');
48});
49
50test('een gesloten track hoort er NIET in -- dicht is dicht', () => {
51 // De bibliotheek is openbaar; dat kan alleen als er niets in zit dat het
52 // niet is. Zou een gated track hier binnenglippen, dan zou de openbaarheid
53 // van de bak zijn poort overrulen.
54 const lib = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'));
55 assert.deepEqual(lib.items.map((a) => a.name), ['Het open nummer']);
56});
57
58test('elke Audio wijst naar de bibliotheek', () => {
59 // Dit is het veld waar het om begonnen was: het haakje waar een upload aan komt.
60 const lib = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'));
61 for (const a of lib.items) assert.equal(a.library, LIB);
62 // Ook als de track ergens anders vandaan komt -- de losse ophaal, de
63 // trackcollectie, de playlist. Anders hangt het er maar op een plek.
64 const los = AP.buildTrackAudio(BASE, site, AP.openTrack('s1', 't1'), { standalone: true });
65 assert.equal(los.library, LIB);
66});
67
68// Wat hun serializers WERKELIJK eisen, uit de bron en niet uit de docs.
69// api/funkwhale_api/federation/serializers.py op develop, gelezen 15-8:
70//
71// LibrarySerializer(PaginatedCollectionSerializer) regel 1071
72// type in [as:Collection, fw:Library] · name · id · first · last · totalItems
73// audience: ChoiceField(required=False) MAAR create() doet
74// privacy[validated_data["audience"]] -- ontbreekt de sleutel, dan een
75// KeyError en dus een 500. Zo gaf open.audio op 15-8 een 500 op onze URL.
76// en het is de schakel naar privacy_level 'everyone' == afspeelbaar.
77//
78// CollectionPageSerializer regel 1157
79// type == as:CollectionPage (NIET OrderedCollectionPage) · items (NIET
80// orderedItems) · id · first · last · partOf · totalItems
81//
82// Deze test staat er omdat die eisen nergens anders bij ons zijn opgeschreven.
83// Zonder hem is het volgende dat iemand de bibliotheek 'opruimt' naar de
84// gewone OrderedCollection-vorm, en dan is hij daar stil weer leeg.
85test('de bibliotheek voldoet aan Funkwhale LibrarySerializer', () => {
86 const lib = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'));
87 assert.equal(lib.audience, 'https://www.w3.org/ns/activitystreams#Public',
88 'zonder audience een KeyError in hun create() -- 500');
89 assert.ok(Array.isArray(lib.items), 'Collection draagt items, geen orderedItems');
90 assert.equal(lib.orderedItems, undefined);
91 for (const veld of ['id', 'name', 'first', 'last', 'totalItems', 'attributedTo']) {
92 assert.ok(lib[veld] !== undefined, `${veld} ontbreekt`);
93 }
94});
95
96test('een bibliotheekpagina voldoet aan Funkwhale CollectionPageSerializer', () => {
97 const p = AP.buildLibrary(BASE, site, AP.siteOpenTracks('s1'), { page: 1 });
98 assert.equal(p.type, 'CollectionPage', 'OrderedCollectionPage wijzen ze af');
99 assert.ok(Array.isArray(p.items), 'items, niet orderedItems');
100 assert.equal(p.orderedItems, undefined);
101 assert.equal(p.partOf, LIB);
102 assert.equal(p.id, `${LIB}?page=1`);
103 for (const veld of ['first', 'last', 'totalItems']) {
104 assert.ok(p[veld] !== undefined, `${veld} ontbreekt op de pagina`);
105 }
106});
107
108test('de term is gedeclareerd, anders laat een strikte lezer hem vallen', async () => {
109 const core = await import('../src/services/ap-core.js');
110 const term = core.AP_CONTEXT.find((x) => x && typeof x === 'object' && x.library);
111 assert.ok(term, 'library staat in de context');
112 assert.equal(term.library['@id'], 'fw:library');
113 assert.equal(term.library['@type'], '@id', 'het is een URI, geen tekst');
114 assert.equal(term.Library, 'fw:Library');
115 assert.equal(term.fw, 'https://funkwhale.audio/ns#');
116});
Note: See TracBrowser for help on using the repository browser.