source: Klonkt/test/post-access.test.js@ b1b7652

main
Last change on this file since b1b7652 was b1b7652, checked in by Bart <bart@…>, 3 weeks ago

Wie wat van een bericht mag zien, staat nu op één plek

Het besluit zat alleen in de postroute, en werd daar beantwoord door een ANDERE
PAGINA te renderen: paid-gate of fan-gate in plaats van het bericht. Dat werkt
zolang een bericht een pagina is. De tijdlijn gaat hele berichten tonen en heeft
hetzelfde besluit nodig als een stuk tussen de andere berichten.

Twee plekken die allebei zelf bepalen wie een betaalde tekst mag lezen, is een
lek dat op een gelegenheid wacht. Dus PostAccessService: één functie die 'full',
'paid', 'fan' of 'forbidden' zegt, en verder niets over vormgeving. paidTeaser()
verhuist mee, want een tweede kopie daarvan geeft vroeg of laat meer prijs dan
deze.

Gedrag ONGEWIJZIGD overgenomen, inclusief de volgorde: betaald wordt vóór
fan-only getoetst omdat een bericht dat allebei is opengaat met een passkey en
niet met een login. Elf toetsen leggen dat vast, waaronder dat een bewijs voor
een ander bericht dit bericht niet opent.

Nog niet aangesloten: de route blijft voorlopig zijn eigen ifs gebruiken. Eerst
het besluit vastleggen en toetsen, dan pas de aanroepers verplaatsen.

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

  • Property mode set to 100644
File size: 4.6 KB
Line 
1// Wie mag wat zien van een bericht -- op ÉÉN plek.
2//
3// Het besluit zat alleen in de postroute, waar het een andere PAGINA rendert
4// (paid-gate/fan-gate). De tijdlijn gaat hele berichten tonen en heeft hetzelfde
5// besluit nodig als een stuk. Twee plekken die allebei zelf bepalen wie een
6// betaalde tekst mag lezen, is een lek dat op een gelegenheid wacht -- vandaar
7// deze module, en vandaar dat de toetsen hier scherper staan dan elders.
8import { test } from 'node:test';
9import assert from 'node:assert/strict';
10
11process.env.DATABASE_PATH = ':memory:';
12process.env.PUBLIC_BASE_URL = 'https://test.example';
13
14const dbMod = await import('../src/config/database.js');
15dbMod.initializeDatabase();
16const { postAccess, canReadBody, paidTeaser } = await import('../src/services/PostAccessService.js');
17
18const SITE = { id: 's1', slug: 'demo', owner_id: 'u1' };
19const OWNER = { id: 'u1', role: 'god' };
20const VREEMDE = null;
21const INGELOGD = { id: 'u2', role: 'user' };
22
23const post = (extra = {}) => ({
24 id: 'p1', slug: 'hallo', status: 'published', site_id: 's1', author_id: 'u1',
25 content: '<p>eerste alinea</p><p>tweede alinea</p>', ...extra,
26});
27
28test('een gewoon bericht is voor iedereen', () => {
29 assert.equal(postAccess(post(), { user: VREEMDE, site: SITE }), 'full');
30 assert.equal(canReadBody(post(), { user: VREEMDE, site: SITE }), true);
31});
32
33test('een concept is van de maker, en van niemand anders', () => {
34 const concept = post({ status: 'draft' });
35 assert.equal(postAccess(concept, { user: VREEMDE, site: SITE }), 'forbidden');
36 assert.equal(postAccess(concept, { user: INGELOGD, site: SITE }), 'forbidden');
37 assert.equal(postAccess(concept, { user: OWNER, site: SITE }), 'full', 'de eigenaar ziet zijn eigen concept');
38});
39
40test('fan-only vraagt om inloggen, niet om betalen', () => {
41 const f = post({ fan_only: 1 });
42 assert.equal(postAccess(f, { user: VREEMDE, site: SITE }), 'fan');
43 assert.equal(postAccess(f, { user: INGELOGD, site: SITE }), 'full', 'ingelogd is genoeg');
44});
45
46test('betaald blijft betaald tot er een geldig bewijs ligt', () => {
47 const b = post({ paid: 1 });
48 assert.equal(postAccess(b, { user: VREEMDE, site: SITE }), 'paid');
49 assert.equal(postAccess(b, { user: INGELOGD, site: SITE }), 'paid',
50 'inloggen koopt niets: een Klonkt-account is geen supporter');
51 assert.equal(postAccess(b, { user: VREEMDE, site: SITE, unlockedSlug: 'hallo' }), 'full');
52});
53
54test('een bewijs voor een ANDER bericht opent dit bericht niet', () => {
55 // De gevaarlijke vorm: één keer betalen en dan de hele site lezen.
56 const b = post({ paid: 1 });
57 assert.equal(postAccess(b, { user: VREEMDE, site: SITE, unlockedSlug: 'een-ander' }), 'paid');
58 assert.equal(postAccess(b, { user: VREEMDE, site: SITE, unlockedSlug: '' }), 'paid');
59 assert.equal(postAccess(b, { user: VREEMDE, site: SITE, unlockedSlug: null }), 'paid');
60});
61
62test('betaald WINT van fan-only, en die volgorde is geen detail', () => {
63 // Een bericht dat allebei is gaat open met een passkey, niet met een login.
64 // Andersom belandt een anonieme supporter op het inlogscherm en ziet hij de
65 // ontgrendelknop nooit -- de reden dat deze volgorde in de route stond.
66 const beide = post({ paid: 1, fan_only: 1 });
67 assert.equal(postAccess(beide, { user: VREEMDE, site: SITE }), 'paid');
68 assert.equal(postAccess(beide, { user: INGELOGD, site: SITE }), 'paid',
69 'ook ingelogd: de betaalmuur staat vooraan');
70
71 // TWEE POORTEN ACHTER ELKAAR, en dat is de bestaande werking -- overgenomen
72 // uit de route, niet hier bedacht. Wie betaalt maar niet ingelogd is, staat
73 // na het ontgrendelen alsnog voor de fanpoort:
74 assert.equal(postAccess(beide, { user: VREEMDE, site: SITE, unlockedSlug: 'hallo' }), 'fan');
75 // Betalen ÉN ingelogd komt er wel doorheen.
76 assert.equal(postAccess(beide, { user: INGELOGD, site: SITE, unlockedSlug: 'hallo' }), 'full');
77});
78
79test('de eigenaar loopt niet tegen zijn eigen muur op', () => {
80 assert.equal(postAccess(post({ paid: 1, fan_only: 1 }), { user: OWNER, site: SITE }), 'full');
81});
82
83test('geen bericht is geen toegang', () => {
84 assert.equal(postAccess(null, { user: OWNER, site: SITE }), 'forbidden');
85 assert.equal(postAccess(undefined, {}), 'forbidden');
86});
87
88test('de teaser lekt nooit voorbij de eerste alinea', () => {
89 // Dit is de hele belofte van een betaalmuur: wat hierna komt, komt er niet uit.
90 const t = paidTeaser(post({ paid: 1 }));
91 assert.match(t, /eerste alinea/);
92 assert.ok(!t.includes('tweede'), 'de tweede alinea blijft binnen');
93});
94
95test('een expliciete excerpt gaat voor', () => {
96 assert.equal(paidTeaser(post({ excerpt: 'zelf geschreven' })), 'zelf geschreven');
97});
Note: See TracBrowser for help on using the repository browser.