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'; // App met een instelbare ingelogde gebruiker en vastgelegde queries; de // shared_library-select levert een vaste mix van systeembrede en school-items. function makeApp(user, sharedRows = []) { 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.includes('FROM shared_library')) return { rows: sharedRows }; if (sql.includes('INSERT INTO shared_library') && sql.includes('RETURNING id')) return { rows: [{ id: 42 }] }; 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 teacher = { id: 7, username: 'juf', role: 'teacher', school_id: 2, class_id: null, data: {}, data_rev: 0 }; const pupil = { id: 9, username: 'kind', role: 'pupil', school_id: 2, class_id: 5, data: {}, data_rev: 0 }; test('GET /shared/:kind filtert op systeembreed + eigen school voor staf', async () => { const { app, calls } = await makeApp(teacher, [ { id: 1, school_id: null, owner_id: 1, folder: 'Rekenen', name: 'Breuken', owner_name: 'SM' }, ]); const res = await app.inject({ method: 'GET', url: '/api/shared/board', cookies }); assert.equal(res.statusCode, 200, res.body); const items = res.json().items; assert.equal(items[0].scope, 'global'); assert.equal(items[0].canManage, false); const q = calls.find((c) => c.sql.includes('FROM shared_library')); assert.ok(q.sql.includes('school_id IS NULL OR s.school_id = $2')); assert.equal(q.params[1], 2); await app.close(); }); test('leerlingen hebben geen toegang tot de gedeelde bibliotheek', async () => { const { app } = await makeApp(pupil); const res = await app.inject({ method: 'GET', url: '/api/shared/board', cookies }); assert.equal(res.statusCode, 403); await app.close(); }); test('publiceren met scope global vereist systeemmanager', async () => { const { app } = await makeApp(teacher); const res = await app.inject({ method: 'POST', url: '/api/shared/board', cookies, payload: { name: 'Breuken', folder: 'Rekenen', data: { widgets: [] }, scope: 'global' } }); assert.equal(res.statusCode, 403); const { app: superApp, calls } = await makeApp(superUser); const ok = await superApp.inject({ method: 'POST', url: '/api/shared/board', cookies, payload: { name: 'Breuken', folder: 'Rekenen', data: { widgets: [] }, scope: 'global' } }); assert.equal(ok.statusCode, 200, ok.body); const ins = calls.find((c) => c.sql.includes('INSERT INTO shared_library')); assert.equal(ins.params[0], null); // school_id NULL = systeembreed await superApp.close(); await app.close(); }); test('publiceren met scope school gebruikt de eigen school', async () => { const { app, calls } = await makeApp(teacher); const res = await app.inject({ method: 'POST', url: '/api/shared/anchor', cookies, payload: { name: 'water', data: { cells: {} }, scope: 'school' } }); assert.equal(res.statusCode, 200, res.body); const ins = calls.find((c) => c.sql.includes('INSERT INTO shared_library')); assert.equal(ins.params[0], 2); assert.equal(ins.params[2], 'anchor'); await app.close(); }); test('beheren mag alleen door eigenaar, schoolbeheerder of systeemmanager', async () => { // teacher (id 7) probeert andermans school-item te verwijderen const foreignItem = { id: 5, school_id: 2, owner_id: 8, kind: 'board', folder: '', name: 'X', data: {} }; const { app } = await makeApp(teacher, [foreignItem]); const res = await app.inject({ method: 'DELETE', url: '/api/shared/board/5', cookies }); assert.equal(res.statusCode, 403); // eigenaar mag wel const ownItem = { ...foreignItem, owner_id: 7 }; const { app: app2 } = await makeApp(teacher, [ownItem]); const res2 = await app2.inject({ method: 'DELETE', url: '/api/shared/board/5', cookies }); assert.equal(res2.statusCode, 200, res2.body); await app2.close(); await app.close(); }); test('map-rewrite weigert als niet alle items beheerd mogen worden', async () => { const rows = [ { id: 5, school_id: 2, owner_id: 7, kind: 'board', folder: 'Taal', name: 'A', data: {} }, { id: 6, school_id: 2, owner_id: 8, kind: 'board', folder: 'Taal/Sub', name: 'B', data: {} }, ]; const { app } = await makeApp(teacher, rows); const res = await app.inject({ method: 'PATCH', url: '/api/shared/board/folder', cookies, payload: { scope: 'school', from: 'Taal', to: 'Lezen' } }); assert.equal(res.statusCode, 403); await app.close(); });