fix(routes): two entries shared a route name, so two routes never registered - #2085
Merged
Merged
Conversation
…istered
Nextcloud names a route after its controller, its action and its postfix, and
RouteCollection::add() overwrites an entry of the same name. The URL and the
verb are not part of it. GET /api/lti/{deployment}/login was overwritten by the
POST beside it, and GET / by the SPA catch-all.
Measured with Nextcloud's own RouteParser: declared=261 registered=259 before,
261 after. The postfix goes on the entries nothing references by name, so
info.xml navigation and Flow\SynchronizationLogActions keep resolving
integriq.ui.dashboard to the catch-all they resolve today. No URL or verb
changes.
RouteNameUniquenessTest asserts on each entry's registration key and is
mutation-checked against the old routes file.
rubenvdlinde
requested review from
WilcoLouwerse,
bbrands02 and
rjzondervan
as code owners
September 19, 2026 08:50
Contributor
Quality Report — ConductionNL/integriq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ❌ | ||||
| phpmd | ❌ | ||||
| psalm | ❌ | ||||
| phpstan | ❌ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ❌ | ||||
| format | ❌ | ||||
| check-schema-l10n | ❌ | ||||
| check-l10n-js | ✅ | ||||
| composer | ✅ | ✅ 141/141 | |||
| npm | ✅ | ✅ 545/545 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| lockfile sync | ✅ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred: E2E runs locally and on the promotion path only. This pull request targets development, so the suite is asked once per promotion into beta and main rather than once per push per open pull request. Run it on any branch from the Actions tab, or locally with npx playwright test. |
||||
| Hydra gates | ❌ | ||||
| Hydra gates (axe) | ✅ |
Quality workflow — 2026-09-19 08:55 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.
Two declared routes were never registered
Nextcloud names a route
strtolower($appName.'.'.$controller.'.'.$action.$postfix)and
RouteCollection::add()overwrites an entry that already carries that name.The URL and the verb are not part of the name. So two entries pointing at the
same controller action with no
postfixare one route, and the last onedeclared is the one that survives. Nothing warns.
Measured with Nextcloud's own
RouteParser::parseDefaultRoutes()over thisfile, not with a regex:
GET /api/lti/{deployment}/loginPOSTbeside itGET /GET /{path}LTI 1.3 third-party login initiation is specified for both verbs and the
Platform picks. A Platform that picked GET got a 405 from an endpoint this app
advertises.
The
ui#dashboardpair carried a comment describing the overwrite as intendedlast-wins behaviour. That is what a lost route looks like once someone has
explained it to themselves.
The postfix goes on the entry that is not referenced
This is the one way the fix can do harm, so it was checked before pushing.
integriq.ui.dashboardis resolved by name in two places:appinfo/info.xmlnavigationlib/Flow/SynchronizationLogActions.php:94, aslinkToRoute('integriq.ui.dashboard', ['path' => ''])Today that name resolves to the catch-all, which takes
pathas a pathsegment. So the catch-all keeps the bare name and the
/entry takespostfix => 'Index'. Renaming the other way round would have turned thatcaller's
pathinto a query string.lti#loginis referenced nowhere, so theGET half takes
postfix => 'Get'and the currently reachable POST keeps itsname.
No URL and no verb changes in this PR.
The test, and what reddens it
tests/Unit/AppInfo/RouteNameUniquenessTestasserts on the registration key ofeach entry, not on the file parsing or the array being non-empty. Two tests:
no two entries may share a key, and the two lost routes are named by URL and
verb so a later edit cannot keep the count and lose them again.
Mutation-checked against the old
routes.php, and both assertions reddened,not a setup line:
composer check:routesalready passed on the broken file and still reportsPASS, all 261 routes point at existing controller methods. It reads thedeclared array and never asks what registers, which is why it could not see
this.
Verified
php -lon both filescomposer check:routes: PASS, 261 routesvendor/bin/phpunit --filter RouteNameUniquenessTest: 2 tests, 267 assertions, greenphpcsscopeslibonly in this repo, and neither changed file is under itInherited, not fixed here
appinfo/routes.phpcarries pre-existing phpcs findings, including a 157character line at
lti#agsScore. They are on lines this change did not touchand belong to the debt sweep.
🤖 Generated with Claude Code