All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 31s
- 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
84 lines
3.6 KiB
JavaScript
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();
|
|
});
|