| [b1b7652] | 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.
|
|---|
| 8 | import { test } from 'node:test';
|
|---|
| 9 | import assert from 'node:assert/strict';
|
|---|
| 10 |
|
|---|
| 11 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 12 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 13 |
|
|---|
| 14 | const dbMod = await import('../src/config/database.js');
|
|---|
| 15 | dbMod.initializeDatabase();
|
|---|
| 16 | const { postAccess, canReadBody, paidTeaser } = await import('../src/services/PostAccessService.js');
|
|---|
| 17 |
|
|---|
| 18 | const SITE = { id: 's1', slug: 'demo', owner_id: 'u1' };
|
|---|
| 19 | const OWNER = { id: 'u1', role: 'god' };
|
|---|
| 20 | const VREEMDE = null;
|
|---|
| 21 | const INGELOGD = { id: 'u2', role: 'user' };
|
|---|
| 22 |
|
|---|
| 23 | const 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 |
|
|---|
| 28 | test('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 |
|
|---|
| 33 | test('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 |
|
|---|
| 40 | test('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 |
|
|---|
| 46 | test('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 |
|
|---|
| 54 | test('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 |
|
|---|
| 62 | test('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 |
|
|---|
| 79 | test('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 |
|
|---|
| 83 | test('geen bericht is geen toegang', () => {
|
|---|
| 84 | assert.equal(postAccess(null, { user: OWNER, site: SITE }), 'forbidden');
|
|---|
| 85 | assert.equal(postAccess(undefined, {}), 'forbidden');
|
|---|
| 86 | });
|
|---|
| 87 |
|
|---|
| 88 | test('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 |
|
|---|
| 95 | test('een expliciete excerpt gaat voor', () => {
|
|---|
| 96 | assert.equal(paidTeaser(post({ excerpt: 'zelf geschreven' })), 'zelf geschreven');
|
|---|
| 97 | });
|
|---|
| [8b65e67] | 98 |
|
|---|
| 99 | // ── Wat de tijdlijn krijgt ────────────────────────────────────────────────
|
|---|
| 100 | // De tijdlijn toont hele berichten, dus een gesloten bericht moet zijn plek
|
|---|
| 101 | // houden ZONDER dat de tekst meereist. Dat is geen sjabloonkwestie: wat niet
|
|---|
| 102 | // gerenderd wordt, kan ook niet per ongeluk getoond worden.
|
|---|
| 103 |
|
|---|
| 104 | const { postEntry } = await import('../src/services/PostAccessService.js');
|
|---|
| 105 | const renderBody = (p) => `<div>GEHEIM: ${p.content}</div>`;
|
|---|
| 106 |
|
|---|
| 107 | test('een open bericht krijgt zijn lijf', () => {
|
|---|
| 108 | const e = postEntry(post(), { user: VREEMDE, site: SITE }, { renderBody });
|
|---|
| 109 | assert.equal(e.access, 'full');
|
|---|
| 110 | assert.match(e.content_html, /GEHEIM/);
|
|---|
| 111 | assert.equal(e.teaser, null, 'geen teaser NAAST het lijf');
|
|---|
| 112 | });
|
|---|
| 113 |
|
|---|
| 114 | test('een betaald bericht reist zonder tekst', () => {
|
|---|
| 115 | const e = postEntry(post({ paid: 1 }), { user: VREEMDE, site: SITE }, { renderBody });
|
|---|
| 116 | assert.equal(e.access, 'paid');
|
|---|
| 117 | assert.equal(e.content_html, null, 'het lijf wordt niet eens gerenderd');
|
|---|
| 118 | assert.match(e.teaser, /eerste alinea/);
|
|---|
| 119 | assert.ok(!JSON.stringify(e).includes('tweede alinea'), 'en niets erachter lekt mee');
|
|---|
| 120 | });
|
|---|
| 121 |
|
|---|
| 122 | test('een fan-bericht ook niet', () => {
|
|---|
| 123 | const e = postEntry(post({ fan_only: 1 }), { user: VREEMDE, site: SITE }, { renderBody });
|
|---|
| 124 | assert.equal(e.access, 'fan');
|
|---|
| 125 | assert.equal(e.content_html, null);
|
|---|
| 126 | });
|
|---|
| 127 |
|
|---|
| 128 | test('een concept levert helemaal niets op', () => {
|
|---|
| 129 | const e = postEntry(post({ status: 'draft' }), { user: VREEMDE, site: SITE }, { renderBody });
|
|---|
| 130 | assert.equal(e.access, 'forbidden');
|
|---|
| 131 | assert.equal(e.content_html, null);
|
|---|
| 132 | assert.equal(e.teaser, null, 'ook geen teaser: over een concept valt niets te zeggen');
|
|---|
| 133 | });
|
|---|
| 134 |
|
|---|
| 135 | test('zonder renderer nog steeds geen lijf, en geen fout', () => {
|
|---|
| 136 | const e = postEntry(post(), { user: VREEMDE, site: SITE });
|
|---|
| 137 | assert.equal(e.access, 'full');
|
|---|
| 138 | assert.equal(e.content_html, null);
|
|---|
| 139 | });
|
|---|