| 1 | // A 🛟 help request (FEP-633c 5.2.1) is a message, not a post: it belongs in
|
|---|
| 2 | // Berichten and the Guardian PWA, never in de Krant. It used to land in both,
|
|---|
| 3 | // because the timeline insert only asked "top-level post from someone I follow"
|
|---|
| 4 | // and never looked at who the note was addressed to.
|
|---|
| 5 | import { test } from 'node:test';
|
|---|
| 6 | import assert from 'node:assert/strict';
|
|---|
| 7 |
|
|---|
| 8 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 9 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 10 |
|
|---|
| 11 | const dbMod = await import('../src/config/database.js');
|
|---|
| 12 | const db = dbMod.default;
|
|---|
| 13 | dbMod.initializeDatabase();
|
|---|
| 14 | const AP = (await import('../src/services/ActivityPubService.js')).default;
|
|---|
| 15 |
|
|---|
| 16 | const PUB = 'https://www.w3.org/ns/activitystreams#Public';
|
|---|
| 17 | const WARD = 'https://ward.test/ap/users/kid';
|
|---|
| 18 | const note = (extra) => ({ id: 'https://ward.test/notes/1', type: 'Note', content: '<p>hoi</p>', ...extra });
|
|---|
| 19 |
|
|---|
| 20 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@test', 'x', 'god');
|
|---|
| 21 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,?)').run('s1', 'guard', 'Guard', 'u1', 1);
|
|---|
| 22 |
|
|---|
| 23 | test('a public post from someone we follow belongs in the timeline', () => {
|
|---|
| 24 | assert.equal(AP.belongsInTimeline(note({ to: [PUB], cc: [`${WARD}/followers`] })), true);
|
|---|
| 25 | });
|
|---|
| 26 |
|
|---|
| 27 | test('a followers-only post still belongs there: you follow them', () => {
|
|---|
| 28 | assert.equal(AP.belongsInTimeline(note({ to: [`${WARD}/followers`] })), true);
|
|---|
| 29 | });
|
|---|
| 30 |
|
|---|
| 31 | test('a direct note does not, whoever sent it', () => {
|
|---|
| 32 | const dm = note({ to: ['https://test.example/ap/users/guard'] });
|
|---|
| 33 | assert.equal(AP.noteVisibility(dm), 'direct');
|
|---|
| 34 | assert.equal(AP.belongsInTimeline(dm), false, 'a DM is a message, not a feed post');
|
|---|
| 35 | });
|
|---|
| 36 |
|
|---|
| 37 | test('a help request is direct by construction, so it is refused too', () => {
|
|---|
| 38 | const help = note({ to: ['https://test.example/ap/users/guard'], 'shaer:helpRequest': true });
|
|---|
| 39 | assert.equal(AP.belongsInTimeline(help), false);
|
|---|
| 40 | });
|
|---|
| 41 |
|
|---|
| 42 | test('a reply never belongs in the timeline either: it belongs to its thread', () => {
|
|---|
| 43 | assert.equal(AP.belongsInTimeline(note({ to: [PUB], inReplyTo: 'https://x.test/notes/9' })), false);
|
|---|
| 44 | });
|
|---|
| 45 |
|
|---|
| 46 | test('the self-heal drops a help request that was already cached as a post', async () => {
|
|---|
| 47 | // Two rows with an identical shape; only the mention marks one as a 🛟.
|
|---|
| 48 | for (const id of ['https://ward.test/notes/help', 'https://ward.test/notes/post']) {
|
|---|
| 49 | db.prepare('INSERT INTO ap_timeline (id, slug, author_uri, content) VALUES (?,?,?,?)').run(id, 'guard', WARD, '<p>x</p>');
|
|---|
| 50 | }
|
|---|
| 51 | db.prepare('INSERT INTO ap_mentions (slug, object_uri, actor_uri, content, help_request) VALUES (?,?,?,?,1)')
|
|---|
| 52 | .run('guard', 'https://ward.test/notes/help', WARD, '<p>x</p>');
|
|---|
| 53 | // A public mention from someone you follow IS a post and must survive.
|
|---|
| 54 | db.prepare('INSERT INTO ap_mentions (slug, object_uri, actor_uri, content, help_request) VALUES (?,?,?,?,0)')
|
|---|
| 55 | .run('guard', 'https://ward.test/notes/post', WARD, '<p>x</p>');
|
|---|
| 56 |
|
|---|
| 57 | db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('selfheal_version', '0')").run();
|
|---|
| 58 | await AP.selfHealTimeline();
|
|---|
| 59 |
|
|---|
| 60 | const left = db.prepare('SELECT id FROM ap_timeline WHERE slug = ?').all('guard').map((r) => r.id);
|
|---|
| 61 | assert.ok(!left.includes('https://ward.test/notes/help'), 'the 🛟 is gone from the Krant');
|
|---|
| 62 | assert.ok(left.includes('https://ward.test/notes/post'), 'the ordinary mention stays');
|
|---|
| 63 | });
|
|---|
| 64 |
|
|---|
| 65 | test('and it is still there for Berichten and the Guardian PWA', () => {
|
|---|
| 66 | const help = db.prepare('SELECT * FROM ap_mentions WHERE object_uri = ?').get('https://ward.test/notes/help');
|
|---|
| 67 | assert.ok(help, 'the mention row is untouched');
|
|---|
| 68 | assert.equal(help.help_request, 1);
|
|---|
| 69 | assert.ok(AP.getNotifications('guard', 20).some((n) => n.type === 'mention'), 'it shows up in Berichten');
|
|---|
| 70 | });
|
|---|