source: Klonkt/test/ap-collection-paging.test.js@ 19a9a1e

main
Last change on this file since 19a9a1e 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: 4.9 KB
Line 
1// Wie `first` belooft, moet een PAGINA leveren (open.audio, 15-8).
2//
3// De aanleiding: `/ap/users/dev/library` gaf 200, keurig met `first` en `last`,
4// maar `?page=1` gaf de bibliotheek OPNIEUW -- zelfde id, type Library, en een
5// `first` die weer naar zichzelf wees. open.audio volgde die verwijzing en gaf
6// een 500 terug op onze URL.
7//
8// De bouwer kon al pagineren; de ROUTE gaf `?page=` niet door. Daarom loopt deze
9// test over HTTP en niet over `buildLibrary()`: een unit-test op de bouwer stond
10// er al en was groen, precies terwijl het gat openstond. Zie
11// [verificatie-niet-zonder-slaagtest] -- een controle die niet kan falen op wat
12// je verandert is geen bewijs.
13//
14// Hij is bewust een LUS over alle collecties en geen rijtje losse gevallen: het
15// gat ontstond doordat vier routes de doorgifte wel hadden en twee niet, en een
16// per-collectie-test vraagt om precies diezelfde vergeetachtigheid.
17import { test } from 'node:test';
18import assert from 'node:assert/strict';
19
20process.env.DATABASE_PATH = ':memory:';
21process.env.PUBLIC_BASE_URL = 'https://test.example';
22
23const dbMod = await import('../src/config/database.js');
24const db = dbMod.default;
25dbMod.initializeDatabase();
26const express = (await import('express')).default;
27const routes = (await import('../src/routes/activitypub.js')).default;
28
29db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
30 .run('u1', 'u1', 'u1@t', 'x', 'god');
31db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public, is_primary) VALUES (?,?,?,?,1,1)')
32 .run('s1', 'band', 'De Band', 'u1');
33
34const insM = db.prepare('INSERT INTO media (id, site_id, filename, storage_path, mime_type, size) VALUES (?,?,?,?,?,1)');
35const insT = db.prepare('INSERT INTO audio_tracks (id, site_id, title, artist, duration, media_id, fedi_open, position) VALUES (?,?,?,?,?,?,1,?)');
36for (const n of [1, 2, 3]) {
37 insM.run(`m${n}`, 's1', `t${n}.mp3`, `audio/t${n}.mp3`, 'audio/mpeg');
38 insT.run(`t${n}`, 's1', `Nummer ${n}`, 'De Band', 120, `m${n}`, n);
39}
40db.prepare("INSERT INTO playlists (id, site_id, title, kind) VALUES ('plaat','s1','De Plaat','album')").run();
41db.prepare("INSERT INTO playlist_tracks (playlist_id, track_id, position) VALUES ('plaat','t1',1)").run();
42db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at)
43 VALUES ('p1','s1','u1','hallo','Hallo','<p>x</p>','published','2026-08-01T00:00:00Z')`).run();
44
45const app = express(); app.use(routes);
46const server = app.listen(0);
47await new Promise((r) => server.once('listening', r));
48const base = `http://127.0.0.1:${server.address().port}`;
49
50const haal = async (pad) => {
51 const r = await fetch(base + pad, { headers: { Accept: 'application/activity+json' } });
52 return { status: r.status, body: r.status === 200 ? await r.json() : null };
53};
54
55// Elke collectie die een actor of een object naar buiten adverteert.
56const COLLECTIES = [
57 '/ap/users/band/outbox',
58 '/ap/users/band/followers',
59 '/ap/users/band/following',
60 '/ap/users/band/featured',
61 '/ap/users/band/tracks',
62 '/ap/users/band/playlists',
63 '/ap/users/band/library',
64 '/ap/users/band/library/followers',
65];
66
67for (const pad of COLLECTIES) {
68 test(`${pad}: first wijst naar een echte pagina`, async () => {
69 const wortel = await haal(pad);
70 assert.equal(wortel.status, 200, `${pad} gaf ${wortel.status}`);
71
72 // Geen `first`? Dan belooft hij ook niets en valt er niets te controleren.
73 if (!wortel.body.first) return;
74
75 const eerste = typeof wortel.body.first === 'string' ? wortel.body.first : wortel.body.first.id;
76 assert.ok(eerste, `${pad}: first zonder id`);
77
78 const pagina = await haal(new URL(eerste).pathname + new URL(eerste).search);
79 assert.equal(pagina.status, 200, `${pad}: first gaf ${pagina.status}`);
80
81 // DE KERN: de pagina moet een pagina zijn en niet de collectie opnieuw.
82 assert.match(pagina.body.type, /Page$/, `${pad}: first gaf type ${pagina.body.type}`);
83 assert.equal(pagina.body.partOf, wortel.body.id, `${pad}: pagina hoort niet bij de collectie`);
84 assert.notEqual(pagina.body.id, wortel.body.id, `${pad}: pagina deelt zijn id met de collectie`);
85
86 // Een pagina MAG `first` dragen (AS2: CollectionPage erft van Collection,
87 // en Funkwhale eist het zelfs op library-pagina's). Wat niet mag is een
88 // `next` naar zichzelf: dat is de lus, en die zit in de verwijzing die een
89 // lezer volgt, niet in de aanwezigheid van het veld.
90 assert.notEqual(pagina.body.next, eerste, `${pad}: next wijst naar de pagina zelf`);
91
92 // De items horen bij het type: geordend -> orderedItems, ongeordend ->
93 // items. Een `Collection` met `orderedItems` is voor een strikte lezer leeg.
94 const sleutel = pagina.body.type === 'CollectionPage' ? 'items' : 'orderedItems';
95 assert.ok(Array.isArray(pagina.body[sleutel]), `${pad}: ${pagina.body.type} zonder ${sleutel}`);
96 });
97}
98
99test.after(() => server.close());
Note: See TracBrowser for help on using the repository browser.