Skip to content

test: de-flake ItemHandleCheckerIT (mock the live handle resolver)#1346

Open
milanmajchrak wants to merge 2 commits into
dtq-devfrom
fix/flaky-handle-checker-it
Open

test: de-flake ItemHandleCheckerIT (mock the live handle resolver)#1346
milanmajchrak wants to merge 2 commits into
dtq-devfrom
fix/flaky-handle-checker-it

Conversation

@milanmajchrak

@milanmajchrak milanmajchrak commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Why

After #1344 merged, a scheduled dtq-dev build went red on ItemHandleCheckerIT.testItemHandleNotFound:

expected: .../123456789/1239-1 = [404 - FAILED]
but was:  .../123456789/1239-1 = [617 - FAILED - Error: java.net.SocketTimeoutException: Read timed out]

This is not a regression from #1344 — the same merge commit passed 5 subsequent scheduled runs. It's a
separate, pre-existing flaky test (the 5th de-flaked so far), in the same family as the ORCID one: a live
network dependency
.

Root cause

The checkhandles curation task (ItemHandleChecker) issues a JAX-RS HEAD request to each item's handle
URL
(handle.canonical.prefix + handle) with a 3-second read timeout; on timeout it reports a synthetic
status 617. The test set handle.canonical.prefix = http://hdl.handle.net/ and asserted that the live
resolver returns 404 for a non-existent handle (testItemHandleNotFound) and 302 → 200 for a real handle
(testItemHandleRedirected, and testCurateCollection). Whenever hdl.handle.net was slow/unreachable from
the CI runner, the HEAD timed out → 617 instead of the expected status → red pipeline.

Fix (test-only — no production change)

Serve the handle URLs from a local okhttp3.mockwebserver.MockWebServer instead of the live resolver:

  • setUp() starts the mock server and sets handle.canonical.prefix to its base URL, so item canonical URLs
    resolve to http://localhost:<port>/....
  • A path-based Dispatcher returns deterministic responses: 302 (with a Location to a -target path) for
    the "real" redirect handle, 200 for the redirect target, and 404 for any other (well-formed) handle URL.
  • The real / invalid / ignored URLs are computed from the mock base instead of hard-coded hdl.handle.net
    literals; the mock is closed in @After.

This keeps all six tests and their assertions exactly as before (404, 302→200, no-item-fail,
client-side URISyntaxException→500, ignore-list skip, and the mixed-collection case) — it only removes the
non-deterministic network hop. No production code changed.

Verification

Run locally against the dspace-api IT harness 4×, all green (Tests run: 6, Failures: 0, Errors: 0), with
no external network involved (everything goes to loopback). Reviewed by a senior Java/Spring pass.

Summary by CodeRabbit

  • Tests
    • Handle-resolution tests now run against a local simulated service instead of a live external endpoint, making them more reliable and self-contained.
    • Test cases were updated to cover redirects, invalid handles, and ignored handles using dynamically generated local URLs.

ItemHandleCheckerIT.testItemHandleNotFound intermittently failed on dtq-dev with
a synthetic 617 (SocketTimeoutException) instead of 404. The checkhandles task
(ItemHandleChecker) issues a HEAD request to each item's handle URL
(handle.canonical.prefix + handle) with a 3s read timeout; the test pointed the
prefix at the live http://hdl.handle.net/ and asserted the resolver returns 404
(non-existent handle) and 302->200 (a real handle). When the live resolver was
slow/unreachable the HEAD timed out -> 617 -> red pipeline. Same class of flake
as the ORCID tests; not a regression (the merge commit passed many other runs).

Fix (test-only): serve the handle URLs from a local okhttp3 MockWebServer. setUp
sets handle.canonical.prefix to the mock base URL; a path-based dispatcher returns
302 (+Location to a -target path) for the redirect handle, 200 for the target, and
404 otherwise. The real/invalid/ignored URLs are derived from the mock base instead
of hard-coded hdl.handle.net literals; the server is closed in @after. All six tests
keep their original assertions and behavior; only the live-network hop is removed.

Verified locally: 6/6 green across 4 runs, no external network involved.

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

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@milanmajchrak, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 22 minutes and 56 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa481f24-8a36-4c31-b3b5-ed9403c3f37b

📥 Commits

Reviewing files that changed from the base of the PR and between 32742af and de0a2c8.

📒 Files selected for processing (1)
  • dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java
📝 Walkthrough

Walkthrough

ItemHandleCheckerIT now runs handle resolution tests against a local MockWebServer, computes mock-based handle URLs from its dynamic port, updates the test cases to use those URLs, and closes the mock server during teardown.

Changes

Mocked handle resolution tests

Layer / File(s) Summary
Mock server setup
dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java
Adds MockWebServer imports, updates the class description, replaces real handle constants with mock paths, and starts a dispatcher-backed server in setUp() with handle.canonical.prefix pointing at its base URL.
Mock URL test cases
dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java
Redirect, invalid, ignored, and collection-curation cases replace stored handle URIs with the computed mock URLs and assert the mock-based invalid handle string.
Mock server teardown
dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java
destroy() closes mockHandleServer before the registered handle and collection cleanup runs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: de-flaking ItemHandleCheckerIT by mocking the live handle resolver.
Description check ✅ Passed The description covers the problem, root cause, fix, and verification, but it does not use the repository's exact template headings or checkboxes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java`:
- Around line 97-128: The test setup in ItemHandleCheckerIT mutates the shared
cfg property handle.canonical.prefix to point at the mock MockWebServer, but
never restores the original value after the test finishes. Capture the previous
handle.canonical.prefix before setUp changes it, then restore it in the
corresponding teardown/cleanup path for ItemHandleCheckerIT so later tests are
not left pointing at a dead localhost server.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 25e86edd-81a9-46b7-bc41-bb7214f63d6b

📥 Commits

Reviewing files that changed from the base of the PR and between f6f1356 and 32742af.

📒 Files selected for processing (1)
  • dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java

Comment thread dspace-api/src/test/java/org/dspace/curate/ItemHandleCheckerIT.java
Address CodeRabbit: setUp overwrites the shared handle.canonical.prefix with the
per-run mock-server URL. Capture the previous value and restore it in destroy()
(after closing the mock server) so a later test in the same JVM is never left
pointing at the now-closed localhost port. (The superclass destroy() reloadConfig()
already resets it, but restoring explicitly makes the test self-contained.)

Re-verified locally: 6/6 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant