{/* GENERATED by `npm run gen:behaviour-spec` (scripts/gen-behaviour-spec.mjs) — do not edit. Every bullet below is a real `it()` title or backend test method name, extracted verbatim from the suite. The team rejected Cucumber/Gherkin for BDD scenarios (runtime string matching undoes the compile-time guarantees WP-70 bought, and needs two frameworks for .NET+TS) — this page is the replacement: business-readable documentation generated FROM test names, so it can never drift from what the suite actually asserts. A test name changing (or a test being added/removed) is the only way this page changes; hand-editing it is pointless, the next `npm run gen:behaviour-spec` overwrites it. */} import { Meta } from '@storybook/addon-docs/blocks'; # Behaviour spec _Generated by `npm run gen:behaviour-spec` — do not hand-edit; the next generation overwrites this page. See [BDD](?path=/docs/foundations-bdd--docs) for how these names are written, and [Testing strategy](?path=/docs/foundations-testing-strategy--docs) for what gets tested where._ Every bullet below is a real test name from the suite — an `it()` title (frontend) or a test method name (backend), read as a sentence. Nothing here is hand-written prose: this page **is** the suite, reshaped for a business reader. 451 frontend behaviours across 9 contexts; 231 backend behaviours across 39 test classes. ## Frontend (by context) ### auth #### isAuthenticated - narrows a present principal to Principal - reports no principal as not authenticated - narrows a present principal to Principal - reports no principal as not authenticated #### parseRollen - parses a single recognized rol - is case-insensitive and trims whitespace - drops unrecognized tokens (the deny-path toggle, e.g. ?rollen=geen) - returns an empty list for an empty string #### parseStoredPrincipal - returns null when nothing is stored - returns null for a non-JSON string - returns null when the stored shape is wrong (no naam) - returns null when kind is not medewerker - returns null when rollen holds an unrecognized token - restores a well-shaped stored principal as-is (no BSN to strip) - returns null when nothing is stored - returns null for a non-JSON string - returns null when the stored shape is wrong (no naam) - G1: a stored bsn is never restored, even if present in the raw value ### behandeling #### besluit reduce - SetField updates the draft while editing - Submit with no besluit chosen stays Editing and reports a field error - Submit Afwijzen without a toelichting stays Editing and reports a field error - Submit Goedkeuren with no toelichting moves to Submitting (optional there) - Submit Afwijzen with a toelichting moves to Submitting with the trimmed value - SubmitConfirmed maps Submitting to Submitted - SubmitFailed maps Submitting to Failed with the error - Retry re-submits a failure - Reset returns to the initial editing state #### detailRows - lists soort/status/referentie/eigenaar/ingediend - a reden row is present only for Afgewezen and MeerInfoGevraagd #### parseBeoordelingStatus - parses each tag with its required fields - rejects a malformed status #### parseBeoordelingView - maps a valid DTO to domain - rejects a malformed view - defaults an absent documenten list to empty #### parseWerkvoorraad - parses a list and fails fast on a bad element #### parseWerkvoorraadItem - parses Ingediend and InBehandeling - rejects a case whose status is not an open queue tag - rejects a missing owner, bad type, and non-objects #### statusLabel - labels every tag distinctly #### werkvoorraadRow - heading is the type, subtitle carries the owner BSN - status line carries the status label, reference and submit date - manual review is called out distinctly from an automatic InBehandeling - a missing submit date leaves no dangling separator ### beheer #### StamdataStore undo/redo (WP-32) - records a cell edit, undoes and redoes it - records addRow and undoes it - clears history when switching table #### activeOn (valid-time, half-open [van, tot)) - includes a row whose window covers the date - excludes a row before its geldigVan - excludes on the geldigTot boundary (half-open) #### changeCounts (diff against the loaded snapshot, by key) - counts an added key - counts a removed key - counts an edited cell #### parseStamdataTable - maps schema + rows and turns a null cell into empty text - falls back to text for an unknown column type - rejects a response with no columns #### rowErrors / isValid (format only) - flags a blank key - flags a missing geldigVan on a temporal table - flags geldigTot on or before geldigVan - passes a well-formed row #### stamdata-editor reduce - Loaded snapshots original independently of rows - RowAdded appends an empty row shaped by the schema - RowRemoved drops the row at the index - edit messages are ignored unless loaded - LoadFailed and Loading transition regardless of prior state #### toJson (draft → file shape) - reconstructs an open-ended geldigTot as null and pretty-prints - coerces a number column ### brief #### BriefStore action state (Idle | Busy | Failed) - is Busy synchronously once a transition starts - settles to Idle on a successful transition - goes Busy then Failed on a failing transition, surfacing the error - a subsequent successful transition clears a prior Failed state #### BriefStore rejection diff - captures the rejected letter and diffs a subsequent edit against it #### BriefStore undo/redo history - starts with nothing to undo - records an edit and makes it undoable - undo reverts the edit and enables redo - redo reapplies the undone edit - a no-op edit does not clear the redo future - a new edit clears the redo future - caps history at 50 snapshots #### BriefStore.flushPending (CanDeactivate guard / beforeunload) - flushes a pending debounced edit immediately and clears the pending flag - is a no-op when no edit is pending #### BriefStore.previewLetter - opens the composed letter in a new tab on success - surfaces the error without opening a tab on failure #### BriefStore.revealBigNummer (PRD-0002 §5c) - swaps the masked value for the revealed one on success - keeps the value masked and surfaces the error on failure #### LetterPreviewAdapter.preview (BIO-012) - sends no X-Role/X-Subject headers outside isDevMode() - sends X-Role (and X-Subject when known) under isDevMode() #### RevealBigNummerAdapter.reveal (BIO-006a + BIO-012) - sends X-Step-Up only when the caller passes stepUp: true - sends X-Role only under isDevMode() #### besluitGuidance - positief: counts inserted passages, no reden needed (positief has no redenen) - negatief without a reden: flags that a reden must be chosen - negatief with a reden: no longer flags, and the reason passage is counted #### brief selectors - unresolvedPlaceholders returns deduped manual keys only (auto excluded) - allDiagnostics flattens across sections and blocks - canSubmit is false when a required section is empty, true otherwise #### brief.adapter parse boundary - parses a well-formed view into the domain unions - rejects a view whose case context is missing or malformed - parses the org template and drops a null logoDocumentId - rejects a view whose org template is missing or malformed - rejects a view whose decisions are missing or malformed - narrows node variants and rejects unknown ones - rejects a status DTO missing its required fields - reads section.locked (default false) and paragraph.list - rejects a library passage with an unknown scope - rejects a passage block missing provenance #### brief.machine reduce - BriefLoaded moves loading to loaded - BriefLoadFailed moves loading to failed with the reason - Seed sets the state directly - BesluitSelected composes the kern: the besluit passages, in reading order, as frozen local blocks - BesluitSelected swaps the passages when the selection changes, keeping free text - BesluitSelected deep-copies content — later library mutation does not leak in - FreeTextBlockAdded appends an empty free-text block - BlockContentEdited replaces content and marks a passage block edited - BlockMovedWithinSection reorders blocks within a section - BlockRemoved drops a block from a section - edits to a locked section are no-ops (besluit, free-text, content, remove, move) - edits are no-ops once submitted (status invariant) - editing a rejected letter reopens it to draft - Submitted fires only from draft and only when required sections are filled - approve fires only from submitted - reject fires from submitted, carrying comments - send fires only from approved - a status transition replaces decisions with the fresh server value #### diffBlocks - marks added, removed, changed and unchanged by blockId - changedBlocks drops unchanged and keeps added/removed/changed #### errorMessage (TE-002 trust boundary) - surfaces the ProblemDetails detail when present - falls back to PREVIEW_FAILED when the body has no detail - falls back to PREVIEW_FAILED when the body is not JSON #### inferSelection - round-trips a positief selection - round-trips a negatief selection with redenen (in order) - an empty kern (nothing chosen) infers no besluit - ignores free-text blocks and unknown passage ids #### lintPlaceholders - clean content (auto-resolvable, fillable, current key) yields no diagnostics - flags an unknown key as an error - flags a not-fillable key as an error - flags a deprecated key as a warning - flags a manual placeholder as unresolved-at-send (warning) - flags raw braces in text as malformed (paste safety net) - returns diagnostics in document order across paragraphs/nodes - severityOf maps each code to its policy #### org-template.machine - DraftLoaded moves to loaded with the draft, clean - LoadFailed carries the reason - FieldEdited edits the draft and marks dirty - MarginEdited edits one edge and marks dirty - DraftSaved clears dirty when the saved draft is the current one - DraftSaved keeps dirty when an edit landed during the save round-trip - edits are no-ops in non-loaded states - a completed logo upload sets logoDocumentId + dirty - removing the logo clears logoDocumentId + dirty - DraftLoaded (sub-org switch) keeps the loaded logo category, drops uploads #### parseOrgTemplateAdminView - parses a well-formed admin view - rejects a missing draft - rejects a missing count field - rejects a malformed history entry #### parseRevealed (TE-002 trust boundary) - accepts a well-formed body - rejects a bigNummer sent as a number - rejects a missing bigNummer field - rejects null and non-object bodies #### passagesForBesluit - positief = shared intro + the positief passage, no negatief/reason passages - negatief without redenen = intro + negatief base, but no reason-specific passages - negatief with a reden ticked includes that reason-specific passage only - preserves library order (= reading order) - never offers non-kern passages #### proefbriefErrorMessage (TE-002 trust boundary) - surfaces the ProblemDetails detail when present - falls back to PROEFBRIEF_FAILED when the body has no detail - falls back to PROEFBRIEF_FAILED when the body is not JSON #### redenenFor - derives reason checkboxes (code + label) from the negatief reason passages - positief has no reason-specific redenen ### herregistratie #### IntakeWizardComponent - renders each field group as its own grey <fieldset> #### STEPS (fixed) and inline questions - always has the same three steps - reveals the buitenland detail questions inline only when worked abroad - reveals the scholing question only when NL-hours are below the threshold - uses the (server-owned) threshold passed in, not a hardcoded constant #### herregistratie hasProgress - is false for a fresh form - is true once a field is filled or the user advances #### intake acceptance journeys - high uren, no buitenland werk: no scholing question, straight through to Submitted - low uren requires the scholing question, and punten only once scholing is followed - buitenland gewerkt requires land and hours abroad before advancing - gaNaarStap corrects an earlier answer without losing later ones - raising uren above the threshold after answering scholing drops both fields (WP-69 §6) - SetPolicy (server-owned threshold) can turn an already-answered uren into one that now requires scholing #### intake hasProgress - is false for a fresh questionnaire - is true once an answer is given or the user advances #### intake-policy.adapter parse boundary - parses a well-formed policy - rejects a missing or non-numeric threshold #### navigation - Next is a no-op (sets an error) when the current step is invalid - Next advances once the step is valid - editing an answer leaves the cursor fixed (steps never collapse) - Back never goes below the first step - gaNaarStap jumps back to an earlier step, clearing errors - gaNaarStap ignores a same/forward jump and jumps outside Answering #### reduce (message-driven happy path) - drives abroad branch end to end #### reduce (message-driven) - drives the full happy path via messages - blocks submit until required documents are satisfied - SubmitFailed then Retry returns to Submitting with the same data - Seed mounts an arbitrary state #### submit - reaches Submitting ONLY with valid answers - punten is required only when aanvullende scholing was gevolgd - low hours requires the scholing answer before submit - does not require punten for a hidden question (WP-69 §6) - drops punten when raising uren hides the question (WP-69 §6) - resolve maps Submitting to Submitted on a successful submit - resolve maps Submitting to Failed on a failed submit #### wizard.machine - next advances only when step 1 parses - next advances step 2 → 3 only when punten parses - submit reaches Submitting ONLY from step 3 with fully valid data - next requires BOTH step-1 fields (uren and jaren) - back steps down one (3 → 2 → 1) and is a no-op from step 1 - resolve maps a successful Submitting to Submitted - resolve maps a failing Submitting to Failed - gaNaarStap jumps back to an earlier step, clearing errors - gaNaarStap ignores a same/forward jump and jumps outside Editing ### registratie #### AdminCasesStore - loads and parses the cross-owner list - deletes optimistically and confirms via the admin endpoint - rolls back the removal when the delete fails #### STEPS (fixed) - always has the same three steps #### adres origin (BRP vs handmatig) - prefillAdres flags origin brp - editing a prefilled address field flips origin to handmatig - typing an address with no BRP prefill yields handmatig - editing the e-mail field does not change the address origin - a manually entered address still submits (only manual diploma is gated) #### big-register.adapter parse boundary - parses known aantekening types - rejects an unknown type #### blockActions - a Concept can be resumed or cancelled - an in-behandeling aanvraag only exposes its documents - ingediend and meer-info-gevraagd behave like in-behandeling - resolved aanvragen have no actions #### change-request reduce - SetField updates the draft while editing - Submit with an invalid draft stays Editing and reports field errors - Submit with a valid draft moves to Submitting with parsed (normalised) data - SubmitConfirmed maps Submitting to Submitted with the referentie - SubmitFailed maps Submitting to Failed with the error - Retry re-submits a failure - Reset returns to the initial editing state #### createDraftSync - coalesces rapid snapshot changes into ONE debounced sync of the latest value - a trailing change after the debounce fires schedules its own sync #### createDraftSync › flushPending (CanDeactivate guard / beforeunload) - hasPendingSave reflects an armed debounce timer - flushPending writes the pending draft immediately, before the debounce fires - flushPending is a no-op when nothing is pending #### createDraftSync › submit - resolves ok with the server response on success - folds a rejected submit into a Result error, never throwing - recovers from a create conflict by adopting the existing Concept (WP-35) #### createSubmitChangeRequest - resolves ok with the referentie on success - folds a rejected call into a Result error, never throwing - surfaces a ProblemDetails detail message when the server rejects with one #### detailRows - lists soort/waarvoor/status/referentie/ingediend, plus reason when rejected - reference falls back to em dash for a Concept #### hasProgress - is false for a fresh wizard - ignores an auto-prefilled BRP address at step 0 - is true once the user advances, picks correspondence/diploma, or is past step 0 #### inline document upload (beroep step) - routes Upload messages through the upload reducer - blocks the beroep step until a required category is satisfied - includes delivery refs in the submitted data #### kiesDiploma - derives the beroep from the chosen diploma and flags origin duo #### manual diploma fallback - KiesHandmatig flags handmatig with the maximal question set and no beroep yet - requires a declared beroep + all maximal questions before submit #### navigation - Next is a no-op (sets errors) when the adres step is invalid - Next advances once the adres step is valid - requires a valid e-mail only when the channel is email - beroep step requires a chosen diploma - Back never goes below the first step and preserves the draft - GaNaarStap only jumps backwards #### parseAanvraagStatus - parses each tag with its required fields - rejects a missing status, unknown tag, and wrong-typed fields #### parseApplicationSummary - maps a valid DTO to domain - rejects a bad type and non-objects #### parseApplications / parseApplicationDetail - parses a list and fails fast on a bad element - carries the opaque draft through detail #### parseBigNummer - accepts exactly 11 digits, trimming whitespace - rejects wrong length or non-digits #### parseBrpAddress (trust boundary) - accepts a found address - accepts "geen adres" (gevonden: false) as a valid outcome - rejects malformed responses #### parseDashboardView (trust boundary) - maps a valid response into a DashboardView - rejects malformed responses instead of trusting them #### parseDuoLookup (trust boundary) - maps a valid lookup (diplomas + manual fallback) - accepts an empty diploma list (forces manual entry) - rejects malformed responses #### parseEmail - accepts a well-formed address and trims it - rejects malformed addresses #### parsePostcode - normalises to "1234 AB" (uppercase, single space, trimmed) - rejects malformed postcodes #### parseTelefoonnummer - accepts a 10-digit number starting 0 and strips formatting - normalises a +31 prefix to a leading 0 - rejects a too-short number, a non-0 start, and junk #### parseUren - accepts non-negative whole numbers, including 0 - rejects empty, negative, and non-integer input #### policy questions (geldigheidsvragen) - a diploma with questions blocks Next until they are answered - validateAll keeps only the answers to the questions that applied #### reduce (message-driven happy path) - adres and correspondentie set, Next advances from adres to beroep - diploma chosen, Next advances from beroep to controle - Submit moves a complete Invullen draft to Indienen - SubmitConfirmed moves Indienen to Ingediend - SubmitFailed moves Indienen to Mislukt - Retry returns Mislukt to Indienen with the same data #### registration.policy - statusLabel echoes the tag - statusColor is total over the union - herregistratieDeadline is only set for an active registration #### submit - stays in Invullen when the draft is incomplete (no diploma) - reaches Indienen with a complete, valid draft, carrying its data - resolve maps Indienen to Ingediend with the referentie - resolve maps Indienen to Mislukt on a failed submit #### submittedRow - heading is the type, subtitle is the purpose - status line carries the status label, reference and submit date - manual review adds a note - rejection adds its reason - meer-info-gevraagd adds its reason, like a rejection #### tasksFromProfile - offers herregistratie when the server says eligible, with the formatted deadline - offers nothing when the server says not eligible - surfaces a notice for a suspended registration (independent of eligibility) - surfaces a notice for a struck-off registration ### shared #### BackgroundUploadsReturned - resolves each in-flight upload to complete or failed #### CategoriesLoadFailed / BackgroundSyncAvailability - records the error - flips background sync availability #### CategoriesLoaded - defaults every category to digital without clobbering existing choices - clears a prior categoriesError - drops uploads + channel choices for categories that disappear #### DeliveryChannelChanged - switching to post removes that category uploads - rejects post for a category that does not allow it - switching back to digital starts clean #### FileRejected - stores a per-category message #### FileSelected - queues a new upload and clears any rejection for that category - ignores selection for an unknown category - ignores selection for a category set to post-delivery - single-file category: a new selection replaces the existing upload - multiple category: selections accumulate #### PendingSaves registry - hasPending is true only while some registered owner has a pending write - unregister removes an owner so it no longer counts - flushAll flushes only the pending owners - flushAll awaits every owner and swallows a rejected flush #### RemoteData combinators - map only touches Success - map2 precedence: Failure > Loading > Success #### authGuard - allows an authenticated user - redirects an anonymous user to /login #### capabilityGuard - waits for /me, then allows an entitled admin - sends an authenticated-but-unentitled user to /dashboard (not a login loop) - redirects an anonymous user to /login without waiting for caps - reads authentication through the port, not an app-local store #### createDebouncedSave - flushes after the delay when canSave is true - does not schedule when canSave is false - coalesces rapid schedules into a single flush - flushPending runs the save immediately and clears; no-op when idle - cancel drops a scheduled save without running it #### createHistory - starts empty; undo/redo are no-ops - records pre-edit snapshots, then undoes and redoes through them - record() clears the redo stack (no dead redo after a fresh edit) - caps the stack depth - clear() empties both stacks #### createStore - applies the pure update on dispatch - dispatch from inside an effect does not self-loop #### currentRole (dev mechanism) - lists the three roles - reads a valid ?role= from the URL and persists it for the tab - falls back to drafter when nothing is set or the value is invalid #### currentRole (dev mechanism) › outside isDevMode() (production build) - ignores a ?role= in the URL and returns the default - never touches sessionStorage - ignores a role already sitting in sessionStorage from a prior dev session #### currentSubject (dev mechanism) - reads a ?subject= from the URL and persists it for the tab - returns undefined when nothing has ever been set #### currentSubject (dev mechanism) › outside isDevMode() (production build) - ignores a ?subject= (a BSN) in the URL and returns undefined - never writes the BSN into sessionStorage - ignores a subject already sitting in sessionStorage from a prior dev session #### delete flow (optimistic, revertible) - UploadDeleteRequested keeps the documentId for revert - UploadDeleteComplete removes the upload - UploadDeleteFailed reverts to complete with the original documentId #### deliveryRefs - emits documentId for completed digital uploads and channel for post - omits digital categories with no completed upload #### flushPendingGuard - flushes then allows navigation when a write is pending - allows navigation immediately when nothing is pending #### formatDatumNl - formats a Date in long Dutch form - formats an ISO string the same way - is empty-safe: undefined, null, and empty string all yield the empty string - returns empty for an unparseable string rather than "Invalid Date" #### httpClientFetch - sends the pending idempotency key as a header for a write, not a fresh one per attempt - retries a failing GET twice before giving up - never retries a failing write #### inFlight - returns only queued/uploading uploads #### localeLinks (nl at root, en under /en/) - an nl route (no prefix) links nl to the bare path, en under /en, marks active - an en route strips the /en prefix for the nl target (deep path, en active) - keeps query + hash on both targets - the root maps nl → / and en → /en/ #### machineRemoteData - maps loading → Loading - maps failed → Failure carrying an Error with the reason - maps loaded → Success carrying the whole loaded state #### parseBsn (elfproef) - accepts a valid BSN (passes the elfproef) - rejects a 9-digit number that fails the elfproef - rejects wrong length / non-digits / all zeros #### parseMe (trust boundary) - parses a known capability list - parses an empty list (drafter — no capabilities) - recognizes the admin org-template capability (WP-23) - recognizes the behandelportal besluit capability (WP-66) - drops unrecognized capability strings instead of rejecting the response - rejects malformed responses instead of trusting them #### pii maskers - maskBsn keeps the last 3 digits - maskTail keeps the requested tail length - masks the whole value when it is not longer than the kept tail #### problemDetail - extracts the detail from an RFC-7807 ProblemDetails - falls back when there is no detail #### problemFieldErrors (G4 seam) - maps a ValidationProblemDetails errors dict to first-message-per-field - returns {} when there is no errors envelope (the current backend shape) #### rejectReason - rejects a disallowed type - rejects an oversized file - accepts a valid file - allows any type when the category lists none #### rich-text - emptyBlock is one empty paragraph and reads as empty - isBlockEmpty is false when any placeholder or non-blank text exists - placeholderKeysIn walks in document order, keeping duplicates - deepCopyBlock is an independent value copy (frozen snapshot) #### rich-text DOM boundary - round-trips text, marks, placeholders, line breaks and multiple paragraphs - round-trips an empty paragraph (filler <br> is not a line break) - renders a placeholder as a non-editable chip carrying its key and label - reads combined marks in canonical order regardless of nesting - round-trips bullet and numbered lists mixed with paragraphs - groups consecutive same-kind list lines into one <ul>/<ol> - adjacentChip finds a chip next to a collapsed caret so Backspace/Delete can remove it - marks auto-resolvable vs manual chips with data-auto for styling #### roleInterceptor - leaves an unrelated endpoint untouched #### runResult - folds a resolved call into ok(value) - maps a ProblemDetails rejection to err(detail) - falls back when the rejection has no detail - mints no Idempotency-Key — the read fold #### runSubmit - folds a resolved call into ok(value) - maps a ProblemDetails rejection to err(detail) - falls back when the rejection has no detail - mints exactly one Idempotency-Key for the whole call — the write fold #### satisfaction helpers - a post-delivery choice satisfies a required category - an active upload satisfies a required category - a failed upload does not satisfy a required category - requiredCategoriesSatisfied ignores optional categories #### scenario (dev mechanism) - reads a valid ?scenario= from the URL and persists it for the tab - falls back to default when nothing is set or the value is invalid - setScenario persists the chosen scenario #### stripDevParams (WP-37) - removes ?scenario and ?role so the stored dev value wins on reload - keeps unrelated query params and the path/hash - is a no-op when neither param is present #### subjectInterceptor - stamps X-Subject on an /api/v1/ request once ?subject= has been seen - keeps stamping later requests on the same tab after the query param is gone (WP-33-style stickiness) - leaves a non-API request untouched even when a subject is known - sends no header at all when no subject has ever been seen #### upload lifecycle messages - queued → progress → complete - failed then retried returns to queued - UploadRemoved drops the upload #### withIdempotencyKey / currentIdempotencyKey - threads the key to every read made inside the wrapped fn - clears the key once the wrapped fn settles - falls back to a generated uuid-shaped key when none is pending ### shell #### redactProfile - masks the BIG-nummer to its last 3 digits - redacts the name on both the registration and the person - redacts every date of birth - redacts the address - keeps structural/decision-relevant fields untouched ### showcase #### highlightTs - wraps keywords, strings and comments in the styling spans - escapes HTML metacharacters so the [innerHTML] sink is safe - treats the whole // tail as one comment (keywords after // are not re-highlighted) ## Backend (by test class) ### AdminCasesTests - Admin lists every case with its owner - Non admin is forbidden - Admin can delete a submitted case - Deleting a missing case is not found ### ApplicationRuleTests - AanvraagStatusTag covers the published lifecycle ### ApplicationTests - Create then list shows a concept with step progress - Draft sync is readable back from detail - Submit duo registratie is in behandeling and auto - Submit handmatig registratie succeeds as manual case - Submit herregistratie with zero uren is afgewezen - Submitting twice conflicts - Creating a second concept of the same type conflicts - A concept of a different type is allowed - A new concept is allowed once the previous one is submitted - Cancel concept removes it - Cancel submitted aanvraag conflicts - Applications are scoped to the caller bsn - Submitting a foreign documentId is rejected and leaves it deletable by its owner - Draft sync with a foreign documentId is rejected - AutoApprovable flips to goedgekeurd after the window - Manual case never auto advances ### AuthzAuditTests - A denied admin action is recorded - A reveal attempt is recorded - An allowed admin action is recorded - An admin upload delete is recorded - A feature flag toggle records which flag changed - A refused brief transition is recorded - No audit row carries a subjects bsn - The audit schema carries no pii ### AuthzTests - Drafter may not approve or reject even when submitted - Approver may approve and reject a different drafters letter - Send is not role gated - Decisions CanEdit true for drafter in editable statuses - Decisions CanApprove requires submitted status and approver role - Decisions CanSend requires approved status only - RoleCapabilities are empty for drafter and the three brief capabilities for approver - CanRevealBigNummer only for the case drafter behandelaar - Decisions CanRevealBigNummer is status independent - CanBeoordelen true for a medewerker with the behandelaar rol - CanBeoordelen false for a medewerker without it - CanBeoordelen false for a zorgverlener regardless of role ### BeoordelingIdMismatchTests - Besluit resolves by referentie when the case id differs from the local aanvraag id ### BeoordelingRuleTests - Only open statuses are decidable - Only a non approval requires a toelichting - A terminal decision refuses any further besluit - MeerInfoOpvragen is not terminal a further besluit is still legal ### BeoordelingTests - Detail shows status documents and a masked owner - Concept and unknown id are not found - Zorgverlener is forbidden even with admin role - Medewerker without behandelaar rol is forbidden - Goedkeuren advances status to Goedgekeurd - Afwijzen requires a toelichting - MeerInfoOpvragen is still decidable afterwards - Already decided case rejects a further besluit - Concurrent besluiten on the same aanvraag yield exactly one success - Unknown id 404s - Zorgverlener is forbidden from deciding ### BesluitLifecycleTests - A terminal besluit is frozen - MeerInfoGevraagd can be decided again - A recorded decision wins over the auto approve computation - Afwijzen requires a toelichting ### BriefEndpointTests - Get creates a draft with expected sections locked and empty - Get offers only global and arts scoped besluit tagged passages - Get joins the case context with the BIG nummer masked - Reveal returns the unmasked BIG nummer for the drafter with step up - Reveal is forbidden without the step up - Reveal is forbidden for a role without the capability - Save is drafter only - Submit blocks on empty required section - Submit succeeds when required sections filled - Drafter cannot approve own letter but a different reviewer can - Reject returns comments - Editing a rejected letter reopens it to draft - Send only from approved - Decisions on the view mirror the acting principal and live status - Me returns no capabilities for drafter and the brief set for approver - Reset recreates a fresh draft with locked prefilled sections ### DiplomaRuleTests - Profession is derived from program - English diploma requires dutch proficiency - Dutch diploma has no policy questions - Manual diploma gets maximal set - Manual professions match known programs ### DocumentRuleTests - Rejects unknown category - Rejects disallowed type - Rejects oversized file - Accepts valid file - First load has no diploma upload - Manual diploma needs a diploma upload - Duo diploma skips diploma upload - Confirmed dutch proficiency requires taalvaardigheid proof - Unconfirmed proficiency requires no taalvaardigheid proof - Find resolves taalvaardigheid for upload validation ### EndpointTests - DashboardView computes eligibility decision - Notes returns seeded aantekeningen - Brp returns address - Duo lookup carries server decided questions and professions - IntakePolicy returns scholing threshold - Change request with valid phone succeeds - Change request with bad phone is rejected with problem details - Health endpoint is ok - Correlation id supplied by the caller is echoed back - Correlation id is generated when the caller omits it - Categories are server owned config - Upload then status reports complete for known localId - Upload content is served back with its type inline for pdf - Upload content 404 for unknown document - Upload rejects wrong type - User delete succeeds then 404 - User delete blocked with 409 once linked to submission - Admin delete requires admin role - Audit log records upload and delete metadata only ### FeatureFlagTests - The catalog has unique keys - Get flags returns the catalog - Toggling is admin only and an unknown key is 404 - Closing inschrijving blocks a registratie then reopening allows it ### HerregistratieRuleTests - Eligible within window - Not eligible before window - Eligible on window boundary - Suspended is not eligible ### IdempotencyTests - Replaying the same idempotency key returns the same reference not a new one - Different idempotency keys are independent submissions - A rejected submission replays the same rejection not a retry ### IntakeRuleTests - Below threshold with no answer is incomplete - At the threshold no answer is required - Niet gevolgd is a complete answer below threshold - Gevolgd without punten is incomplete - Gevolgd with zero punten is valid - Gevolgd with negative punten is refused ### IntakeSubmissionTests - Below threshold without an answer is rejected and stays a concept - Below threshold with an answer succeeds - Above threshold needs no answer - Punten without gevolgd is rejected - Herregistratie is unaffected by the intake only gate - Zero uren is still afgewezen not a 400 ### LetterHtmlTests - Render matches the golden file - Every letter prefixed class exists in letter css ### NotificatieTests - Correct shared secret is accepted and recorded - Wrong secret is rejected and recorded - Missing authorization header is rejected ### OpenZaakDocumentSourceTests - Upload registers an eio in drc and persists its url locally - Upload falls back to openbaar for a category absent from the confidentialiteit table - Upload keeps the local document when the category has no configured informatieobjecttype - Upload keeps the local document and does not throw when drc rejects it - LinkToZaak posts a zaakinformatieobject per document once a zaak exists - LinkToZaak makes no zgw call when the local source created no zaak ### OpenZaakIntegrationTests - Admin cases returns the seeded zaak mapped through real HTTP and JWT ### OpenZaakZaakSourceTests - Follows pagination maps the internal aanvraag type and sends bearer token - ListCases throws when a zaak zaaktype has no configured aanvraag type - ListMyCases filters by the callers bsn - CreateZaak posts zaak status and rol and maps the result back - CreateZaak throws when the aanvraag type has no configured zaaktype - RecordBesluit posts the last statustype with besluit and toelichting - RecordBesluit creates a resultaat before posting the eindstatus - RecordBesluit does nothing when the aanvraag has no zaak - RecordBesluit throws when the aanvraag type has no configured zaaktype - CreateZaak retries a transient failure and then succeeds - CreateZaak gives up after three attempts on a persistent transient failure - CreateZaak does not retry a permanent rejection ### OrgTemplateEndpointTests - Admin endpoints are admin only - Publish increments the version - Publish appends to the version history - Publish counts the unsent briefs it affects - Save draft validates margins - Save draft round trips the edited values - Rollback copies an old version into the draft without rewriting history - Sent brief keeps its pinned template after a republish - Unsent brief follows a republish - Me returns the orgtemplate capability for admin - Admin cannot slip into the brief review flow ### OrgTemplateRuleTests - Accepts a complete draft within the margin bounds - Rejects a missing organisation name - Rejects a missing signature name - Rejects a margin outside the allowed range - Accepts margins on the boundary ### PhoneFormatContractTests - A leading plus31 is accepted like the frontends normalised form - Parentheses around the area code are accepted like the frontend ### PreviewEndpointTests - Preview of an unsent brief renders live with a watermark - Preview of a sent brief serves the archive unchanged after a republish - Proefbrief is admin only - Proefbrief renders the draft template with a watermark ### ProductionIdentityProviderTests - Production environment with no real identity provider fails at startup ### ProfessionsTests - A mapping is absent before its geldigVan - A mapping is present on and after its geldigVan - A closed mapping is absent from its geldigTot onwards - ByProgram is evaluated per call not captured at type load ### StamdataEndpointTests - Stamdata reads are admin only - Table list exposes the reflected schema - Table returns all rows without a peildatum - Peildatum before the seed windows hides every row - Unknown table is 404 ### StamdataValidationTests - Every declared reference into stamdata resolves - Profession map has no blank programs or professions - Manual professions are non empty and distinct - Every policy question has an id and wording - Manual question set has distinct ids - Every catalog table is valid ### StubIdentityProviderTests - No headers resolves to the seeded citizen as a drafter - X role maps to the principal role - X subject overrides the default bsn - No headers resolves a zorgverlener kind - X medewerker resolves a medewerker with the default behandelaar rol - X medewerker takes precedence over x subject - Empty x medewerker falls through to the zorgverlener default - X rollen parses known tokens and drops unknown ones - X role still applies to a medewerker - Never returns null even with no headers at all ### SubmissionRuleTests - Zero hours is rejected - Worked hours are accepted - Phone change is validated ### UploadAccessTests - The owner can read the bytes - Another citizen gets 404 not 403 - A behandelaar can read a linked document - A medewerker without the behandelaar rol does not - The document audit trail records a masked actor - Status reports another citizens localId as unknown ### WerkvoorraadTests - Behandelaar sees submitted cases in the queue - Queue excludes concepts - Zorgverlener is forbidden even with admin role - Medewerker without behandelaar rol is forbidden - Me reports the capability only for a behandelaar ### ZgwDiagnosticHandlerTests - Passes request and response through unchanged ### ZgwDivergenceTests - Submit with a failing zgw flags the divergence instead of diverging silently - Submit with a healthy zgw leaves no divergence flag - A recorded divergence carries no response body and no query string ### ZgwTokenProviderTests - Mints a three part jwt with the required claims - Mint with a caller carries that citizen not the static config identity - Mint with a medewerker caller uses the medewerkerId as user id - Signature verifies with the shared secret ### ZgwZaakMapperTests - Maps url identity zaaktype and open status - Closed zaak maps to goedgekeurd - Uuid extracts trailing segment ignoring trailing slash