| 1 | // /cirkel viel om op een media_json die geldig JSON was maar geen array.
|
|---|
| 2 | //
|
|---|
| 3 | // Gevonden op 14 augustus 2026: boiert.eu gaf 500 op /cirkel terwijl de
|
|---|
| 4 | // voorpagina het deed. Van buitenaf uitgebinaird over ?offset= -- rij 19 van de
|
|---|
| 5 | // eerste 72 brak het -- en de stacktrace bevestigde het:
|
|---|
| 6 | //
|
|---|
| 7 | // TypeError: safeJson(...).map is not a function
|
|---|
| 8 | // at coverMedia (src/routes/circle.js:25)
|
|---|
| 9 | //
|
|---|
| 10 | // Een remote server stuurde media_json = "[]": een STRING met daarin `[]`.
|
|---|
| 11 | // JSON.parse geeft dan een string terug, en een string heeft geen .map. De
|
|---|
| 12 | // catch ving alleen KAPOTTE json, niet geldige json van het verkeerde type.
|
|---|
| 13 | //
|
|---|
| 14 | // Exact dezelfde fout stond al beschreven in views/partials/note-body.ejs, waar
|
|---|
| 15 | // drie vreemde notes de Krant meenamen. Die les was hier nooit toegepast.
|
|---|
| 16 | import { test } from 'node:test';
|
|---|
| 17 | import assert from 'node:assert/strict';
|
|---|
| 18 |
|
|---|
| 19 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 20 | process.env.PUBLIC_BASE_URL = 'https://ons.test';
|
|---|
| 21 |
|
|---|
| 22 | const dbMod = await import('../src/config/database.js');
|
|---|
| 23 | const db = dbMod.default;
|
|---|
| 24 | dbMod.initializeDatabase();
|
|---|
| 25 | const express = (await import('express')).default;
|
|---|
| 26 | const circle = (await import('../src/routes/circle.js')).default;
|
|---|
| 27 |
|
|---|
| 28 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
|
|---|
| 29 | .run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 30 | db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)')
|
|---|
| 31 | .run('s1', 'dev', 'Dev', 'u1');
|
|---|
| 32 |
|
|---|
| 33 | // Een post in de cirkel met precies de vorm die het omver haalde.
|
|---|
| 34 | const zet = (id, media) => {
|
|---|
| 35 | db.prepare(`INSERT INTO ap_timeline (id, slug, author_uri, author_name, content, url, published, media_json)
|
|---|
| 36 | VALUES (?,?,?,?,?,?,?,?)`)
|
|---|
| 37 | .run(id, 'dev', 'https://elders.test/u/a', 'Iemand', '<p><strong>Titel</strong></p>',
|
|---|
| 38 | 'https://elders.test/n/' + id, '2026-08-13T12:00:00Z', media);
|
|---|
| 39 | db.prepare(`INSERT INTO ap_my_reactions (site_slug, target_uri, kind) VALUES (?,?,'boost')`)
|
|---|
| 40 | .run('dev', id);
|
|---|
| 41 | };
|
|---|
| 42 |
|
|---|
| 43 | const app = express();
|
|---|
| 44 | app.use((req, res, next) => {
|
|---|
| 45 | res.locals.site = db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
|
|---|
| 46 | res.locals.siteUrlBase = '';
|
|---|
| 47 | next();
|
|---|
| 48 | });
|
|---|
| 49 | app.use(circle);
|
|---|
| 50 | app.use((req, res) => res.status(404).end());
|
|---|
| 51 | const server = app.listen(0);
|
|---|
| 52 | await new Promise((r) => server.once('listening', r));
|
|---|
| 53 | const poort = server.address().port;
|
|---|
| 54 | const haal = async (pad) => (await fetch(`http://127.0.0.1:${poort}${pad}`)).status;
|
|---|
| 55 |
|
|---|
| 56 | test('media_json als STRING met [] erin sloopte de pagina', async () => {
|
|---|
| 57 | zet('n-string', '"[]"');
|
|---|
| 58 | assert.equal(await haal('/cirkel'), 200, 'geen 500 meer');
|
|---|
| 59 | assert.equal(await haal('/cirkel?append=1'), 200, 'ook niet op het append-pad');
|
|---|
| 60 | });
|
|---|
| 61 |
|
|---|
| 62 | test('en de andere vormen die geen array zijn ook niet', async () => {
|
|---|
| 63 | // Geldig JSON, verkeerd type: een object, een getal, een kale string, null.
|
|---|
| 64 | zet('n-obj', '{"url":"https://x/y.jpg"}');
|
|---|
| 65 | zet('n-num', '42');
|
|---|
| 66 | zet('n-str', '"gewoon tekst"');
|
|---|
| 67 | zet('n-null', 'null');
|
|---|
| 68 | assert.equal(await haal('/cirkel'), 200);
|
|---|
| 69 | });
|
|---|
| 70 |
|
|---|
| 71 | test('een ECHTE array werkt nog gewoon', async () => {
|
|---|
| 72 | zet('n-goed', JSON.stringify([{ url: 'https://elders.test/p.jpg', type: 'image/jpeg' }]));
|
|---|
| 73 | assert.equal(await haal('/cirkel'), 200);
|
|---|
| 74 | });
|
|---|
| 75 |
|
|---|
| 76 | test('en kapotte json blijft ook afgevangen', async () => {
|
|---|
| 77 | zet('n-kapot', '{niet eens json');
|
|---|
| 78 | assert.equal(await haal('/cirkel'), 200);
|
|---|
| 79 | });
|
|---|
| 80 |
|
|---|
| 81 | test.after(() => server.close());
|
|---|