source: Klonkt/test/c2s-title.test.js@ 1d5ffc0

main
Last change on this file since 1d5ffc0 was d7d2ede, checked in by Robin <roboburr@…>, 2 weeks ago

C2S: een note met een name krijgt echt een titel (shaer-uply)

Een post die over C2S binnenkwam kon nooit een titel hebben: c2sCreatePost
schreef in posts.title en in posts_fts en las object.name nooit. Een
client die er een zette zag hem geruisloos verdwijnen -- accepteren en
weggooien, het slechtste van de drie mogelijke gedragingen. De API was
daarmee de enige publicatieweg die iets niet kan wat het web wel kan.
Gevonden door R9999, dat alleen over C2S publiceert.

Alle drie de schrijfplekken, want een halve reparatie laat een gat:

  • posts.title krijgt de titel, dus het web toont hem;
  • posts_fts krijgt hem ook, dus hij is op zijn titel te VINDEN;
  • deliverCreate krijgt hem mee, dus de gefedereerde note krijgt zijn vetgedrukte eerste regel van buildNote, precies als een webpost (Mastodon negeert name op een Note).

De titel is platte tekst (toPlainText), per AS2 en per wat de kolom overal
verwacht, getrimd en begrensd op 200 -- de huisregel voor korte vrije tekst
(content warning, sitetitel); de posteditor op het web heeft geen eigen
grens.

Vier tests in test/c2s-title.test.js, met tegenbewijs gedraaid: tegen de
oude code falen de twee titeltests en slagen de twee die ongewijzigd gedrag
vastleggen (geen name = lege titel, witruimte = geen titel). Suite: 1226
groen.

  • Property mode set to 100644
File size: 4.4 KB
RevLine 
[d7d2ede]1// Een C2S-post kon nooit een titel hebben (shaer-uply): c2sCreatePost schreef
2// '' in posts.title en in posts_fts en las `name` op het binnenkomende object
3// nooit. Een client die er een zette zag hem geruisloos verdwijnen -- de API
4// was daarmee de enige publicatieweg die iets niet kon wat het web wel kan.
5// Gevonden door R9999, dat alleen over C2S publiceert.
6import { test } from 'node:test';
7import assert from 'node:assert/strict';
8
9process.env.DATABASE_PATH = ':memory:';
10process.env.PUBLIC_BASE_URL = 'https://test.example';
11
12const dbMod = await import('../src/config/database.js');
13const db = dbMod.default;
14dbMod.initializeDatabase();
15const AP = (await import('../src/services/ActivityPubService.js')).default;
16
17db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
18 .run('u1', 'robin', 'u1@t', 'x', 'god');
19db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kid', 'kid', 'u1');
20const site = db.prepare('SELECT * FROM sites WHERE slug = ?').get('kid');
21const user = db.prepare('SELECT * FROM users WHERE id = ?').get('u1');
22
23function maak(object) {
24 return AP.ingestOutboxActivity(site, user, {
25 type: 'Create',
26 object: {
27 type: 'Note',
28 to: ['https://test.example/ap/users/kid/followers'],
29 cc: ['https://www.w3.org/ns/activitystreams#Public'],
30 ...object,
31 },
32 });
33}
34
35test('de titel van een C2S-post komt in de kolom, de zoekindex en de note', async () => {
36 const r = await maak({ name: 'Mijn eerste getitelde note', content: '<p>de inhoud</p>' });
37 assert.equal(r.status, 201);
38
39 const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id);
40 assert.equal(post.title, 'Mijn eerste getitelde note', 'de kolom draagt de titel');
41
42 // Vindbaar OP de titel, niet alleen op de inhoud -- de tweede schrijfplek
43 // uit shaer-uply. Was alleen de kolom gerepareerd, dan faalde deze.
44 const hit = db.prepare("SELECT post_id FROM posts_fts WHERE posts_fts MATCH 'getitelde'").get();
45 assert.ok(hit && hit.post_id === r.id, 'de titel staat in de zoekindex');
46
47 // En hij federeert zoals een webpost: Mastodon negeert `name` op een Note,
48 // dus buildNote vouwt de titel als vetgedrukte eerste regel in de content.
49 const note = AP.buildNote('https://test.example', site, post);
50 assert.match(note.content, /^<p><strong>Mijn eerste getitelde note<\/strong><\/p>/, 'vetgedrukte eerste regel');
51});
52
53test('zonder name blijft alles zoals het was: lege titel, geen kopregel', async () => {
54 const r = await maak({ content: '<p>gewoon een note</p>' });
55 assert.equal(r.status, 201);
56 const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id);
57 assert.equal(post.title, '', 'geen name is een lege titel, geen undefined of null');
58 const note = AP.buildNote('https://test.example', site, post);
59 assert.ok(!/<strong>/.test(note.content.split('</p>')[0] + '</p>') || !post.title, 'geen verzonnen kopregel');
60});
61
62test('de titel is platte tekst: HTML erin wordt tekst, en de grens is 200', async () => {
63 // `name` is per AS2 platte tekst, maar een client kan sturen wat hij wil.
64 // De kolom en de views verwachten tekst; opmaak wordt dus tekst, en een
65 // scriptpoging houdt niets uitvoerbaars over.
66 const r = await maak({ name: 'Dag <b>wereld</b><script>boem()</script>', content: '<p>x</p>' });
67 const post = db.prepare('SELECT * FROM posts WHERE id = ?').get(r.id);
68 assert.ok(!post.title.includes('<'), 'geen tags in de kolom');
69 assert.match(post.title, /^Dag wereld/, 'de tekst zelf blijft');
70
71 // En de note escapet hem daarna zelf weer voor de vetgedrukte regel, dus
72 // ook daar geen tag-injectie via de titel.
73 const note = AP.buildNote('https://test.example', site, post);
74 assert.ok(!/<script/i.test(note.content), 'niets uitvoerbaars in de note');
75
76 const lang = await maak({ name: 'x'.repeat(500), content: '<p>y</p>' });
77 const langePost = db.prepare('SELECT title FROM posts WHERE id = ?').get(lang.id);
78 assert.equal(langePost.title.length, 200, 'de huisgrens van 200, zoals content warning en sitetitel');
79});
80
81test('een titel met alleen witruimte is geen titel', async () => {
82 const r = await maak({ name: ' ', content: '<p>z</p>' });
83 const post = db.prepare('SELECT title FROM posts WHERE id = ?').get(r.id);
84 assert.equal(post.title, '', 'witruimte trimt weg tot leeg');
85 const note = AP.buildNote('https://test.example', site, post);
86 assert.ok(!note.content.startsWith('<p><strong>'), 'en er komt geen lege kopregel');
87});
Note: See TracBrowser for help on using the repository browser.