PDF-export met QuestPDF
16.1 Doel en scope
Dit hoofdstuk beschrijft hoe OefenHub PDF-export technisch realiseert met QuestPDF. De PDF-export is bedoeld voor resultaatweergaven van afgeronde oefenruns en gebruikt dezelfde historische brondata als de schermweergave van resultaten.
De PDF-export is geen zelfstandige bron van waarheid. Een PDF-bestand is een tijdelijke representatie die op aanvraag wordt opgebouwd uit opgeslagen rungegevens, voortgangsdata, snapshots, statistiekvelden en module-specifieke exportrepresentaties.
Dit hoofdstuk beschrijft:
- de verantwoordelijkheid van
OefenHub.Reporting; - de relatie met
OefenHub.Practice,OefenHub.ExerciseModuleHosten concrete oefenmodules; - de technische opbouw van PDF-documenten met QuestPDF;
- de bestandsnaamlogica;
- de omgang met tabellen, pagina-einden, headers en footers;
- tijdelijke opslag en cleanup;
- autorisatie, logging, privacy en foutafhandeling;
- teststrategie en implementatiecontrole.
Dit hoofdstuk definieert geen nieuwe functionele inhoud van de PDF. De functionele inhoud volgt uit Functioneel Ontwerp, Software Requirements Specification, schermdocumentatie en de historische runcontext.
16.2 Eigenaarschap en modulegrenzen
PDF-export valt technisch onder OefenHub.Reporting. Deze module is verantwoordelijk voor exportorkestratie, documentopbouw en tijdelijke output. De module is niet eigenaar van oefenruns, vraaginhoud, resultaten, relaties, autorisaties of moduleconfiguraties.
| Onderdeel | Eigenaar | Toelichting |
|---|---|---|
| Exportorkestratie | OefenHub.Reporting | Bouwt exportmodel op via publieke query-services en rendercontracten. |
| Oefenrunbrondata | OefenHub.Practice | Levert afgeronde run, voortgang, antwoorden, totalen, statistieken en snapshots. |
| Module-specifieke vraag-/antwoordrepresentatie | Concrete oefenmodule via OefenHub.ExerciseModuleHost | Levert PDF-veilige representaties voor bijvoorbeeld breuken, machten of samengestelde antwoorden. |
| Autorisatiecontrole | OefenHub.Authorization met context uit betrokken domeinen | Controleert of gebruiker de run mag exporteren. |
| Bestandresponse | OefenHub.Web | Stuurt tijdelijke PDF als download naar de gebruiker. |
| Tijdelijke bestandsopslag | OefenHub.Reporting / infrastructuurconfiguratie | Alleen voor tijdelijke output, niet als bronrecord. |
| Cleanup | OefenHub.Scheduling triggert, Reporting voert uit | Verwijdert verlopen tijdelijke exportbestanden. |
OefenHub.Reporting mag geen directe toegang hebben tot PracticeDbContext, CatalogDbContext of module-interne entities. Alle brondata wordt opgehaald via publieke contracts/query-services.
Voorbeeld van toegestane afhankelijkheden:
OefenHub.Reporting
gebruikt:
- IExerciseRunExportReader uit OefenHub.Practice.Contracts
- IAuthorizationContextService uit OefenHub.Authorization.Contracts
- IExerciseModuleExportRenderer via OefenHub.ExerciseModuleHost
- IClock / Correlation primitives uit SharedKernel
Niet toegestaan:
OefenHub.Reporting
gebruikt niet:
- PracticeDbContext
- ExerciseRun entity
- CatalogDbContext
- concrete module-interne services
- Web componenten of Razor markup
16.3 Plaats in de solution
De technische PDF-export gebruikt de volgende projectstructuur.
src/
OefenHub.Reporting/
Contracts/
IResultPdfExportService.cs
Models/
Enums/
Models/
ReadModels/
Internal/
Services/
Interfaces/
Rendering/
Documents/
Files/
Extensions/
OefenHub.Practice/
Contracts/
IExerciseRunExportReader.cs
Models/
ExerciseRunExportModel.cs
OefenHub.ExerciseModuleHost/
Contracts of public services for module resolving
OefenHub.Modules.Abstractions/
IExerciseModulePdfRenderer.cs
Models for module export representation
OefenHub.Web/
Pages or Components that request export
OefenHub.Reporting krijgt alleen een eigen ReportingDbContext en schema reporting als via een expliciet technisch ontwerpbesluit persistente exportmetadata, exportjobs of exporthistorie functioneel nodig zijn. In de eerste technische baseline is PDF-output tijdelijk en wordt er geen verplicht permanent documentrecord aangemaakt.
16.4 Exportflow op hoofdlijnen
Een PDF-export verloopt technisch in vaste stappen.
| Stap | Verantwoordelijke module | Resultaat |
|---|---|---|
| 1. Gebruiker vraagt export aan | Web | Exportcommand met run-id en actuele rolcontext. |
| 2. Autorisatiecontrole | Authorization en brondomein | Alleen toegestane gebruiker mag verder. |
| 3. Exportbrondata ophalen | Practice | Historisch exportmodel met run, voortgang, snapshots en statistieken. |
| 4. Module-exportrepresentaties ophalen | ExerciseModuleHost en concrete module | PDF-veilige vraag- en antwoordweergave. |
| 5. PDF-document opbouwen | Reporting | QuestPDF-documentobject. |
| 6. Tijdelijk bestand of stream maken | Reporting | PDF-bytearray, stream of tijdelijk bestand. |
| 7. Downloadresponse leveren | Web | Browser ontvangt PDF met veilige bestandsnaam. |
| 8. Tijdelijke output opruimen | Scheduling + Reporting | Oude tijdelijke bestanden worden verwijderd. |
De export schrijft de oefenrun, voortgang, score, statistieken, relaties of gebruikersinstellingen niet bij.
16.5 Exportbronnen
De export gebruikt uitsluitend historische en geautoriseerde brondata. Er wordt niet opnieuw functioneel berekend vanuit clientstate.
| Bron | Gebruik in PDF |
|---|---|
| ExerciseRun-exportmodel | Hoofdcontext, gebruiker, niveau, categorie, oefening, module, aantallen en statussen. |
| ExerciseRunProgress/exportregels | Vraagvolgorde, opgave, gegeven antwoord, juiste antwoord, resultaat, Geen-idee-markering en timings. |
| Runstatistieken | Gemiddelde tijd, mediaan, grenzen, uitschieters, totale doorlooptijd en overige opgeslagen statistiekvelden. |
| Snapshots | Historische naamgeving en context wanneer actuele brondata is gewijzigd of niet meer bruikbaar is. |
| Module-exportrepresentatie | PDF-veilige weergave van module-specifieke inhoud. |
| Exportaanvragercontext | Autorisatie, logging en bestandsnaamcontext. |
De export mag actuele catalogus- of moduledetaildata alleen gebruiken wanneer deze volgens het Practice-exportmodel nog geldig en passend is. Bij conflict of ontbrekende actuele data zijn de snapshots en opgeslagen payloads leidend.
16.6 Autorisatie per exportcontext
Iedere PDF-export herhaalt server-side de autorisatiecontrole. Een run-id, routeparameter, browsergeschiedenis of eerder geopende resultaatweergave is nooit voldoende.
| Exportcontext | Autorisatiegrens |
|---|---|
| Leerling | Eigen afgeronde niet-test run binnen toegestane context, of eigen historische run volgens leerlingregels. |
| Docent | Afgeronde leerlingrun binnen de eigen docentcontext en niveauautorisatiegrens. |
| Ouder/voogd | Afgeronde run van actief gekoppeld kind, over alle historische niveaus. |
| Beheerder | Alleen wanneer een expliciete beheer- of supportflow dit toestaat; reguliere live meekijk- of leerlingcontext wordt niet geïmpliceerd. |
Bij ontbrekende toegang retourneert de export geen gedeeltelijke resultaatdata en geen PDF met foutinhoud. De gebruiker krijgt een veilige foutafhandeling vanuit Web.
16.7 Exportmodel vanuit Practice
OefenHub.Practice levert een exportmodel dat onafhankelijk is van EF Core entities.
Voorbeeldstructuur:
ExerciseRunExportModel
RunId
ViewerContext
StudentSnapshot
LevelSnapshot
CategorySnapshot
ExerciseSnapshot
ModuleKey
ModuleVersionSnapshot
CompletedAtUtc
TotalQuestionCount
TotalCorrect
TotalIncorrect
TotalDunno
Statistics
Questions[]
Voor een vraagregel:
ExerciseRunExportQuestionModel
QuestionNumber
ModuleQuestionPayload
ModuleAnswerPayload
GivenAnswerPayload
IsCorrect
IsDunno
FirstShownAtUtc
CompletedAtUtc
Duration
Reporting mag deze payloads niet zelf inhoudelijk interpreteren buiten generieke velden om. Module-specifieke payloads worden via ExerciseModuleHost aan de juiste module-exportrenderer aangeboden.
16.8 Module-specifieke PDF-representatie
Concrete oefenmodules kunnen een PDF-representatie leveren voor vraag, gegeven antwoord en juiste antwoord. Dit voorkomt dat Reporting kennis nodig heeft van breuken, wortels, machten, taalopgaven of samengestelde antwoordstructuren.
Het contract kan conceptueel bestaan uit:
IExerciseModulePdfRenderer
RenderQuestionForPdf(modulePayload, exportContext)
RenderGivenAnswerForPdf(modulePayload, answerPayload, exportContext)
RenderCorrectAnswerForPdf(modulePayload, exportContext)
De concrete returnvorm moet QuestPDF-veilig zijn. Dit kan bijvoorbeeld een neutrale export-DTechnisch Ontwerp zijn en geen directe afhankelijkheid op Web/Razor.
Voorbeeld:
ModulePdfContent
PlainText
InlineParts[]
AccessibilityText
RenderKind
Mogelijke renderkinderen:
| RenderKind | Gebruik |
|---|---|
PlainText | Simpele tekst of getallen. |
RichTextParts | Beperkte opmaak, bijvoorbeeld vet of superscript. |
MathExpression | Module-specifieke wiskundige notatie. |
FallbackText | Veilige fallback wanneer rijke rendering niet beschikbaar is. |
Een concrete module mag geen QuestPDF-documentlayout van het hele resultaat bepalen. De module levert alleen inhoudsrepresentatie voor haar eigen vraag-/antwoordonderdelen. De generieke PDF-structuur blijft eigendom van Reporting.
16.9 QuestPDF-documentopbouw
De PDF wordt opgebouwd als generiek resultaatdocument met vaste secties.
| Sectie | Inhoud |
|---|---|
| Titelblok | Titel, subtitel met categorie en oefening, eventueel contextlabel. |
| Samenvatting | Gebruiker, afrondmoment, aantal vragen, goed, fout, Geen idee indien van toepassing. |
| Resultaattabel | Vraagnummer, vraag, gegeven antwoord, juiste antwoord, resultaat en markering. |
| Statistieken | Gemiddelde, mediaan, ondergrens, bovengrens, doorlooptijd en uitschieters. |
| Duplicaat-/deelcontext | Alleen wanneer de historische runcontext dit vereist. |
| Footer | Applicatienaam, paginanummering en exportdatum. |
De documentopbouw is generiek. Module-specifieke inhoud wordt alleen binnen tabelcellen of detailblokken geplaatst via de module-exportrepresentatie.
16.10 Layoutregels
De PDF moet leesbaar blijven bij lange vragen, lange antwoorden en meerdere pagina’s.
| Onderdeel | Regel |
|---|---|
| Paginaformaat | Standaard A4, portrait, tenzij via een expliciet technisch ontwerpbesluit expliciet anders besloten. |
| Marges | Vaste marges met voldoende ruimte voor footer. |
| Lettertype | Technisch te kiezen beschikbaar font; geen fontbestand meeleveren in repository zonder licentiecontrole. |
| Resultaattabel | Kolomheaders worden op vervolgpagina’s herhaald. |
| Tabelrijen | Een rij wordt bij voorkeur niet over pagina’s gesplitst. |
| Lange tekst | Vraag en antwoorden mogen over meerdere regels afbreken. |
| Vervolgtekst | Bij voortzetting van de resultaattabel wordt een duidelijke vervolgindicatie getoond. |
| Statistiekensectie | Waar mogelijk als geheel bij elkaar houden. |
| Footer | Op iedere pagina dezelfde structuur. |
De export mag tekst niet inhoudelijk inkorten om layoutproblemen op te lossen. Als tekst lang is, moet de layout daarop reageren met regelafbreking en paginaovergang.
16.11 Footer en paginanummering
De footer is generiek en consistent.
Links: OefenHub
Midden: Pagina X van Y
Rechts: Exportdatum
De exportdatum wordt bepaald op het moment dat de PDF wordt gegenereerd. Het afrondmoment van de oefenrun blijft de datum die in de samenvatting wordt getoond voor het resultaat.
De footer mag geen persoonsgegevens tonen.
16.12 Bestandsnaamlogica
De bestandsnaam wordt server-side opgebouwd en gesanitized.
Basisvorm:
<yyyy_MM_dd-HH.mm>_OefenHub_Resultaat_<Categorie>_<Oefening>.pdf
Regels:
| Regel | Toelichting |
|---|---|
| Datumdeel | Gebaseerd op exportmoment of functioneel gekozen exporttijd; standaard exportmoment. |
| Ongeldige tekens | Verwijderen of vervangen door veilige underscore. |
| Spaties | Normaliseren en vervangen door underscores. |
| Dubbele underscores | Samenvoegen. |
| Lengte categorie | Begrenzen volgens functionele bestandsnaamregels. |
| Lengte oefening | Begrenzen volgens functionele bestandsnaamregels. |
| Extensie | Altijd .pdf. |
| Persoonsgegevens | Niet opnemen in bestandsnaam. |
Voorbeeld:
2026_05_08-14.32_OefenHub_Resultaat_Rekenen_Optellen.pdf
De bestandsnaam gebruikt snapshots wanneer de historische context dat vereist.
16.13 Tijdelijke output en opslag
PDF-bestanden zijn tijdelijke output. Zij worden niet als permanente documenten opgeslagen tenzij via een expliciet technisch ontwerpbesluit een aparte functionele eis wordt toegevoegd.
Mogelijke technische varianten:
| Variant | Gebruik |
|---|---|
| Directe stream | Kleine exports die direct naar de browser kunnen worden gestuurd. |
| Tijdelijk bestand | Grotere exports of situaties waarin streaming technisch onhandig is. |
| In-memory bytearray | Alleen voor kleine exports en testscenario’s. |
Tijdelijke bestanden worden opgeslagen op een geconfigureerde locatie buiten publieke webroot. De opslaglocatie mag niet rechtstreeks via URL benaderbaar zijn.
Voor tijdelijke bestanden moet minimaal worden vastgelegd in technische logging:
CorrelationId
ExportRequestId indien aanwezig
RunId
ViewerUserId
ViewerRoleContext
FileName
CreatedAtUtc
StorageKind
CleanupEligibleAtUtc
De logging mag geen vraaginhoud, antwoorden of volledige payloads bevatten.
16.14 Cleanup van tijdelijke exports
Cleanup wordt uitgevoerd via OefenHub.Scheduling en domeinlogica in OefenHub.Reporting.
| Onderdeel | Regel |
|---|---|
| Trigger | Periodieke TickerQ-job. |
| Eigenaarschap joblifecycle | OefenHub.Scheduling. |
| Eigenaarschap exportcleanup | OefenHub.Reporting. |
| Selectie | Tijdelijke exportbestanden met CleanupEligibleAtUtc in het verleden; standaard na 24 uur. |
| Planning | Minimaal iedere 6 uur of vaker wanneer de opslaggroei dat vereist. |
| Hard maximum | Bestanden ouder dan 48 uur signaleren als cleanupachterstand. |
| Idempotentie | Opnieuw uitvoeren mag geen fout geven wanneer bestand al weg is. |
| Foutafhandeling | Fout bij één bestand blokkeert cleanup van andere bestanden niet. |
| Logging | CorrelationId/job-id, aantal verwerkt, aantal fouten en foutcategorieën. |
De bewaartermijn is configureerbaar, maar de V1.0-startwaarde is 24 uur. Verlenging van de bewaartermijn is alleen toegestaan wanneer daar een technische beheerreden voor is en privacy, opslagruimte en monitoring opnieuw zijn beoordeeld.
16.15 Transaction boundaries
PDF-export is in principe read-only voor de functionele brondomeinen.
| Actie | Transactiebeleid |
|---|---|
| Runexportmodel ophalen | Read-only query binnen Practice. |
| Module-exportrepresentatie opbouwen | Geen databasewrite in concrete module. |
| PDF genereren | Geen functionele databasewrite. |
| Tijdelijk bestand schrijven | Technische IO-actie, geen bronmutatie. |
| Exportlogging | Technische logging, geen functioneel exportrecord tenzij via een expliciet technisch ontwerpbesluit besloten. |
| Cleanup | Technische verwijdering van tijdelijke output. |
Als via een expliciet besluit persistente exportrecords worden toegevoegd, moet opnieuw worden bepaald of exportregistratie onderdeel is van de gebruikersactie of alleen technische logging is.
16.16 Foutafhandeling
PDF-export kent veilige foutcategorieën.
| Foutcategorie | Gebruikersreactie | Technische afhandeling |
|---|---|---|
| Geen toegang | Veilige toegang-geweigerdmelding | Security/access-denied logging zonder payload. |
| Run niet gevonden | Niet-beschikbaarmelding | Logging met run-id en correlation-id. |
| Run niet afgerond | Export niet beschikbaar | Geen PDF genereren. |
| Module niet beschikbaar | Export probeert fallbackrepresentatie indien toegestaan | Logging met modulekey en versie. |
| Modulepayload ongeldig | Export faalt veilig of gebruikt expliciete fallback | Geen stacktrace naar gebruiker. |
| QuestPDF-renderfout | Exportfoutmelding | Technische foutlog met correlation-id. |
| Tijdelijke opslag fout | Exportfoutmelding | Logging met storagekind, geen payload. |
| Cleanupfout | Geen gebruikersimpact | Joblogging en retry/failurestatus. |
Gebruikers krijgen nooit technische details, stacktraces, padnamen, modulepayloads of interne identifiers te zien.
16.17 Fallback bij moduleproblemen
Historische runs moeten zo lang mogelijk exporteerbaar blijven. Toch kan een gekoppelde module in de toekomst gewijzigd, verwijderd of incompatibel zijn.
Fallbackvolgorde:
- Gebruik de moduleversie of compatibele renderer die bij de run hoort.
- Gebruik een backwards-compatible renderer van dezelfde modulefamilie.
- Gebruik opgeslagen modulepayload en snapshots om veilige tekstrepresentatie te tonen als de module dit ondersteunt.
- Toon een expliciete niet-beschikbare representatie voor de betreffende vraag of export faalt veilig, afhankelijk van functionele afspraken.
De PDF mag niet stilzwijgend verkeerde antwoorden of foutieve vraagrepresentaties tonen. Bij twijfel faalt de export veiliger dan dat onjuiste onderwijsinhoud wordt geproduceerd.
16.18 Privacy en gegevensbescherming
PDF-export bevat mogelijk persoonsgegevens en onderwijsresultaten. Daarom gelden strikte privacyregels.
| Onderwerp | Regel |
|---|---|
| Autorisatie | Iedere export hercontroleert server-side toegang. |
| Bestandsnaam | Geen leerlingnaam of andere persoonsgegevens. |
| Tijdelijke opslag | Niet publiek benaderbaar. |
| Logging | Geen vraaginhoud, antwoorden of volledige payloads. |
| Cleanup | Tijdelijke bestanden worden periodiek verwijderd. |
| Browsercache | Downloadheaders moeten zo worden gekozen dat onbedoelde langdurige caching wordt beperkt waar passend. |
| Anonimisering | Bij herexport na anonimisering worden geanonimiseerde snapshotwaarden gebruikt volgens privacybeleid. |
Wanneer een eerder afgeronde run nog exporteerbaar blijft na accountanonimisering, moet de export de dan geldende geanonimiseerde waarden gebruiken en geen actuele persoonsgegevens reconstrueren.
16.19 Securitygrenzen
PDF-export volgt dezelfde securitybaseline als de rest van OefenHub.
| Grens | Regel |
|---|---|
| Routeparameters | Geen autorisatiebewijs. |
| Querystring | Geen gevoelige payloads. |
| Tijdelijke downloadtoken | Alleen gebruiken indien nodig, kortlevend en server-side gevalideerd. |
| Bestandspaden | Niet zichtbaar voor gebruiker. |
| HTML/Rich text | Niet als actieve inhoud renderen; alleen veilige exportrepresentatie. |
| Module-output | Wordt beschouwd als onbetrouwbaar totdat gevalideerd door modulecontract. |
| Externe resources | PDF gebruikt geen ongecontroleerde externe resources. |
Concrete oefenmodules mogen geen externe afbeeldingen, scripts, fonts of bestanden in de PDF trekken zonder expliciete platformondersteuning en securitycontrole.
16.20 Performance en schaalbaarheid
PDF-generatie kan relatief zwaar zijn. Daarom gelden technische grenzen.
| Onderwerp | Regel |
|---|---|
| Exportgrootte | Begrens maximale vraag-/rijaantallen of bewaak timeouts. |
| Renderingtijd | Gebruik cancellation tokens en timeouts waar technisch mogelijk. |
| Parallelle exports | Rate limiting of throttling kan nodig zijn. |
| Geheugen | Vermijd onnodig grote in-memory buffers bij grote exports. |
| Module-rendering | Module-exportrendering moet deterministisch en snel zijn. |
| Cleanup | Tijdelijke bestanden mogen opslag niet onbeperkt laten groeien. |
Als exports in een toekomstige uitbreiding groot of langdurig worden, kan een async exportflow met jobstatus nodig zijn. In de eerste technische baseline is directe export het uitgangspunt voor normale resultaat-PDF’s.
16.21 Logging en correlation
Iedere PDF-export krijgt een correlation-id. Deze wordt doorgegeven aan betrokken modules.
Minimale technische logvelden:
CorrelationId
ViewerUserId
ViewerRoleContext
RunId
ExportType
ModuleKey
ModuleVersionSnapshot indien beschikbaar
StartedAtUtc
CompletedAtUtc or FailedAtUtc
DurationMs
FailureCategory indien van toepassing
Voor module-rendering kan aanvullend worden gelogd:
QuestionCount
RendererKey
RendererVersion
FallbackUsed
Niet loggen:
Volledige vraagpayload
Gegeven antwoorden
Juiste antwoorden
PDF-bytes
Bestandspad richting gebruiker
Tokens
Credentials
16.22 Publieke contracts van Reporting
OefenHub.Reporting biedt een beperkte publieke ingang.
Voorbeelden:
IResultPdfExportService
GenerateResultPdfAsync(request, cancellationToken)
IExportFileCleanupService
CleanupExpiredExportsAsync(request, cancellationToken)
Voor request/response DTO’s:
ResultPdfExportRequest
RunId
ViewerUserId
ViewerRoleContext
PreferredCulture
CorrelationId
ResultPdfExportResult
FileName
ContentType
Stream or FileReference
Length
Web gebruikt deze service voor downloadresponses. Scheduling gebruikt cleanupcontracts voor tijdelijke exportopruiming.
16.23 Interactie met Web
OefenHub.Web toont exportacties alleen wanneer ze functioneel beschikbaar lijken, maar de server-side exportservice voert altijd opnieuw autorisatie en statuscontrole uit.
Web is verantwoordelijk voor:
- knop of link tonen;
- command naar
Reportingsturen; - loading state tonen;
- veilige foutmelding tonen;
- downloadresponse teruggeven.
Web is niet verantwoordelijk voor:
- PDF-layoutlogica;
- QuestPDF-documentconstructie;
- directe runqueries;
- modulepayloadinterpretatie;
- tijdelijke bestandscleanup.
16.24 Teststrategie
PDF-export gebruikt een gelaagde regressieteststrategie. De strategie is gericht op stabiele bewaking van inhoud, structuur, paginering, module-rendering, privacy en downloadgedrag zonder dat tests onnodig afhankelijk worden van byte-identieke PDF-output.
16.24.1 Uitgangspunt
Een PDF is een presentatielaag boven historische runcontext. Regressietests controleren daarom primair de technische en functionele eigenschappen van de export, niet de interne byteopbouw van het PDF-bestand.
Niet toegestaan als primaire regressietest:
- byte-voor-bytevergelijking van volledige PDF-bestanden;
- snapshots met productiegegevens;
- tests die slagen doordat actuele catalogusdata toevallig nog beschikbaar is;
- visuele golden masters voor ieder PDF-scenario;
- screenshots of mockup-HTML als bron voor PDF-layout.
Wel verplicht:
- deterministische testfixtures met vaste klok, vaste cultuurinstelling en vaste snapshots;
- expliciete controle op verplichte tekstinhoud, secties en totalen;
- controle op pagina-aantallen en herhaalde headers/footers voor meerpagina-exports;
- controle op module-specifieke vraag-, antwoord- en fallbackrepresentaties;
- controle dat persoonsgegevens, antwoordpayloads en technische details niet in logs of tijdelijke paden lekken.
16.24.2 Testlagen
| Testtype | Doel | Verplichting |
|---|---|---|
| Unit tests | Bestandsnaamnormalisatie, layoutbeslissingen, fallbacklogica, formattering en exportmodelmapping. | Verplicht in pull-requestchecks. |
| Contracttests | Practice-exportreader en module-PDF-rendercontracten. | Verplicht per modulecontractwijziging. |
| Integratietests | Export van afgeronde run met module-rendering, autorisatie en tijdelijke output. | Verplicht voor kernscenario's. |
| Structurele regressietests | Genormaliseerde PDF-inhoud, metadata, pagina-aantallen, headers, footers en sectievolgorde. | Verplicht voor stabiele exports. |
| Beperkte visuele snapshots | Gerenderde pagina-afbeelding of SVG voor enkele stabiele kernlayouts. | Alleen voor geselecteerde scenario's; standaard niet voor iedere modulevariant. |
| Securitytests | Geen export zonder toegang, geen payloads in logs, geen publieke tijdelijke bestanden. | Verplicht. |
| Cleanup tests | Idempotente verwijdering van verlopen tijdelijke exports. | Verplicht zodra tijdelijke bestanden worden gebruikt. |
| Performance tests | Exporttijd en geheugengebruik bij grotere resultaatsets. | Nightly of releasecheck zodra grenswaarden zijn vastgesteld. |
16.24.3 Toolingbaseline
Voor PDF-regressietests geldt de volgende toolingbaseline:
| Onderdeel | Keuze | Toelichting |
|---|---|---|
| PDF-generatie | QuestPDF | Productie- en testpad gebruiken dezelfde documentopbouw. |
| Snapshot/assertion tooling | Verify | Voor genormaliseerde snapshotasserties van tekst, metadata, objectmodellen en eventueel image-output. |
| PDF-tekstextractie | PdfPig of Verify.PdfPig | Voor inhoudscontrole op gegenereerde PDF's zonder bytevergelijking. |
| Testadapter | Volgt het gekozen .NET-testframework | Bijvoorbeeld Verify.Xunit wanneer xUnit de testbaseline wordt. |
| Visuele output | QuestPDF image- of SVG-output waar stabiel toepasbaar | Alleen voor geselecteerde kernlayouts en bij voorkeur in een vaste runtime/container. |
Packageversies worden tijdens implementatie centraal vastgepind. De toolkeuze zelf is onderdeel van de technische baseline.
16.24.4 Snapshot- en baselinebeheer
PDF-snapshots zijn reviewartefacten en geen informele bijlage.
Regels:
- snapshots staan in het testproject, niet in productiemappen;
- snapshots bevatten uitsluitend fictieve of geanonimiseerde testdata;
- gegenereerde timestamps, correlation-id's, tijdelijke paden en niet-deterministische metadata worden genormaliseerd;
- wijziging van een verified snapshot vereist code review met duidelijke reden;
- snapshotupdates verwijzen waar relevant naar een Software Requirements Specification-, acceptatiecriterium-, module- of Technisch Ontwerp-wijziging;
- ontvangen outputbestanden uit mislukte tests worden niet automatisch gecommit;
- visuele snapshots worden beperkt tot stabiele kernlayouts om fragiele diffs te voorkomen.
16.24.5 Verplichte scenario's
De minimale PDF-regressieset bevat:
| Scenario | Controle |
|---|---|
| Leerling exporteert eigen afgeronde run | Inhoud, totalen, bestandsnaam, footer en autorisatie. |
| Docent exporteert leerlingresultaat binnen autorisatiegrens | Contextlabel, leerling-snapshot, niveau/categorie en autorisatiehercontrole. |
| Ouder/voogd exporteert resultaat van gekoppeld kind | Kind-snapshot, privacygrens en historische context. |
| Lange resultaatset over meerdere pagina's | Pagina-aantal, tabelheaders, rijgedrag en footerpagina's. |
| Module met rijke representatie | Vraag, gegeven antwoord en juiste antwoord via modulecontract. |
| Modulefallback | Veilige fallbacktekst zonder technische details of payloadlek. |
| Ontbrekende of gewijzigde actuele catalogusdata | Historische snapshots blijven leidend. |
| Onbevoegde exportpoging | Geen PDF-output, veilige foutafhandeling en beperkte logging. |
| Tijdelijke output en cleanup | Bestand buiten webroot, retentie en idempotente verwijdering. |
PDF-binaire vergelijking blijft uitgesloten als primaire regressiemethode. Wanneer een visuele regressietest faalt, bepaalt review of de layoutwijziging bedoeld, acceptabel of regressie is.
16.25 Implementatiechecklist
Bij implementatie van PDF-export moet minimaal worden gecontroleerd:
OefenHub.Reportinggebruikt geen module-interne DbContexts of entities.- Exportbrondata komt via publieke
Practicecontracts. - Autorisatie wordt server-side per export herhaald.
- Module-specifieke vraag-/antwoordweergave loopt via
ExerciseModuleHost. - PDF gebruikt historische snapshots wanneer nodig.
- Resultaattabel ondersteunt lange vragen en antwoorden.
- Headers worden op vervolgpagina’s herhaald.
- Footer bevat OefenHub, pagina X van Y en exportdatum.
- Bestandsnaam wordt gesanitized en bevat geen persoonsgegevens.
- Tijdelijke opslag staat buiten webroot.
- Cleanup is idempotent en via Scheduling/TickerQ ingericht.
- Logging gebruikt correlation-id en bevat geen antwoordpayloads.
- Foutafhandeling lekt geen technische details.
- Tests dekken autorisatie, fallback, rendering, cleanup, bestandsnaamlogica, structurele PDF-regressie en geselecteerde visuele kernlayoutscenario's.
16.26 Implementatieverificaties
De volgende punten moeten bij implementatie concreet worden vastgesteld of gevalideerd:
| Punt | Te bepalen |
|---|---|
| QuestPDF-licentie/configuratie | Exacte licentie-instelling en projectconfiguratie. |
| Fontkeuze | Beschikbare fontfamilie zonder ongeoorloofde fontdistributie. |
| Tijdelijke opslaglocatie | Pad, rechten en hostingvolume controleren tegen OefenHub:Reporting:TempExportPath; standaardretentie 24 uur en cleanupachterstand vanaf 48 uur. |
| Direct streamen of tijdelijk bestand | Definitieve keuze per exportgrootte. |
| Browsercacheheaders | Exacte downloadheaders voor privacygevoelige exports. |
| Module-rendercontract | Definitieve DTO-vorm voor PDF-veilige module-output. |
| Fallbackbeleid | Exact gedrag wanneer module of payload niet meer renderbaar is. |
| Performancegrenzen | Maximale exportgrootte, timeout en throttling. |
| TickerQ-cleanupjob | Planning minimaal iedere 6 uur, retrybeleid en dashboardzichtbaarheid inrichten. |