fix(connections): mount connection routes in the HTTP server#17
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a wiring gap where the connection service was registered in the DI container (from PR #9) but never mounted in the HTTP muxer, causing all /projects/{projectId}/connection-* routes to return a bare 404 in the running binary. It mounts the generated connections Goa server, guarded on cont.Connections, so the routes are served (returning the typed 503 when the DB is unavailable rather than 404).
Changes:
- Build connection endpoints from
cont.Connectionsand apply the debug payload-logging middleware whencfg.Debugis set. - Thread
connEndpointsthroughhandleHTTPServerand mount the generated connections server on the shared muxer, reusing the existing error handler.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9d4fe78 to
61acc37
Compare
|
Rebased onto main to pick up #24's MegaLinter/gitleaks fix — the previous red MegaLinter run predated that merge. Single commit, no conflicts; required checks (License Header, DCO, Build and Test) green. Ready for review. |
61acc37 to
3035f9f
Compare
3035f9f to
42bcb1f
Compare
42bcb1f to
90dbef1
Compare
…(PR #17) Per Rashad's review: the `if cont.Connections != nil` guard before building the connection endpoints was always true (NewContainer wires Connections in both the DB and no-DB paths), so it was dead code that could mask a mis-wired container — callers would silently skip mounting the connection routes and serve 404s instead of failing at startup. Construct the endpoints unconditionally and return an explicit error if Connections is unexpectedly nil, so a mis-wire crashes loudly at startup. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
…ver (PR #17) The prior fix made StartServer fail loudly on a nil Connections service, but handleHTTPServer still had `if connEndpoints != nil { mount }`, reintroducing the same silent-404 failure mode. connEndpoints is now always non-nil (StartServer guarantees it), so mount unconditionally and fail loudly if it's ever nil rather than silently skipping the connection routes. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
debug.HTTP() in clue v1.2.1 does not log headers/statuses — it only propagates the runtime /debug toggle into the request context. Correct the server.go comment and the OKF cmd doc, which both wrongly described it as HTTP logging. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
The connection service was wired in the DI container but never mounted in the
HTTP muxer, so every /projects/{projectId}/connection-* route returned 404 in
the running binary even though the handlers existed. Mount the generated
connection server (guarded on cont.Connections, which is non-nil even without a
database so the routes return the typed 503 rather than a bare 404).
Not caught earlier because the unmounted service still compiles as dead code.
LFXV2-2556
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
…(PR #17) Per Rashad's review: the `if cont.Connections != nil` guard before building the connection endpoints was always true (NewContainer wires Connections in both the DB and no-DB paths), so it was dead code that could mask a mis-wired container — callers would silently skip mounting the connection routes and serve 404s instead of failing at startup. Construct the endpoints unconditionally and return an explicit error if Connections is unexpectedly nil, so a mis-wire crashes loudly at startup. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
…ver (PR #17) The prior fix made StartServer fail loudly on a nil Connections service, but handleHTTPServer still had `if connEndpoints != nil { mount }`, reintroducing the same silent-404 failure mode. connEndpoints is now always non-nil (StartServer guarantees it), so mount unconditionally and fail loudly if it's ever nil rather than silently skipping the connection routes. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
debug.HTTP() in clue v1.2.1 does not log headers/statuses — it only propagates the runtime /debug toggle into the request context. Correct the server.go comment and the OKF cmd doc, which both wrongly described it as HTTP logging. Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
5466690 to
1986fef
Compare
dealako
left a comment
There was a problem hiding this comment.
Hi @mrautela365 👋 — reviewed the final state of this PR after the full round of iteration with @MRashad26 and Copilot.
Overall impression: Tight, well-scoped fix for a genuine wiring bug — the connection service was DI-wired but never mounted, so every /projects/{projectId}/connection-* route 404'd in the running binary despite compiling. The mount is correct, and the PR thread shows careful, evidence-driven iteration: the always-true nil-guard was replaced with a fail-loud posture end-to-end, debug.LogPayloads() was removed entirely (it would have leaked bearer tokens + plaintext provider credentials), and the debug.HTTP() comment was corrected to match clue v1.2.1's actual behavior. I independently verified all three claims against the code.
Verification I ran:
connsvcsvr.New(connEndpoints, mux, decoder, encoder, eh, nil)matches the generated 6-arg constructor; thekoHTTPDirargs thesvcserver needs are correctly omitted (connections serves no OpenAPI files).NewContainersetsConnectionsnon-nil in both the DB (container.go:91) and no-DB (container.go:57) paths; nil-repo mode returns a typed 503 viaensureAvailable(), not a 404 — covered byTestNilRepo_ReturnsServiceUnavailable.- Auth isn't bypassed by mounting: JWT is baked into the generated endpoints (
a.JWTAuth), not the HTTP layer, andConnectionServiceimplements theAutherinterface. debug.LogPayloads()has zero live invocations repo-wide (comment-only).go build ./cmd/...clean; all CI checks green; mergeable.
Structured issue count:
- 🔴 Blocking: 0
- 🟡 Minor: 1 — no automated test locks in the route mount (the exact regression class this PR fixes).
- ⚪ Nit: 1 — the
connEndpoints == nilguard inhandleHTTPServeris statically unreachable. - ❔ Question: 0
Final decision: ✅ Approved with minor comments. Nothing here blocks merge — CI is green, every prior reviewer thread is resolved, and the security posture is clean. The one item worth a follow-up (not this PR) is a lightweight mount test; details inline.
…17) Address @dealako's [minor] review note that nothing automated guarded the invariant this PR establishes (connection routes are reachable) — a future deletion of the connsvcsvr.Mount call would compile and silently reintroduce the 404 regression. - server.go: extracted mux construction + both service mounts into a buildMux() seam returning the goahttp.Muxer; handleHTTPServer now calls it. The fail-loud nil-connEndpoints guard is retained as intentional defense-in-depth against a future refactor introducing a second caller (per the review). - server_test.go (new): TestConnectionRoutesAreMounted asserts a known connection route (POST /projects/{id}/connection-google-ads) and a campaign route (/livez) resolve to a real handler (non-404, using the no-DB services which return 503); TestBuildMuxNilConnEndpointsFailsLoud covers the guard. Verified: gofmt, check-headers, go build, go vet, golangci-lint (0 issues), go test -race ./cmd/... (pass). Signed-off-by: Misha Rautela <mrautela@linuxfoundation.org>
dealako
left a comment
There was a problem hiding this comment.
Thanks for the fast turnaround, @mrautela365 👏 — you closed out both items from the prior round cleanly, and in the best possible way.
👏 Nice work:
- Extracted the exact
buildMuxseam I suggested and addedTestConnectionRoutesAreMounted, which assertsPOST /projects/{id}/connection-google-ads(and/livez) resolve to a real handler — any non-404 proves the mount. I verified this genuinely locks the invariant: stubbing outconnsvcsvr.Mountwith a no-op makes the test fail with a 404, and restoring it passes. That's exactly the regression guard the PR was missing. - Turned my dead-guard nit into a tested invariant rather than deleting it. Moving the
connEndpoints == nilcheck intobuildMux(which the newTestBuildMuxNilConnEndpointsFailsLoudcalls directly) means the fail-loud guard is now exercised instead of statically unreachable — a genuinely better resolution than removing it.
Revision tracking:
- ✅ Resolved — [minor] no automated mount test →
buildMuxseam +TestConnectionRoutesAreMounted(82d6b0a). Mutation-verified. - ✅ Resolved — [nit] unreachable nil-guard → relocated into
buildMuxand now covered byTestBuildMuxNilConnEndpointsFailsLoud(82d6b0a).
New-diff review: The buildMux extraction is behavior-identical — same mux, same mount order, same middleware chain (RequestID → Debug-gated debug.HTTP() → otelhttp) applied by handleHTTPServer to the returned mux, same fail-loud posture. ctx/cfg/endpoints/connEndpoints all still used; no orphaned imports or params (go vet clean). The test uses only synthetic data (proj-123, {} body) — no PII, no secrets, and it makes no positive auth claim, so it can't mask an auth gap. go build ./cmd/..., go test ./cmd/campaign-service/..., and go vet ./cmd/... all pass; all CI checks green; still mergeable. Both my security and code-quality passes came back clean, and Copilot's review of this commit generated no new comments.
Structured issue count (all open items):
- 🔴 Blocking: 0
- 🟡 Minor: 0
- ⚪ Nit: 0 (a couple of purely cosmetic, take-it-or-leave-it observations inline — not worth a revision on their own)
Final decision: ✅ Approved. Both prior findings are fully resolved, the fix is well-tested, and there are no new issues. Ship it.
| for _, tc := range cases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| rec := httptest.NewRecorder() | ||
| req := httptest.NewRequest(tc.method, tc.path, strings.NewReader("{}")) |
There was a problem hiding this comment.
[nit] Cosmetic only — take-it-or-leave-it. The table sets a JSON body (strings.NewReader("{}")) and Content-Type: application/json for every case, including the GET /livez health row, which ignores the body. Harmless (it still returns 200), just slightly incongruous for a GET probe. Optional: set the body/content-type per-case, or add a one-line comment noting connsvcsvr.Mount is all-or-nothing so a single sentinel route is sufficient to detect a dropped mount. Neither is worth a revision.
The connection service is wired in the DI container (PR #9) but was never mounted in the HTTP muxer, so every
/projects/{projectId}/connection-*route returned 404 in the running binary even though the handlers existed.This mounts the generated connection server, guarded on
cont.Connections(which is non-nil even without a database — nil repo → typed 503 rather than a bare 404).Why it slipped through: an unmounted Goa service still compiles as dead code, so
go build/tests pass. Introduced when PR #9 was reconstructed onto main andserver.gowas not among the cherry-picked files.LFXV2-2556
🤖 Generated with Claude Code