Skip to content

Commit 126eda6

Browse files
authored
test(gates): real contract tests close gate-25 (41 -> 0); gate-26 3 -> 1 (#480)
* test(gates): real contract tests for all 41 gate-25 endpoints; gate-26 3 -> 1 gate-25 (contract-coverage) 41 -> 0 and gate-26 (visual-coverage) 3 -> 1, measured with the gate helpers at ConductionNL/.github@b8c7ead — the SHA the shared quality workflow floats on, which is what this repo's CI actually runs (this repo sets no `hydra-gates-ref`, so it defaults to @main). Nine new PHPUnit contract-test classes, 134 tests, 459 assertions. Every one calls the controller method under test and asserts its wire contract; none of them is an annotation over untested code, and no `@contract exclude` was added. What the tests actually pin, beyond "a 200 comes back": * deny-before-grant on every registered public endpoint — the backing service is asserted NEVER invoked when the caller is anonymous, so an implementation that queried first and filtered afterwards fails. * `GET /api/gebruik` (@publicpage): a `gebruik-beheerder` is narrowed to their own organisation BEFORE the `_rbac:false` bypass query is issued, and asking for another organisation's `afnemer` is denied outright rather than silently widened (vendor-visibility-rbac REQ-001/REQ-003). * `/api/aangeboden-gebruik/ambtenaar{,/{id}}` (@publicpage + RBAC bypass): the admin/ambtenaar group check is the only thing between an anonymous caller and every organisation's records — both the empty envelope AND the un-issued query are asserted. * `GET /api/email/config`: the non-admin 403. This endpoint once returned the SMTP password and provider API keys to any authenticated user; a test asserting only "200 for a logged-in user" would have passed on the broken version. * `/api/progress/{id}` and its SSE twin: another user's operation reads as 404 with no `progress` key — the ownership guard, on both variants. * `/api/archimate/download/{fileName}`: five traversal shapes refused 400 with the DI container asserted never consulted, so the guard is proven to run before any filesystem resolution. * `/api/contactpersonen/change-password`: the full ladder — non-admin on another account 403, self-service without the current password 400, wrong current password 403, <10 chars 400, policy-rejected `setPassword()` false surfaced as a failure rather than swallowed. * `/api/preferences/{key}`: the key that reaches IConfig is proven sanitised and `pref_`-namespaced, so `../apps/Password` cannot read another app's user values. Proven in both directions: removing the anonymous guard from `ViewController::getView()` turns the corresponding test red (500 != 401); restored, green. Skip count is unchanged at 25 — the suite goes 528 -> 662 tests with no test passing by being skipped. gate-26: `LifecycleRoadmapView.vue` now has behavioural e2e that asserts the component's OWN surface (root class, h2, intro, refresh control, org selector, and that `.rm-groups` is ABSENT before an organisation is picked). The previous assertion was an OR over two strings that a breadcrumb or the nav entry alone satisfies — it could pass on a page that is not this component. `src/views/organisaties/OrganisatieIndex.vue` is deleted as dead code, not waived: the manifest's `Organisaties` page is now `type: index` with `config.cardComponent: OrganisatieCard` (Phase 8), the file's own docblock names the CnIndexPage `cardComponent` gap as its reason to exist, and that gap is closed. Nothing in src/ imports it and no router or manifest entry names it. Remaining, deliberately NOT waived: `src/views/gemmaviews/GemmaViewIndex.vue` is likewise unreachable, but this repo's own openspec/changes/beta-surface-alignment/proposal.md defers its disposition to a maintainer ("may be dead code or a future menu item"). Writing a `@visual exclude` whose reason is "nothing routes to it" would be a claim about the state of the world that rots the moment someone wires it up, so gate-26 stays at 1 pending that decision rather than being closed with a waiver. * fix(e2e): query the roadmap refresh control by its accessible name CI run 31475813082 failed on this assertion (75 passed, 1 failed) and it was my bug, not the product's. The NcButton carries aria-label="Refresh data"; an aria-label overrides text content when computing the accessible name, so `getByRole('button', { name: 'Refresh', exact: true })` could never match the visible label "Refresh". Querying by the accessible name is also the better assertion — it is what a screen-reader user actually hears, so a future change that drops the aria-label now fails here.
1 parent f699b5a commit 126eda6

11 files changed

Lines changed: 3655 additions & 255 deletions

src/views/organisaties/OrganisatieIndex.vue

Lines changed: 0 additions & 252 deletions
This file was deleted.

0 commit comments

Comments
 (0)