source: Klonkt/test/friends-history.test.js

main
Last change on this file was 73a10c2, checked in by Robin <roboburr@…>, 6 weeks ago

Vrienden krijgen de geschiedenis mee, en een block blijft een dichte deur

Robins besluit (30-7): wie later vriend wordt, moet de oudere friends-only
posts alsnog kunnen zien. Dat kon niet: friends-only was een moment-opname
(bezorgd aan de volgers van dat moment) en de outbox verborg fan-only juist,
dus de backfill die bij een nieuw volgen draait had niets op te halen.

Drie stukken, samen de lus:

  1. De outbox antwoordt naar PUBLIEK: outboxAudience beslist per lezer. De eigenaar (bearer) en een geverifieerde geaccepteerde volger of guardian krijgen de friends-only geschiedenis mee; anoniem en een vreemde met alleen een handtekening krijgen de publieke set, precies als voorheen. Bijvangst: de auteur ziet nu ook zijn EIGEN friends-only posts in de app.
  2. De backfill identificeert zich: signedGetJson tekent de GET als de volgende site ((request-target) host date, wat verifyRequest checkt), dus de servende kant herkent de vriend. Een server die de handtekening negeert gedraagt zich exact als vroeger.
  3. Het moment dat de vriendschap ontstaat is het moment dat de geschiedenis meekomt: op de Accept van onze Follow draait de backfill.

En Robins scherpe toevoeging: een geverifieerde caller die deze instance
BLOKKEERT krijgt een LEGE collectie, niet eens de publieke set. Een block is
een dichte deur, en een gesigneerde fetch is aankloppen met je naam erop. De
block wint ook van een verweesd volger-rijtje.

Changed files:
src/services/ActivityPubService.js

  • signedGetJson; backfill gesigneerd; backfill op Accept(Follow); outboxAudience als pure, testbare beslissing

src/routes/activitypub.js

  • de outbox-route beslist via outboxAudience; blocked = lege set

New file:
test/friends-history.test.js

  • anoniem blijft publiek; identificatie is geen vriendschap; volger en eigenaar lezen de geschiedenis; block wint van alles, ook van een stale volger-rij

remarks: 341 tests groen, server start.

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

  • Property mode set to 100644
File size: 2.8 KB
Line 
1// Friends get the history (Robins besluit, 30-7). A friends-only post used to
2// be a moment-snapshot: delivered to the followers of that instant, hidden
3// from the outbox, so a friend added LATER could never see it in a feed. Now
4// the outbox answers by audience, and the backfill a fresh follow triggers
5// identifies itself, so the past comes along with the friendship.
6//
7// And the door stays a door: a verified caller this instance BLOCKS gets an
8// EMPTY collection, not even the public set.
9import { test } from 'node:test';
10import assert from 'node:assert/strict';
11
12process.env.DATABASE_PATH = ':memory:';
13process.env.PUBLIC_BASE_URL = 'https://test.example';
14
15const dbMod = await import('../src/config/database.js');
16const db = dbMod.default;
17dbMod.initializeDatabase();
18const AP = (await import('../src/services/ActivityPubService.js')).default;
19
20db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
21 .run('u1', 'kid', 'u1@t', 'x', 'god');
22db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,1)').run('s1', 'kid', 'kid', 'u1');
23
24const FRIEND = 'https://vriend.test/u/f';
25const STRANGER = 'https://vreemde.test/u/x';
26const BLOCKED = 'https://pest.test/u/b';
27db.prepare("INSERT INTO ap_followers (slug, actor_uri) VALUES ('kid', ?)").run(FRIEND);
28db.prepare("INSERT INTO ap_blocks (slug, kind, target) VALUES ('kid', 'actor', ?)").run(BLOCKED);
29
30test('the audience decides what the outbox holds', () => {
31 assert.equal(AP.outboxAudience('kid', {}), 'public', 'anonymous stays public');
32 assert.equal(AP.outboxAudience('kid', { verifiedActor: STRANGER }), 'public',
33 'a signature alone earns nothing: identification is not friendship');
34 assert.equal(AP.outboxAudience('kid', { verifiedActor: FRIEND }), 'friend',
35 'an accepted follower reads the friends-only history');
36 assert.equal(AP.outboxAudience('kid', { bearerSlug: 'kid' }), 'friend',
37 'the owner sees their own friends-only posts in their own app');
38 assert.equal(AP.outboxAudience('kid', { bearerSlug: 'someone-else' }), 'public',
39 "another local account's bearer is not this account");
40});
41
42test('a blocked actor who signs their fetch gets an empty set, not the public one', () => {
43 assert.equal(AP.outboxAudience('kid', { verifiedActor: BLOCKED }), 'blocked',
44 'the block outranks everything: a closed door, and they knocked with their name on it');
45});
46
47test('a blocked FOLLOWER is still blocked: the block outranks the follow', () => {
48 // The row order of checks matters: someone blocked after having followed
49 // must not keep reading through the stale follower row.
50 db.prepare("INSERT INTO ap_followers (slug, actor_uri) VALUES ('kid', ?)").run(BLOCKED);
51 assert.equal(AP.outboxAudience('kid', { verifiedActor: BLOCKED }), 'blocked');
52});
Note: See TracBrowser for help on using the repository browser.