Skip to content

fix: validate paging parameters of ListTasks - #115

Merged
uniqueg merged 7 commits into
devfrom
fix/114-paging-validation
Nov 7, 2026
Merged

uniqueg merged 7 commits into
devfrom
fix/114-paging-validation

Conversation

@uniqueg

@uniqueg uniqueg commented Nov 7, 2026 •

Copy link
Copy Markdown
Member

Summary

GET /tasks now validates its paging parameters:

  • An expired or malformed page_token (Kubernetes answers 410 Gone, or 400 Bad Request with a Status about the continue key; other 400s stay 500) gives 400 Bad Request instead of 500. The message names page_token but never its value; the Kubernetes message, which can repeat the token, is dropped.
  • page_size must be between 1 and 2048 (inclusive); other numbers give 400 Bad Request. Before, any number was accepted, and numbers beyond the range of an int wrapped around. 2048 sits in a constant that also feeds the API description.
  • A request parameter of the wrong type (e.g. page_size=abc) still gives 400, but the message now names the parameter instead of repeating the value (CustomErrorHandler.handleTypeMismatch). Today page_size is the only typed request parameter.
  • Docs: docs/api.md ("Listing tasks") and docs/upgrading.md.

Closes #114

Verification

  • Wrote the regression tests first; they failed against dev (400 expected, 200 and 500 returned; KubernetesException instead of the new exception).
  • ./mvnw -B spotless:check checkstyle:check clean verify (Temurin 8): BUILD SUCCESS, 7329 unit tests and 53 integration tests, no failures.
  • I couldn't run mkdocs build --strict locally because the Material theme isn't installed. The Docs job in CI covers it.
  • CI on 9a19a78: all checks pass (CI passed, Lint, Test, Docs, Smoke test, Scan image, CodeQL, Sourcery review, codecov/patch 100% after adding tests for the remaining branches).

Declined review findings

  • Null-guard KubernetesException.getApiException(): the constructor is private, and the only factory is always given the caught ApiException, so the value can't be null.
  • Forged tokens that Kubernetes answers with neither 410 nor 400 (e.g. a future resource version, which gives 500 or 504) still give 500. Mapping 5xx to client errors would hide real cluster failures. This is documented in docs/api.md.
  • page_size=2048 is accepted, although the TES 1.1 text says "less than 2048". This follows the issue. Clients that follow the spec never send 2048, so accepting it doesn't hurt. The constant's comment says so.
  • Sourcery: an empty page_size= is treated as absent, like the other optional parameters of GET /tasks (empty name_prefix and page_token also mean "not given"); rejecting it would need parsing the raw parameter outside the Spring binding.
  • Sourcery: forged tokens answered with 5xx stay 500 (see above).
  • No end-to-end test from the Kubernetes error to HTTP 400: both halves are tested (wrapper: ApiException to InvalidPageTokenException; controller: exception to 400 and message).

Open questions

None.

🤖 Generated with Claude Code

uniqueg and others added 4 commits November 7, 2026 20:26
GET /tasks answered an expired or malformed page_token with 500 and
accepted any page_size, wrapping around beyond the range of an int.

- Kubernetes refuses a continue token with 410 (expired) or 400
  (malformed); with a page_token, both now give 400 Bad Request with a
  message that names page_token, never its value.
- page_size must be between 1 and 2048 (the maximum named by the TES
  1.1 API description); other values give 400 Bad Request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the allowed page_size range and the 400 for an invalid
page_token in the API docs ("Listing tasks") and the upgrade notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spring answered a page_size that is not a whole number with a message
that repeats the value. Name the parameter instead, for all request
parameters. Also take the page_size limit in the API description from
the constant, share the Kubernetes stub of the wrapper tests, and say
in the docs which page_token errors give 400.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uniqueg uniqueg added this to the v1.2.0 milestone Nov 7, 2026
@sourcery-ai

sourcery-ai Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

API

Validate page_size at the ListTasks boundary and publish its accepted range in the API parameter description.

  • src/main/java/uk/ac/ebi/tsc/tesk/tes/api/TasksApiController.java L31-36 (+3 small edits)

Define dedicated client-error exceptions for out-of-range page sizes and rejected page tokens.

  • src/main/java/uk/ac/ebi/tsc/tesk/tes/exception/InvalidPageSizeException.java L1-14
  • src/main/java/uk/ac/ebi/tsc/tesk/tes/exception/InvalidPageTokenException.java L1-18

Uses of the API

Translate Kubernetes expired or malformed continue-token responses into a sanitized page-token client error while preserving unrelated Kubernetes failures.

  • src/main/java/uk/ac/ebi/tsc/tesk/k8s/service/KubernetesClientWrapper.java L200-209, L234-250 (+2 small edits)

Implementation

Replace Spring type-mismatch error text for named request parameters so it identifies the parameter without echoing its invalid value.

  • src/main/java/uk/ac/ebi/tsc/tesk/config/CustomErrorHandler.java L6-8, L27, L111-128

Documentation

Document the page-size range, invalid-token responses, and upgrade implications for clients using paging parameters.

  • docs/api.md L29-32, L47-52
  • docs/upgrading.md L50-59

Tests

Cover token-error classification in the Kubernetes wrapper, including unrelated and insufficiently described Kubernetes errors.

  • src/test/java/uk/ac/ebi/tsc/tesk/k8s/service/KubernetesClientWrapperTest.java L4-7, L32-33, L452-531

Test named and unnamed type-mismatch error handling and ListTasks paging validation at bounds, invalid input, and rejected tokens.

  • src/test/java/uk/ac/ebi/tsc/tesk/config/CustomErrorHandlerTest.java L1-48
  • src/test/java/uk/ac/ebi/tsc/tesk/tes/api/TasksApiControllerTest.java L4-9, L47-50, L303-381 (+1 small edits)

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.24%. Comparing base (0c6a8e3) to head (9a19a78).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #115      +/-   ##
==========================================
+ Coverage   62.66%   63.24%   +0.57%     
==========================================
  Files          64       66       +2     
  Lines        2510     2530      +20     
  Branches      259      262       +3     
==========================================
+ Hits         1573     1600      +27     
+ Misses        797      790       -7     
  Partials      140      140              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Codecov requires full patch coverage; the branch for type mismatches
that are not about a named method argument had no test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uniqueg
uniqueg marked this pull request as ready for review November 7, 2026 20:01

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/uk/ac/ebi/tsc/tesk/k8s/service/KubernetesClientWrapper.java" line_range="203" />
<code_context>
+        try {
+            result = this.listJobs(pageToken, labelSelector, itemsPerPage);
+        } catch (KubernetesException e) {
+            if (pageToken != null && isInvalidContinueToken(e.getApiException())) {
+                // a client error; the message names the parameter, never the token
+                throw new InvalidPageTokenException();
</code_context>
<issue_to_address>
**Other list errors look like bad tokens**

When a list request includes a page token and Kubernetes returns 400 for another parameter, such as a generated label selector or namespace, `isInvalidContinueToken` treats the 400 as an invalid token, so `listAllTaskmasterJobsForUser` throws `InvalidPageTokenException` and callers are told to restart paging even though the underlying request still fails.

Only convert a 400 to `InvalidPageTokenException` when the response identifies the continue token as the cause.
</issue_to_address>

### Comment 2
<location path="src/main/java/uk/ac/ebi/tsc/tesk/tes/api/TasksApiController.java" line_range="100-101" />
<code_context>
                     @RequestParam(value = "view", required = false, defaultValue = "MINIMAL")
                     String view) {

+        if (pageSize != null && (pageSize < 1 || pageSize > MAX_PAGE_SIZE)) {
+            throw new InvalidPageSizeException(MAX_PAGE_SIZE);
+        }
         TesListTasksResponse response =
</code_context>
<issue_to_address>
**Empty page sizes bypass validation**

When a request explicitly supplies `page_size=`, spring converts an empty optional `Long` request parameter to `null`, so the range check is skipped and `TesService.listTasks` receives no limit; callers see an unbounded listing instead of a 400 for the supplied invalid value.

Distinguish an absent parameter from an explicitly empty value before converting it to `Long`, and reject the empty value with 400.
</issue_to_address>

### Comment 3
<location path="src/test/java/uk/ac/ebi/tsc/tesk/config/CustomErrorHandlerTest.java" line_range="37-39" />
<code_context>
+                        HttpStatus.BAD_REQUEST,
+                        new ServletWebRequest(request));
+
+        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
+        assertNull(request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
+        assertEquals(400, ((Map<?, ?>) response.getBody()).get("status"));
+    }
+}
</code_context>
<issue_to_address>
**Fallback message goes unchecked**

When the handler’s response message changes while its status and request attribute stay as asserted, when `handleTypeMismatch` returns an empty or altered message for a plain `TypeMismatchException` but keeps the 400 status and leaves `ERROR_MESSAGE` unset, `handleTypeMismatch_withoutParameterName` checks only the status and attribute, so it passes despite Spring’s fallback message being lost.

Assert that the response retains Spring’s default type-mismatch message.
</issue_to_address>

Linked issues

#114 fix: validate paging parameters of ListTasks · 4 of 4 met

# Requirement Status Notes
1 Return 400 for an unusable page_token without echoing its value ✅ Met Kubernetes 410 and 400 responses are mapped to InvalidPageTokenException when a token was supplied; its message names page_token and omits the token.
2 Validate page_size as positive with a documented maximum; return 400 outside the range ✅ Met The controller enforces 1–2048 inclusive, and the API and upgrading docs describe the limit.
3 Add regression tests and update docs/api.md and docs/upgrading.md ✅ Met Tests cover invalid tokens, page-size bounds and type mismatches; both requested documentation files are updated.
4 Write regression tests first ✅ Met The PR description states the regression tests were written first and failed against dev.

Sourcery assessment

Approval pending. 3 findings to address first.

Blocking findings: src/main/java/uk/ac/ebi/tsc/tesk/k8s/service/KubernetesClientWrapper.java:203, src/main/java/uk/ac/ebi/tsc/tesk/tes/api/TasksApiController.java:101, src/test/java/uk/ac/ebi/tsc/tesk/config/CustomErrorHandlerTest.java:39


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

uniqueg and others added 2 commits November 7, 2026 21:08
Kubernetes answers a malformed continue token with 400 and a Status
message about the continue key; other 400s of a list request (e.g. for
a label selector) now stay server errors instead of being reported as
an invalid page_token. Also check that type mismatches without a
parameter name keep the message of Spring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linked issues

#114 fix: validate paging parameters of ListTasks · 2 of 3 met

# Requirement Status Notes
1 Return 400 for an invalid or expired page_token, with a message naming the parameter. 🟡 Partly met Kubernetes 410 responses and 400 responses whose body mentions continue become 400, but unusable tokens that Kubernetes rejects with a 5xx still result in 500.
2 Validate page_size as positive with a documented maximum, and return 400 outside that range. ✅ Met The controller enforces 1–2048 inclusive, and the API and upgrade docs state the limit.
3 Add regression tests and update docs/api.md and docs/upgrading.md. ✅ Met Tests cover invalid tokens and page-size boundaries and malformed values; both requested docs are updated. The PR description says the regression tests were written first.

@uniqueg
uniqueg merged commit 3b7980d into dev Nov 7, 2026
34 checks passed
@uniqueg
uniqueg deleted the fix/114-paging-validation branch November 7, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: validate paging parameters of ListTasks

1 participant