fix(comment): declare missing @PathVariable params on block comment endpoints #489
Reference in New Issue
Block a user
Delete Branch "fix/issue-473-pathvariable-names"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #473
Problem
Two endpoints in
CommentControllerhad path variables declared in the URL template that were not bound to method parameters:GET /api/documents/{documentId}/transcription-blocks/{blockId}/comments—documentIdwas in the URL but not declared as a@PathVariable, so Spring silently ignored it (any string was accepted)POST /api/documents/{documentId}/transcription-blocks/{blockId}/comments/{commentId}/replies—blockIdwas missing; onlydocumentIdandcommentIdwere boundSpring MVC silently ignores undeclared path variables — it extracts them but discards the segment without type-checking. This means a non-UUID
documentIdorblockIdwould not produce a 400, and SpringDoc would emit an OpenAPI spec with a dangling path parameter.Fix
Added the missing
@PathVariable UUID documentIdtogetBlockCommentsand@PathVariable UUID blockIdtoreplyToBlockComment. No logic changes — the parameters were already being passed correctly to the service layer; they just weren't declared for the endpoints that were missing them.Tests
Added two behavioral RED→GREEN tests in
CommentControllerTest:getBlockComments_returns400_when_documentId_is_not_a_UUID— confirmed 200 before fix, 400 afterreplyToBlockComment_returns400_when_blockId_is_not_a_UUID— confirmed 201 before fix, 400 after👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ✅ Approved
TDD Evidence
Red/green discipline is solid. Both tests confirmed RED with the right failure messages (
Status expected:<400> but was:<200>andStatus expected:<400> but was:<201>) before the fix. That's exactly what you want — proof the test would have caught a real regression.Code Quality
The fix is surgical and minimal — exactly what a bug fix should be. No logic changes, no refactoring, no feature creep.
One thing worth noting explicitly:
documentIdis now bound ingetBlockCommentsbut not forwarded to the service — the service only needsblockIdto fetch block comments. This is intentional and correct. The parameter must be declared so Spring performs UUID type conversion (and rejects invalid values with 400), even if the value isn't used downstream. A future reader might question it — the PR description already explains this well, which is the right place for it.Suggestions
None. This is a clean, focused fix with proper TDD evidence. Ship it.
🔒 Nora "NullX" Steiner — Application Security Engineer
Verdict: ✅ Approved
What Was Fixed and Why It Matters
The root issue is CWE-20: Improper Input Validation. When a path variable appears in the URL template but isn't declared as a
@PathVariableparameter, Spring MVC silently discards it — no type coercion, no validation. That means a caller could send any string in thedocumentIdorblockIdposition and Spring would happily route the request to the handler.In isolation this isn't a high-severity finding (no IDOR risk here since the service does its own lookup by
blockId, and the endpoint is authenticated), but:The fix correctly enforces UUID parsing at the Spring MVC dispatcher level. Non-UUID values now fail with 400 before the handler body executes.
Pre-existing Observation (not introduced by this PR)
getBlockCommentshas no@RequirePermissionannotation — any authenticated user can read block comments for any block.postBlockCommentrequiresANNOTATE_ALLorWRITE_ALL. This asymmetry appears intentional (read = any authenticated user, write = privileged), but worth a quick confirmation that this is a deliberate product decision, not an overlooked annotation.No New Concerns
Auth annotations on all write endpoints are correct. No unparameterised queries introduced. No logging of user-controlled input. LGTM.
🏛️ Markus Keller — Application Architect
Verdict: ✅ Approved
Architecture Check
document.comment. No cross-domain reach.CommentControllerowns its path variable declarations. The service's public API is untouched.Documentation Check
Per my review table: this PR contains no new Flyway migrations, no new packages or domain modules, no new routes, no new Docker services, no new ErrorCode or Permission values, and no new external integrations. No doc updates are required for a bug fix of this scope.
Nothing to Flag
Clean, contained fix. The approach of declaring path variables explicitly (even when the controller method doesn't use the value downstream) is the correct Spring MVC idiom — it's how SpringDoc generates accurate parameter specs and how the dispatcher enforces type coercion before the handler executes. No architectural concerns.
🧪 Sara Holt — Senior QA Engineer
Verdict: ✅ Approved
Test Coverage Assessment
RED/GREEN evidence: Both tests confirmed RED before the fix:
getBlockComments_returns400_when_documentId_is_not_a_UUID: was 200, expected 400 ✓replyToBlockComment_returns400_when_blockId_is_not_a_UUID: was 201, expected 400 ✓Test naming: Descriptive and readable as sentences — passes my naming check.
Authority setup:
@WithMockUser(authorities = "ANNOTATE_ALL")on the reply test is correct. Without it, the AOP permission check would return 403 before Spring even tries to bind the path variable, making the test meaningless for this specific behavior.Test targeting: Each test targets precisely the missing parameter for its endpoint:
getBlockCommentswas missingdocumentId→ test sends non-UUIDdocumentId✓replyToBlockCommentwas missingblockId→ test sends non-UUIDblockId✓Test layer:
@WebMvcTestslice is the right choice here — we're testing Spring MVC path variable binding behavior, which lives in the web layer. No database, no Testcontainers needed.Minor Observation
The
getBlockCommentstest uses a freshly generatedblockIdUUID that has no stub —commentService.getCommentsForBlock(blockId)would return null by default if Spring ever reached the handler. But since the test expects 400 (handler should never be reached), this is fine. The test would fail with a 200/NPE if the fix regressed, which is the desired behavior.Full Suite
All 16 tests in
CommentControllerTestpass. No regressions. LGTM.⚙️ Tobias Wendt — DevOps & Platform Engineer
Verdict: ✅ Approved
No infrastructure, CI, Docker Compose, or deployment changes in this PR. Pure backend bug fix — two Java files changed.
The backend JAR builds and tests pass cleanly, which is all I need to confirm from a platform perspective. Nothing to flag.
📋 Elicit — Requirements Engineer
Verdict: ✅ Approved
Requirements Traceability
Issue #473 described a specific correctness gap: path variables declared in URL templates but not bound to handler parameters. The fix delivers exactly what was specified — no more, no less. There is no scope creep.
Acceptance criteria (implicit in the issue):
GET /api/documents/{documentId}/transcription-blocks/{blockId}/commentsrejects non-UUIDdocumentIdwith 400POST /api/documents/{documentId}/transcription-blocks/{blockId}/comments/{commentId}/repliesrejects non-UUIDblockIdwith 400Both are verified by the regression tests.
No NFR Concerns
This fix improves input validation (a security NFR) and OpenAPI spec correctness (a developer-experience NFR) with zero impact on performance, availability, or user-facing behavior for valid inputs. Existing callers passing valid UUIDs are unaffected.
🎨 Leonie Voss — UX Designer & Accessibility Strategist
Verdict: ✅ Approved
Backend-only fix — no Svelte components, no routes, no HTML, no CSS, no i18n strings. Nothing in this PR touches the UI layer.
The 400 error response for malformed UUIDs is handled by Spring's global exception handler, which maps to a structured error response. The frontend already handles API errors through
getErrorMessage(). No UX impact to review.