teach/test/schools.test.js
Ramon a9bf2270d0
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 38s
fix: beperk rollen en klaskoppeling tot staf (v0.3.56-beta)
- Functie-select in gebruikersbeheer alleen zichtbaar voor staf-rollen (teacher/admin/super)
- Klaskoppeling-UI alleen voor staf-rollen
- Server: rolwijziging alleen tussen staf-rollen onderling (leerlingen en toekomstige ouder-accounts kunnen nooit promoveren)
- Server: klaskoppeling weigert alle niet-staf-rollen expliciet
- canPersist() sluit de komende ouderrol uit van borden-opslag
- Servertest: ouder-achtige rol krijgt geen promotie en geen klaskoppeling

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0149FgUQvuwxKKEdvQGmNngF
2026-07-15 23:56:39 +02:00

109 lines
5.1 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';
function makeApp(user, targetUser) {
const calls = [];
const pool = {
async query(sql, params = []) {
calls.push({ sql, params });
if (sql.includes('FROM sessions s JOIN users u')) return { rows: [user] };
if (sql.includes('FROM user_roles')) return { rows: [] };
if (sql.startsWith('UPDATE schools SET name')) return { rows: [{ id: params[1], name: params[0] }] };
if (sql.startsWith('SELECT * FROM classes WHERE id')) return { rows: [{ id: 100, school_id: 2, name: 'Groep 4' }] };
if (sql.startsWith('SELECT * FROM users WHERE id') && targetUser) return { rows: [targetUser] };
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('systeemmanager kan een school hernoemen', async () => {
const { app, calls } = await makeApp(superUser);
const res = await app.inject({ method: 'PATCH', url: '/api/admin/schools/5', cookies,
payload: { name: 'De Nieuwe Naam' } });
assert.equal(res.statusCode, 200, res.body);
assert.equal(res.json().school.name, 'De Nieuwe Naam');
const upd = calls.find((c) => c.sql.startsWith('UPDATE schools SET name'));
assert.deepEqual(upd.params, ['De Nieuwe Naam', '5']);
await app.close();
});
test('schoolbeheerder mag geen scholen hernoemen', async () => {
const { app } = await makeApp(admin);
const res = await app.inject({ method: 'PATCH', url: '/api/admin/schools/5', cookies,
payload: { name: 'Hack' } });
assert.equal(res.statusCode, 403);
await app.close();
});
test('schoolbeheerder en systeemmanager kunnen aan een klas gekoppeld worden', async () => {
// schoolbeheerder van dezelfde school
const { app, calls } = await makeApp(superUser, { id: 3, role: 'admin', school_id: 2 });
const res = await app.inject({ method: 'POST', url: '/api/admin/classes/100/teachers', cookies,
payload: { userId: 3 } });
assert.equal(res.statusCode, 200, res.body);
assert.ok(calls.some((c) => c.sql.includes('INSERT INTO class_teachers')));
await app.close();
// systeemmanager (geen school) mag ook
const { app: app2 } = await makeApp(superUser, { id: 1, role: 'super', school_id: null });
const res2 = await app2.inject({ method: 'POST', url: '/api/admin/classes/100/teachers', cookies,
payload: { userId: 1 } });
assert.equal(res2.statusCode, 200, res2.body);
await app2.close();
// leerlingen niet, en staf van een andere school niet
const { app: app3 } = await makeApp(superUser, { id: 9, role: 'pupil', school_id: 2 });
const res3 = await app3.inject({ method: 'POST', url: '/api/admin/classes/100/teachers', cookies,
payload: { userId: 9 } });
assert.equal(res3.statusCode, 400);
await app3.close();
const { app: app4 } = await makeApp(superUser, { id: 4, role: 'admin', school_id: 3 });
const res4 = await app4.inject({ method: 'POST', url: '/api/admin/classes/100/teachers', cookies,
payload: { userId: 4 } });
assert.equal(res4.statusCode, 400);
await app4.close();
});
test('ouder-achtige rol krijgt geen promotie en geen klaskoppeling', async () => {
// rolwijziging: alleen staf → staf; een parent-account mag nooit promoveren
const { app, calls } = await makeApp(superUser, { id: 7, role: 'parent', school_id: null });
const res = await app.inject({ method: 'PATCH', url: '/api/admin/users/7', cookies,
payload: { role: 'admin' } });
assert.equal(res.statusCode, 200, res.body);
assert.ok(!calls.some((c) => c.sql.startsWith('UPDATE users SET role')),
'rolwijziging mag voor een ouder-account niet uitgevoerd worden');
await app.close();
// klaskoppeling: parent weigeren, net als pupil
const { app: app2, calls: calls2 } = await makeApp(superUser, { id: 7, role: 'parent', school_id: null });
const res2 = await app2.inject({ method: 'POST', url: '/api/admin/classes/100/teachers', cookies,
payload: { userId: 7 } });
assert.equal(res2.statusCode, 400);
assert.ok(!calls2.some((c) => c.sql.includes('INSERT INTO class_teachers')));
await app2.close();
});
test('hernoemen weigert een lege of te lange naam', async () => {
const { app } = await makeApp(superUser);
const res = await app.inject({ method: 'PATCH', url: '/api/admin/schools/5', cookies,
payload: { name: ' ' } });
assert.equal(res.statusCode, 400);
const res2 = await app.inject({ method: 'PATCH', url: '/api/admin/schools/5', cookies,
payload: { name: 'x'.repeat(90) } });
assert.equal(res2.statusCode, 400);
await app.close();
});