From b17e8cde078abaa3516be4a765c444619261f382 Mon Sep 17 00:00:00 2001 From: Jason Pickering Date: Thu, 16 Jul 2026 12:19:22 +0200 Subject: [PATCH 1/2] test: fill in unit test coverage for DataExportParams validation predicates Follow-up from DHIS2-21821 / PR #24465. Before that fix, DataExportParams had no direct unit tests for any of its validation predicates, which is likely how the lastUpdated/lastUpdatedDuration branches were silently dropped from hasPeriodFilters() during the 2.43 SQL-export rewrite (#22406) without any test catching it. Coverage existed only indirectly via slow integration tests asserting the resulting ErrorCode. Adds direct unit tests for every branch of hasDataElementFilters, hasOrgUnitFilters, isExactOrgUnitsFilter, isPeriodOverSpecified, isDateRangeOutOfBounds, isLimitOutOfBounds, isOrgUnitGroupsOverSpecified, plus the previously-untested true-branches of hasPeriodFilters itself (periods, periodTypes, startDate+endDate, includedDate). Verified isPeriodOverSpecified and isDateRangeOutOfBounds tests aren't vacuous by temporarily dropping a conjunct from each in production code, confirming the corresponding _OnlyFalse test caught it, then restoring (net diff to DataExportParams.java is zero). Co-Authored-By: Claude Sonnet 5 --- .../dhis/datavalue/DataExportParamsTest.java | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java index a317e5b83094..3866ec00ac14 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java @@ -34,6 +34,11 @@ import java.time.Duration; import java.util.Date; +import java.util.List; +import java.util.Set; +import org.hisp.dhis.common.UID; +import org.hisp.dhis.period.MonthlyPeriodType; +import org.hisp.dhis.period.Period; import org.junit.jupiter.api.Test; class DataExportParamsTest { @@ -58,6 +63,44 @@ void testHasPeriodFilters_LastUpdatedAloneIsNotAPeriodFilter() { assertFalse(params.hasPeriodFilters()); } + @Test + void testHasPeriodFilters_PeriodsTrue() { + DataExportParams params = + DataExportParams.builder().periods(List.of(Period.of("202201"))).build(); + + assertTrue(params.hasPeriodFilters()); + } + + @Test + void testHasPeriodFilters_PeriodTypesTrue() { + DataExportParams params = + DataExportParams.builder().periodTypes(Set.of(new MonthlyPeriodType())).build(); + + assertTrue(params.hasPeriodFilters()); + } + + @Test + void testHasPeriodFilters_StartAndEndDateTrue() { + DataExportParams params = + DataExportParams.builder().startDate(new Date(0)).endDate(new Date(1)).build(); + + assertTrue(params.hasPeriodFilters()); + } + + @Test + void testHasPeriodFilters_StartDateAloneFalse() { + DataExportParams params = DataExportParams.builder().startDate(new Date(0)).build(); + + assertFalse(params.hasPeriodFilters()); + } + + @Test + void testHasPeriodFilters_IncludedDateTrue() { + DataExportParams params = DataExportParams.builder().includedDate(new Date()).build(); + + assertTrue(params.hasPeriodFilters()); + } + @Test void testHasLastUpdatedFilters_NoFiltersFalse() { DataExportParams params = DataExportParams.builder().build(); @@ -79,4 +122,192 @@ void testHasLastUpdatedFilters_LastUpdatedTrue() { assertTrue(params.hasLastUpdatedFilters()); } + + @Test + void testHasDataElementFilters_NoFiltersFalse() { + DataExportParams params = DataExportParams.builder().build(); + + assertFalse(params.hasDataElementFilters()); + } + + @Test + void testHasDataElementFilters_DataSetsTrue() { + DataExportParams params = DataExportParams.builder().dataSets(List.of(UID.generate())).build(); + + assertTrue(params.hasDataElementFilters()); + } + + @Test + void testHasDataElementFilters_DataElementsTrue() { + DataExportParams params = + DataExportParams.builder().dataElements(List.of(UID.generate())).build(); + + assertTrue(params.hasDataElementFilters()); + } + + @Test + void testHasDataElementFilters_DataElementGroupsTrue() { + DataExportParams params = + DataExportParams.builder().dataElementGroups(List.of(UID.generate())).build(); + + assertTrue(params.hasDataElementFilters()); + } + + @Test + void testHasOrgUnitFilters_NoFiltersFalse() { + DataExportParams params = DataExportParams.builder().build(); + + assertFalse(params.hasOrgUnitFilters()); + } + + @Test + void testHasOrgUnitFilters_OrganisationUnitsTrue() { + DataExportParams params = + DataExportParams.builder().organisationUnits(List.of(UID.generate())).build(); + + assertTrue(params.hasOrgUnitFilters()); + } + + @Test + void testHasOrgUnitFilters_OrganisationUnitGroupsTrue() { + DataExportParams params = + DataExportParams.builder().organisationUnitGroups(List.of(UID.generate())).build(); + + assertTrue(params.hasOrgUnitFilters()); + } + + @Test + void testIsExactOrgUnitsFilter_OrgUnitsOnlyTrue() { + DataExportParams params = + DataExportParams.builder().organisationUnits(List.of(UID.generate())).build(); + + assertTrue(params.isExactOrgUnitsFilter()); + } + + @Test + void testIsExactOrgUnitsFilter_NoOrgUnitsFalse() { + DataExportParams params = DataExportParams.builder().build(); + + assertFalse(params.isExactOrgUnitsFilter()); + } + + @Test + void testIsExactOrgUnitsFilter_IncludeDescendantsFalse() { + DataExportParams params = + DataExportParams.builder() + .organisationUnits(List.of(UID.generate())) + .includeDescendants(true) + .build(); + + assertFalse(params.isExactOrgUnitsFilter()); + } + + @Test + void testIsExactOrgUnitsFilter_OrgUnitGroupsAlsoSetFalse() { + DataExportParams params = + DataExportParams.builder() + .organisationUnits(List.of(UID.generate())) + .organisationUnitGroups(List.of(UID.generate())) + .build(); + + assertFalse(params.isExactOrgUnitsFilter()); + } + + @Test + void testIsPeriodOverSpecified_PeriodsAndStartEndDateTrue() { + DataExportParams params = + DataExportParams.builder() + .periods(List.of(Period.of("202201"))) + .startDate(new Date(0)) + .endDate(new Date(1)) + .build(); + + assertTrue(params.isPeriodOverSpecified()); + } + + @Test + void testIsPeriodOverSpecified_PeriodsOnlyFalse() { + DataExportParams params = + DataExportParams.builder().periods(List.of(Period.of("202201"))).build(); + + assertFalse(params.isPeriodOverSpecified()); + } + + @Test + void testIsPeriodOverSpecified_StartEndDateOnlyFalse() { + DataExportParams params = + DataExportParams.builder().startDate(new Date(0)).endDate(new Date(1)).build(); + + assertFalse(params.isPeriodOverSpecified()); + } + + @Test + void testIsDateRangeOutOfBounds_StartAfterEndTrue() { + DataExportParams params = + DataExportParams.builder().startDate(new Date(1)).endDate(new Date(0)).build(); + + assertTrue(params.isDateRangeOutOfBounds()); + } + + @Test + void testIsDateRangeOutOfBounds_StartBeforeEndFalse() { + DataExportParams params = + DataExportParams.builder().startDate(new Date(0)).endDate(new Date(1)).build(); + + assertFalse(params.isDateRangeOutOfBounds()); + } + + @Test + void testIsDateRangeOutOfBounds_StartDateOnlyFalse() { + DataExportParams params = DataExportParams.builder().startDate(new Date(1)).build(); + + assertFalse(params.isDateRangeOutOfBounds()); + } + + @Test + void testIsLimitOutOfBounds_NegativeTrue() { + DataExportParams params = DataExportParams.builder().limit(-1).build(); + + assertTrue(params.isLimitOutOfBounds()); + } + + @Test + void testIsLimitOutOfBounds_ZeroFalse() { + DataExportParams params = DataExportParams.builder().limit(0).build(); + + assertFalse(params.isLimitOutOfBounds()); + } + + @Test + void testIsLimitOutOfBounds_NullFalse() { + DataExportParams params = DataExportParams.builder().build(); + + assertFalse(params.isLimitOutOfBounds()); + } + + @Test + void testIsOrgUnitGroupsOverSpecified_GroupsAndDescendantsTrue() { + DataExportParams params = + DataExportParams.builder() + .organisationUnitGroups(List.of(UID.generate())) + .includeDescendants(true) + .build(); + + assertTrue(params.isOrgUnitGroupsOverSpecified()); + } + + @Test + void testIsOrgUnitGroupsOverSpecified_GroupsWithoutDescendantsFalse() { + DataExportParams params = + DataExportParams.builder().organisationUnitGroups(List.of(UID.generate())).build(); + + assertFalse(params.isOrgUnitGroupsOverSpecified()); + } + + @Test + void testIsOrgUnitGroupsOverSpecified_DescendantsWithoutGroupsFalse() { + DataExportParams params = DataExportParams.builder().includeDescendants(true).build(); + + assertFalse(params.isOrgUnitGroupsOverSpecified()); + } } From 877bfe81cb378343f4f56cd362ffdbf3c2adebee Mon Sep 17 00:00:00 2001 From: Jason Pickering Date: Thu, 16 Jul 2026 12:23:30 +0200 Subject: [PATCH 2/2] test: cover startDate == endDate boundary for isDateRangeOutOfBounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Date.after() is strict (>), so a single-instant/single-day range (startDate == endDate) is currently treated as valid, not out of bounds. This boundary — the exact line between valid and invalid — wasn't covered by the earlier StartBeforeEndFalse/StartAfterEndTrue tests. Verified via mutation testing: swapped the strict startDate.after(endDate) check for an inclusive !startDate.before(endDate) in DataExportParams, confirmed only the new test failed (all 33 others stayed green), then restored (net diff to DataExportParams.java is zero). Co-Authored-By: Claude Sonnet 5 --- .../org/hisp/dhis/datavalue/DataExportParamsTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java index 3866ec00ac14..37a5d5ec651a 100644 --- a/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java +++ b/dhis-2/dhis-api/src/test/java/org/hisp/dhis/datavalue/DataExportParamsTest.java @@ -264,6 +264,16 @@ void testIsDateRangeOutOfBounds_StartDateOnlyFalse() { assertFalse(params.isDateRangeOutOfBounds()); } + @Test + void testIsDateRangeOutOfBounds_StartEqualsEndFalse() { + // Date.after() is strict (>), so a single-instant/single-day range (start == end) is a valid, + // zero-width range, not an error. + DataExportParams params = + DataExportParams.builder().startDate(new Date(1)).endDate(new Date(1)).build(); + + assertFalse(params.isDateRangeOutOfBounds()); + } + @Test void testIsLimitOutOfBounds_NegativeTrue() { DataExportParams params = DataExportParams.builder().limit(-1).build();