teach/test/cms.test.js
Ramon aa4c5faaf9
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 31s
feat: server-kant CMS voor de systeemmanager (v0.3.83-beta)
- Migratie 014: site_content (sleutel/waarde, JSONB) voor site-inhoud die zonder release aanpasbaar moet zijn
- GET /content (publiek): welkomstwoord, update-melding en widget-uitleg in één antwoord - het inlogscherm toont het welkomstwoord al vóór het inloggen
- PUT /admin/content/:key (alleen systeemmanager, nieuwe permissie content.manage): upsert per toegestane sleutel (welcome/announcement/widgetInfo) met maat-limieten
- 4 servertests: publiek lezen, upsert door super, 403/401 voor anderen, whitelist/vorm/omvang-validatie

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-16 19:29:08 +02:00

84 lines
3.6 KiB
JavaScript

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();
});