Skip to content

fix: reject invalid/negative lastUpdatedDuration on dataValueSets export instead of silently ignoring it#24470

Open
jason-p-pickering wants to merge 4 commits into
masterfrom
fix/DHIS2-21821-invalid-lastupdatedduration-silently-ignored
Open

fix: reject invalid/negative lastUpdatedDuration on dataValueSets export instead of silently ignoring it#24470
jason-p-pickering wants to merge 4 commits into
masterfrom
fix/DHIS2-21821-invalid-lastupdatedduration-silently-ignored

Conversation

@jason-p-pickering

Copy link
Copy Markdown
Contributor

Summary

Found while reviewing edge cases during DHIS2-21821 (#24465). GET /api/dataValueSets accepts a lastUpdatedDuration parameter, but a malformed or negative value (e.g. -5d, abc, 5x) is silently discarded with no error — the request proceeds as if it was never supplied.

  • DateUtils.getDuration(String) returns null on any parse failure (its regex ^(\d+)(d|h|m|s)$ requires unsigned digits, so -5d never matches).
  • DefaultDataExportService.decodeParams() called it unchecked, so an invalid string is indistinguishable from "not provided."
  • Consequence: if lastUpdatedDuration was the client's only time filter, this surfaces as the unrelated E2002 ("must specify a filter") error even though the client did specify one. Otherwise it's dropped with zero indication to the caller.
  • ErrorCode.E2005 ("Duration is not valid: {0}") already exists and is used for exactly this by the sibling DefaultCompleteDataSetRegistrationExchangeService (its createdDuration param), but was never wired up for DataExportParams.

Not in scope: lastUpdatedDuration=0d is deliberately left valid — it parses successfully (Duration.ZERO) and is a well-defined, if narrow, "as of right now" filter, the same reasoning as startDate == endDate being a valid zero-width date range (already covered by DataExportParamsTest).

Fix

DefaultDataExportService.decodeParams() now throws ConflictException(ErrorCode.E2005, rawValue) when lastUpdatedDuration is non-blank but fails to parse, mirroring the existing createdDuration validation pattern in the sibling service.

Test plan

  • New DefaultDataExportServiceTest (Mockito-based unit test): negative duration → E2005, malformed duration → E2005, 0d → no exception
  • Verified via mutation testing: temporarily disabled the new guard, confirmed only the two negative-case tests failed (the 0d test stayed green), then restored (git diff on the production logic nets to the same guard, cleanly re-added)
  • No regressions: DataExportServiceExportTest, DataValueServiceTest (integration, TestContainers), DataValueSetControllerTest (web-api integration) all pass
  • mvn spotless:check clean

🤖 Generated with Claude Code

…ort instead of silently ignoring it

DateUtils.getDuration() returns null on any parse failure (its regex requires
unsigned digits, so negative durations like "-5d" never match), and
DefaultDataExportService.decodeParams() called it unchecked, so a malformed or
negative lastUpdatedDuration was silently discarded - indistinguishable from
"not provided". If it was the client's only time filter, this surfaced as the
unrelated E2002 error instead of pointing at the actual problem; otherwise it
was dropped with no error at all.

ErrorCode.E2005 ("Duration is not valid: `{0}`") already exists for exactly
this and is used by the sibling DefaultCompleteDataSetRegistrationExchangeService
for its createdDuration parameter, but was never wired up here.

decodeParams() now throws ConflictException(E2005, rawValue) when
lastUpdatedDuration is non-blank but fails to parse. lastUpdatedDuration=0d
is deliberately left valid - it parses successfully (Duration.ZERO) and is a
well-defined (if narrow) "as of right now" filter, analogous to
startDate == endDate being a valid zero-width date range.

Verified via mutation testing: temporarily disabled the new guard, confirmed
only the two negative-case tests failed (the 0d-is-valid test stayed green),
then restored. No regressions in DataExportServiceExportTest,
DataValueServiceTest, or DataValueSetControllerTest.

Related to DHIS2-21821.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jason-p-pickering
jason-p-pickering requested review from a team and david-mackessy July 16, 2026 11:35
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.11%. Comparing base (9ad2958) to head (b520f1f).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
...is/dxf2/datavalueset/DefaultDataExportService.java 66.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             master   #24470       +/-   ##
=============================================
+ Coverage     30.40%   57.11%   +26.70%     
+ Complexity     1196      608      -588     
=============================================
  Files          3679     3711       +32     
  Lines        142430   143849     +1419     
  Branches      16619    16765      +146     
=============================================
+ Hits          43308    82160    +38852     
+ Misses        94894    54149    -40745     
- Partials       4228     7540     +3312     
Flag Coverage Δ
integration 50.14% <66.66%> (+19.73%) ⬆️
integration-h2 28.06% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...is/dxf2/datavalueset/DefaultDataExportService.java 79.47% <66.66%> (+22.22%) ⬆️

... and 1765 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1338988...b520f1f. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue
1 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

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.

2 participants