import test from 'node:test'; import assert from 'node:assert/strict'; import Fastify from 'fastify'; import cookie from '@fastify/cookie'; import rateLimit from '@fastify/rate-limit'; import api from '../src/api.js'; // Gemockte pool zoals in schools.test.js: sessie-join levert `user`, // site_content-queries worden per test beantwoord/vastgelegd. function makeApp(user, rows = []) { const calls = []; const pool = { async query(sql, params = []) { calls.push({ sql, params }); if (sql.includes('FROM sessions s JOIN users u')) return { rows: user ? [user] : [] }; if (sql.includes('FROM user_roles')) return { rows: [] }; if (sql.includes('FROM site_content')) return { rows }; return { rows: [] }; }, }; return (async () => { const app = Fastify({ trustProxy: 2 }); await app.register(cookie); await app.register(rateLimit, { global: false }); app.decorate('pg', pool); await app.register(api, { prefix: '/api' }); await app.ready(); return { app, calls }; })(); } const cookies = { teach_session: 'x'.repeat(64) }; const superUser = { id: 1, username: 'sm', role: 'super', school_id: null, class_id: null, data: {}, data_rev: 0 }; const admin = { id: 3, username: 'beheer', role: 'admin', school_id: 2, class_id: null, data: {}, data_rev: 0 }; test('site-inhoud is publiek leesbaar als sleutel/waarde-object', async () => { const { app } = await makeApp(null, [ { key: 'welcome', value: { nl: 'Welkom!', en: 'Welcome!' } }, { key: 'announcement', value: { active: true, nl: 'Update', en: 'Update', id: 7 } }, ]); const res = await app.inject({ url: '/api/content' }); assert.equal(res.statusCode, 200, res.body); const body = res.json(); assert.equal(body.welcome.nl, 'Welkom!'); assert.equal(body.announcement.active, true); await app.close(); }); test('systeemmanager slaat inhoud op via upsert', async () => { const { app, calls } = await makeApp(superUser); const res = await app.inject({ method: 'PUT', url: '/api/admin/content/welcome', cookies, payload: { nl: 'Hallo school!', en: 'Hello school!' } }); assert.equal(res.statusCode, 200, res.body); const upsert = calls.find((c) => c.sql.includes('INSERT INTO site_content')); assert.ok(upsert.sql.includes('ON CONFLICT (key) DO UPDATE')); assert.equal(upsert.params[0], 'welcome'); assert.equal(upsert.params[1].nl, 'Hallo school!'); assert.equal(upsert.params[2], 1); await app.close(); }); test('schoolbeheerder en gasten mogen geen inhoud schrijven', async () => { const { app } = await makeApp(admin); const res = await app.inject({ method: 'PUT', url: '/api/admin/content/welcome', cookies, payload: { nl: 'x' } }); assert.equal(res.statusCode, 403); const { app: app2 } = await makeApp(null); const res2 = await app2.inject({ method: 'PUT', url: '/api/admin/content/welcome', payload: { nl: 'x' } }); assert.equal(res2.statusCode, 401); await app.close(); await app2.close(); }); test('alleen bekende sleutels, objecten en begrensde omvang worden geaccepteerd', async () => { const { app } = await makeApp(superUser); const unknown = await app.inject({ method: 'PUT', url: '/api/admin/content/hack', cookies, payload: { a: 1 } }); assert.equal(unknown.statusCode, 404); const arr = await app.inject({ method: 'PUT', url: '/api/admin/content/welcome', cookies, payload: [1, 2] }); assert.equal(arr.statusCode, 400); const huge = await app.inject({ method: 'PUT', url: '/api/admin/content/welcome', cookies, payload: { nl: 'x'.repeat(9 * 1024) } }); assert.equal(huge.statusCode, 413); await app.close(); });