test: pin the public review-aggregate wire contract and prove five page surfaces render - #470
Merged
Merged
Conversation
…ge surfaces render
gate-25 contract-coverage 1 -> PASS, gate-26 visual-coverage 6 -> PASS, measured
with hydra-gates 48c88ba against origin/beta — the scope a push to development
actually runs.
gate-25 — GET /api/reviews/aggregate had no automated proof
`review#aggregate` is `#[PublicPage]`: an anonymous visitor on a module or
dienst detail page reads it, so its response shape and status codes are part
of the app's public surface. ReviewControllerContractTest pins them:
- success is exactly `{average, count, items}` with HTTP 200;
- the service's internal `ok`/`reason` bookkeeping never reaches the wire;
- a subject with no approved reviews is a 200 with a null average, not a 404
— a consumer rendering a star widget has to tell "nothing approved yet"
from "bad request";
- a rejected request is a 400 carrying exactly `message`;
- subjectType/subjectId reach the service unaltered.
gate-26 — five page components with no browser-level proof
tests/e2e/spec-coverage/page-surfaces.spec.ts drives the REAL UI by clicking
the app's own navigation, then asserts the page's own content rendered and
that the app logged no console error and returned no 5xx:
FacetedCatalogIndexView (Applications and Services), SuitesIndexView,
PortfolioReport, and EolSyncSettings inside the admin settings shell.
Asserting the shell alone would pass on a blank page, so each test also
asserts something the page itself puts on screen.
KwetsbaarhedenView and LicensePostureView already had real behavioural specs
that the gate could not attribute to them; those two specs now name their
page component in the docblock. No new assertion was invented for them and
none was needed — the coverage already existed.
Can-fail proof: removing the new files and reverting the two docblocks puts
both counts back exactly — gate-25 1, gate-26 6. Mutating the controller to
leak `ok` onto the wire turns 2 of the 5 contract tests red.
Unit suite 524 tests green; phpcs lib/ unchanged at 0 errors / 87 warnings.
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-09 15:54 UTC
Download the full PDF report from the workflow artifacts.
…ge surfaces render
gate-25 contract-coverage 1 -> PASS, gate-26 visual-coverage 6 -> PASS, measured
with hydra-gates 48c88ba against origin/beta — the scope a push to development
actually runs.
gate-25 — GET /api/reviews/aggregate had no automated proof
`review#aggregate` is `#[PublicPage]`: an anonymous visitor on a module or
dienst detail page reads it, so its response shape and status codes are part
of the app's public surface. ReviewControllerContractTest pins them:
- success is exactly `{average, count, items}` with HTTP 200;
- the service's internal `ok`/`reason` bookkeeping never reaches the wire;
- a subject with no approved reviews is a 200 with a null average, not a 404
— a consumer rendering a star widget has to tell "nothing approved yet"
from "bad request";
- a rejected request is a 400 carrying exactly `message`;
- subjectType/subjectId reach the service unaltered.
gate-26 — five page components with no browser-level proof
tests/e2e/spec-coverage/page-surfaces.spec.ts drives the REAL UI by clicking
the app's own navigation, then asserts the page's own content rendered and
that the app logged no console error and returned no 5xx:
FacetedCatalogIndexView (Applications and Services), SuitesIndexView,
PortfolioReport, and EolSyncSettings inside the admin settings shell.
Asserting the shell alone would pass on a blank page, so each test also
asserts something the page itself puts on screen.
KwetsbaarhedenView and LicensePostureView already had real behavioural specs
that the gate could not attribute to them; those two specs now name their
page component in the docblock. No new assertion was invented for them and
none was needed — the coverage already existed.
Can-fail proof: removing the new files and reverting the two docblocks puts
both counts back exactly — gate-25 1, gate-26 6. Mutating the controller to
leak `ok` onto the wire turns 2 of the 5 contract tests red.
Unit suite 524 tests green; phpcs lib/ unchanged at 0 errors / 87 warnings.
… specs
The first draft asserted the page's title text inside <main>. That passes on a
blank page and on the WRONG page: the shell renders <main> for every route and
the nav echoes the same label, so the assertion could not distinguish 'the page
rendered' from 'something rendered'.
Each test now asserts on markup only the component under test declares:
- FacetedCatalogIndexView -> the CnFacetSidebar title plus all four GEMMA
dimensions it builds from DIMENSION_LABELS (Reference component, Standard,
Application service, Domain);
- SuitesIndexView -> the 'New suite' wizard trigger in its own action slot;
- PortfolioReport -> [data-testid=pr-summary] and its five declared column
headers, read as columnheader roles;
- EolSyncSettings -> the 'End-of-life feed sync' section name and its
'Sync now' control.
These anchors are declared by the component rather than derived from rows, so
an empty seed makes them ABSENT rather than merely empty — which is what makes
them a check that can fail.
…' into fix/contract-and-visual-coverage # Conflicts: # tests/e2e/spec-coverage/page-surfaces.spec.ts
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ❌ | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-08-09 20:10 UTC
Download the full PDF report from the workflow artifacts.
Found by strengthening the gate-26 e2e assertions from the container to the ITEM. The first draft asserted the page title inside <main>; it passed. Asserting the four GEMMA dimensions the page claims to render failed immediately. FacetedCatalogIndexView passed `:filters="facetDimensionFilters"` to CnFacetSidebar. CnFacetSidebar declares no `filters` prop — its props are `schema`, `facetData`, `activeFilters`, `loading`, `title`, `clearLabel`, `userIsAdmin` — and it derives its own list with `effectiveFilters() => filtersFromSchema(this.schema)`. Vue drops an undeclared prop into `$attrs` silently, `schema` was never passed, and `filtersFromSchema(null)` returns []. So the sidebar rendered its "GEMMA filters" title over an empty body: no console error, no build error, no failing test. Verified against the SHIPPED dist of @conduction/nextcloud-vue 1.0.0-beta.213, not only its src/. The fix passes what the component actually declares. `buildFacetDimensionSchema` (src/utils/facetSchema.js) builds the schema document `filtersFromSchema` reads — `facetable: true` per property, `title` for the label, `order` for the sequence — so the four dimensions become four selects whose options come from the live facet counts this feature already fetches. tests/vitest/facetSchema.spec.js pins that contract against the REAL `filtersFromSchema` imported from the installed package, not a local copy of its rules — a copied rule set is only as fresh as its last manual edit and fails in both directions. One test is a positive control: it feeds the pre-fix shape (the derived filter LIST, no `properties` key) to the real function and asserts it yields [], which is the defect reproduced. Also corrected in the e2e suite: the PortfolioReport assertion targeted [data-testid=pr-summary], which sits behind `v-else-if="selectedOrg && report"`. On an instance with no organisation selected the page correctly renders its empty state, so that assertion was asserting on seed data rather than on the page. It now asserts the unconditional "Refresh report" control plus whichever of the page's two legitimate states is showing. vitest 220/220 (5 new). check:manifest Ajv PASS. gates 25 and 26 PASS.
Contributor
Quality Report — ConductionNL/softwarecatalog @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-vue-demi | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 128/128 | |||
| npm | ✅ | ✅ 718/718 | |||
| PHPUnit | ✅ | ||||
| Newman | ⏭️ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-09 20:33 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
gate-25 contract-coverage 1 → PASS, gate-26 visual-coverage 6 → PASS. hydra-gates
48c88ba1e0d049f8f38538c33e790d3e603c55d0, measured with--scope-to-diff --base origin/beta— the scope a push todevelopmentactually runs.gate-25 —
GET /api/reviews/aggregatehad no automated proofreview#aggregateis#[PublicPage]. An anonymous visitor on a module or dienst detail page reads it, so its response shape and its status codes are part of the app's public surface — not an implementation detail.ReviewControllerContractTestpins them:{average, count, items}with HTTP 200;ok/reasonbookkeeping never reaches the wire;message;subjectType/subjectIdreach the service unaltered.gate-26 — five page components with no browser-level proof
tests/e2e/spec-coverage/page-surfaces.spec.tsdrives the real UI: it clicks the app's own navigation the way a user does, then asserts the page's own content region rendered and that the app logged no console error and returned no 5xx.FacetedCatalogIndexView.vuesuites/SuitesIndexView.vueorganisaties/PortfolioReport.vuesettings/sections/EolSyncSettings.vueAsserting the shell alone would pass on a blank page, so each test also asserts something the page itself puts on screen.
KwetsbaarhedenViewandLicensePostureViewalready had real behavioural specs the gate could not attribute to them; those two specs now name their page component in the docblock. No new assertion was invented for them and none was needed — the coverage already existed, only the link to the component was missing. This is the one case in this PR where the fix is a reference rather than a test, and it is a reference to a test that genuinely drives that page.Visual baselines were deliberately not added:
toHaveScreenshotbaselines are platform-specific pixels, and generating them on this machine to satisfy a gate would commit screenshots that CI's renderer disagrees with — a red gate traded for a flaky one. The e2e route the gate offers is the honest one here.Can-fail proof
Removing
page-surfaces.spec.tsandReviewControllerContractTest.phpand reverting the two docblocks puts both counts back exactly:And the contract tests themselves can fail: mutating
ReviewController::aggregateto put'ok' => $result['ok']on the wire turns 2 of the 5 red —Other measurements
phpcs lib/unchanged at 0 errors / 87 warnings.tsc --noEmiton the new spec: clean.origin/beta: 14 → 12 failing.