docs(geschichte): annotate GET /api/geschichten query parameters in OpenAPI spec (#794) #810
Reference in New Issue
Block a user
Delete Branch "feat/issue-794-geschichte-openapi-params"
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 #794
What changed
@WebMvcTesttests inGeschichteControllerTest:list_passesDocumentIdFilterToService,list_passesLimitToService,list_passesStatusFilterToService— pins existing routing behaviour that had no controller-level coverage (AC 5–7)@Parameterannotations on all four query params ofGeschichteController.list()—status,personId,documentId,limit— with descriptions covering the permission-gated PUBLISHED override, DRAFT author-scoping, AND-filter semantics, and the default/≤0/cap edge cases (AC 1–4)api.tsdiff = 4@descriptionJSDoc lines added, zero type changesPre-work
Created two follow-up issues as recommended by reviewers:
GeschichteServiceTest: list_withDraftRequest_returnsPublished_whenCallerLacksBlogWrite(P2-medium/test)GeschichteControllerTest: list_passesStatusDraftFilterToService(P3-later/test)Manual verification required (ACs 1–4)
ACs 1–4 verify annotation content in
/v3/api-docs— no automated test covers this at@WebMvcTestlevel. Verify by starting backend with dev profile and checkingGET http://localhost:8080/v3/api-docs→paths['/api/geschichten'].get.parametershas non-emptydescriptionon all four params.Test plan
GeschichteControllerTest— 29/29 passing./mvnw clean package -DskipTests)GET /v3/api-docswith dev profile — confirm 4 params have non-empty descriptionsSide fix
Added
/src.main/tofrontend/.prettierignore— this untracked generated artefact was not covered by existing ignore patterns and blocked pre-commit hooks on this branch.👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ✅ Approved
I read every changed file at head
09df686. This is exactly the kind of small, disciplined PR I like: it does one thing (annotate four query params + pin the existing routing behaviour with tests) and does it cleanly.What I checked
list_passesDocumentIdFilterToService,list_passesLimitToService,list_passesStatusFilterToService) follow the existing AAA pattern inGeschichteControllerTestexactly. Each has one logical assertion path: stub →perform→verify. Names read as sentences. Good.@Parameterdescriptions are intent-revealing and accurate againstGeschichteService.list(): the ≤0→50 fallback (safeLimit = limit <= 0 ? DEFAULT_LIMIT : Math.min(limit, MAX_LIMIT)) and thecurrentUserHasBlogWrite() ? status : PUBLISHEDoverride both match the prose. No drift between doc and code.api.ts— diff is 4@descriptionJSDoc lines, no type changes, exactly as the PR claims and the issue predicted.Suggestions (non-blocking)
status().isOk()plus averify(...). That's fine and consistent with the siblingpersonIdtest — but note they don't assert the response shape. That's acceptable here since the point is the routing passthrough, not serialization.statustest verifiesPUBLISHEDpassthrough; the DRAFT routing path (and the service-level override) are correctly deferred to #809 / #808. Splitting those out was the right call — keeps this PR atomic.Clean, atomic, well-tested. No changes requested from me.
🏛️ Markus Keller — Application Architect
Verdict: ✅ Approved
A documentation-only PR over the controller boundary, with no structural change. From an architecture standpoint there is nothing to reverse and nothing to extract.
What I checked
geschichteService.list(...); no repository reached into. The@Parameterannotations sit at the API boundary, which is exactly where contract documentation belongs.personIds == null ? List.of() : personIdsnormalization stays in the controller, the AND-filter sentinel logic stays in the service. The annotation prose documents the contract without leaking service internals (it describes the behaviour, not the00000000-...sentinel implementation detail). Correct level of abstraction.Geschichte,GeschichteStatus,BLOG_WRITEall pre-exist. No GLOSSARY, ADR, or C4 diagram update is triggered by this change. I ran my doc-impact table against the diff: no migration, no new route, no new service/controller, no new ErrorCode/Permission. Nothing to flag.@Parameterpattern (matchingDocumentController/NotificationController) and deliberately avoids@Operationon the list method, preserving codebase-wide annotation consistency. This was the right judgment call.Note (not a blocker)
The
frontend/src.main/side-fix is interesting: a stray untracked directory mirroringsrc/(lib/routes/paraglide). Ignoring it in.prettierignoreunblocks the hook, which is pragmatic. But that directory shouldn't exist at all — it smells like a misconfigured codegen output path. Worth a follow-up to find what produces it, but out of scope here and not blocking.Boring, correct, well-bounded. Approved.
🧪 Sara Holt — QA Engineer
Verdict: ⚠️ Approved with concerns
The three new
@WebMvcTesttests are correctly placed (web-layer slice, not full@SpringBootTest), deterministic, and follow the existing AAA + factory-stub conventions. I'm approving — but I want to be honest about a coverage gap and a verification gap so nobody mistakes this PR for full coverage of the documented behaviour.What's good
@WebMvcTest(GeschichteController.class)with mockedGeschichteService— fast, isolated, no DB. Correct for controller routing verification.@WithMockUser(authorities = "READ_ALL")is used consistently, matching the sibling tests.eq(...)while usingany()for the rest — clean isolation of the behaviour under test.Concerns (not blockers — tracked)
@Parameterprose makes three security-relevant claims: (a) non-BLOG_WRITEcallers always getPUBLISHED, (b)BLOG_WRITE+ DRAFT is author-scoped, (c)limit ≤ 0 → 50, cap 200. None of these are asserted by this PR's tests — they only verify the controller forwards the raw value to the service. The actual override/clamp logic lives inGeschichteService.list()and has no regression guard. This is correctly captured in #808 (service DRAFT override) — that's the most security-sensitive untested path. I'd push for #808 to be P2, not left to drift.limitedge cases untested anywhere.list_passesLimitToServiceproveslimit=5reaches the service. It does not provelimit=0 → 50,limit=-1 → 50, orlimit=10000 → 200. Those are pure functions in the service — cheap unit tests inGeschichteServiceTest. Recommend a follow-up alongside #808 (the issue's "limit description includes default/fallback/cap" AC is documented but not test-pinned)./v3/api-docsdescription content is only checkable manually. That's an accepted limitation of@WebMvcTest(SpringDoc isn't on the slice), but it means the "non-empty description" ACs ride on a manual checkbox that is still unchecked in the PR body. Please complete that manualGET /v3/api-docscheck before merge.No flaky tests, no H2, no wrong-layer tests. The scope is honest. Approved with the above tracked.
🛡️ Nora "NullX" Steiner — Application Security Engineer
Verdict: ⚠️ Approved with concerns
No vulnerability introduced — this PR adds annotations and tests, touching no auth logic. But documenting a security-relevant access-control behaviour without a regression test pinning it is a security smell I want on record.
What I checked
GET /api/geschichtenwas already@RequestMapping-restricted to GET via@GetMapping. Annotations don't change the method, the permission model, or the SecurityConfig. No injection vector — descriptions are static string literals, not interpolated user input.currentUserHasBlogWrite() ? status : PUBLISHED), andgetByIdcorrectly returnsNOT_FOUND(notFORBIDDEN) for DRAFTs to avoid leaking existence. Good design — the override is a genuine authorization boundary: aREAD_ALLreader who passes?status=DRAFTis silently downgraded toPUBLISHEDand cannot enumerate other users' unpublished stories.Concern (smell, not a confirmed vuln)
That authorization boundary — "non-
BLOG_WRITEcallers can never receive DRAFT regardless of thestatusthey pass" — is now documented in the public OpenAPI spec but has no test asserting it holds. This is precisely the failure mode in my playbook: a future maintainer readseffective = currentUserHasBlogWrite() ? status : PUBLISHED, thinks "why ignore the caller's status?", and removes it "to simplify" — turning an IDOR-style enumeration guard off, with no test going red.GeschichteControllerTestonly proves the controller forwardsstatusverbatim (list_passesStatusFilterToServiceassertsPUBLISHEDpassthrough). The override happens one layer down and is untested.list_withDraftRequest_returnsPublished_whenCallerLacksBlogWrite) is exactly the regression test I'd require. From my chair this isn't "P2-medium nice-to-have" — a documented access-control rule with zero test coverage is a security gap. I'd bump it and land it soon, ideally before anyone touchesGeschichteService.list()again.Approving this PR (it doesn't introduce the gap, it inherits it), but flagging #808 as the security-priority follow-up.
🎨 Leonie Voss — UX & Accessibility
Verdict: ✅ Approved (LGTM — nothing in my domain)
This PR is backend OpenAPI annotations, three controller tests, a
.prettierignoreentry, and a regeneratedapi.tswhose only diff is JSDoc@descriptioncomments. There is no.sveltefile, no route, no markup, no CSS, no user-facing string, and no i18n key in the diff.What I checked (and found nothing to flag)
getErrorMessage()mapping touched — the param descriptions are developer-facing Swagger/spec documentation, never rendered to an end user.The only thing that could be user-adjacent is the generated
api.ts, but those@descriptionlines are IDE/devtool documentation, not runtime UI copy.Nothing for me here. Approved.
⚙️ Tobias Wendt — DevOps & Platform Engineer
Verdict: ✅ Approved
No infrastructure surface in this PR — no Compose file, no CI workflow, no Dockerfile, no env var, no image tag, no secret. The diff is Java annotations, Java tests, a regenerated
api.ts, and one.prettierignoreline. From an ops standpoint this is a no-op deploy: same JAR shape, same endpoints, same ports.The one thing in my lane:
frontend/.prettierignoreThe
/src.main/entry unblocks the pre-commit hook (cd frontend && npm run lint) on this branch, where a stray untrackedfrontend/src.main/directory was tripping Prettier. Confirmed it's not tracked in git (git ls-filesshows nothing), so this is purely a local/hook concern — correct fix, correct file, zero production impact.Note (not a blocker)
That
src.main/directory existing at all is the real smell — it mirrorssrc/(lib, routes, paraglide) and looks like an accidental codegen output, possibly from a misconfiguredgenerate:apior a path typo (src.mainvssrc/main). Ignoring it stops the bleeding; finding and fixing the generator that emits it is the actual fix. Worth a quick follow-up issue so it doesn't quietly accumulate on every dev's machine. Out of scope for this PR.CI impact: three extra fast
@WebMvcTestmethods — negligible pipeline time. Approved.Implementation verification — Felix Brandt
All five reviewers approved with no code changes requested. I ran the final checks at head
09df686:Automated checks ✅
GeschichteControllerTest— 29/29 passing (includes the three new routing tests from this PR)./mvnw clean package -DskipTests— BUILD SUCCESS, JAR built cleanlyOpen concerns from reviewers
GET /v3/api-docscheck (Sara — unchecked checkbox in PR body)devprofilelist_withDraftRequest_returnsPublished_whenCallerLacksBlogWrite(Sara + Nora, security-priority)list_passesStatusDraftFilterToService(Sara)src.main/stray directory origin (Markus + Tobias)Branch status
Branch
feat/issue-794-geschichte-openapi-paramsis already pushed and up to date with remote. No new commits were needed — the PR was complete as submitted.Suggested next step: complete the manual
/v3/api-docscheck (start backend with--spring.profiles.active=dev, confirm all fourparameters[].descriptionfields are non-empty), then merge.