teach/test/progress-export.test.js
Ramon 8fe6fc10cc
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 51s
feat: exporteren van resultaten per klas (CSV) (v0.4.29-beta)
- Nieuwe sectie "Exporteren per klas" in de bestaande Voortgang-tab: klas
  kiezen + "Exporteer als CSV" downloadt de volledige activiteitenlijst
  van die klas (leerling, widget, pogingen, goed, sterren, datum/tijd).
- Bouwt voort op de bestaande route GET /progress/class/:id, die tot nu
  toe geen enkele frontend-consument had.
- Server: nieuwe optionele ?limit=-queryparameter (begrensd op 5000) -
  de export vraagt expliciet meer dan de standaard activiteitenweergave
  (200) op, zodat een drukke klas niet stilzwijgend wordt afgekapt in het
  exportbestand. Zonder de parameter blijft het bestaande gedrag (200)
  ongewijzigd.
- Eerste CSV-export in deze codebase: kleine lokale csvValue()/
  downloadCsv()-helpers in admin.js (komma/quote/newline-escaping,
  BOM zodat Excel-NL accenten en komma-scheiding herkent) - bewust nog
  niet gedeeld/verplaatst naar core.js, want er is nu nog maar één
  gebruiker (de geplande wachtwoorden-export kan dit later hergebruiken).
- Tests: test/progress-export.test.js (limit wordt doorgegeven, te hoge
  waarde wordt afgekapt op 5000, standaardgedrag zonder parameter blijft
  200) + volledige testsuite (112/112). Ook live geverifieerd: klas
  kiezen zonder selectie toont een hint i.p.v. te crashen, een echte
  CSV-download met correcte escaping van komma's/aanhalingstekens in
  een leerlingnaam, en de vertaalde widget-namen in de kolommen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 12:54:48 +02:00

56 lines
2.3 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) {
const calls = [];
const query = async (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('SELECT * FROM classes WHERE id')) return { rows: [{ id: 100, school_id: 2, name: 'Groep 4' }] };
if (sql.includes('FROM progress_events pe')) return { rows: [] };
return { rows: [] };
};
const pool = { query, async connect() { return { query, release() {} }; } };
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 admin = { id: 3, username: 'beheer', role: 'admin', school_id: 2, class_id: null, data: {}, data_rev: 0 };
function progressCall(calls) {
return calls.find(c => c.sql.includes('FROM progress_events pe'));
}
test('resultaten-export: ?limit= wordt doorgegeven aan de query', async () => {
const { app, calls } = await makeApp(admin);
const res = await app.inject({ method: 'GET', url: '/api/progress/class/100?limit=3000', cookies });
assert.equal(res.statusCode, 200, res.body);
assert.equal(progressCall(calls).params[1], 3000);
});
test('resultaten-export: een te hoge limit wordt afgekapt op het veilige maximum', async () => {
const { app, calls } = await makeApp(admin);
const res = await app.inject({ method: 'GET', url: '/api/progress/class/100?limit=999999', cookies });
assert.equal(res.statusCode, 200, res.body);
assert.equal(progressCall(calls).params[1], 5000);
});
test('resultaten-export: zonder limit-param blijft het standaardgedrag (200) behouden', async () => {
const { app, calls } = await makeApp(admin);
const res = await app.inject({ method: 'GET', url: '/api/progress/class/100', cookies });
assert.equal(res.statusCode, 200, res.body);
assert.equal(progressCall(calls).params[1], 200);
});