Commit 126eda6
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
File tree
- src/views/organisaties
- tests
- Unit/Controller
- e2e/spec-coverage
This file was deleted.
0 commit comments