From 2d0333b143e6ae47ea46b5bd393b53f158a3af26 Mon Sep 17 00:00:00 2001 From: David Mackessy <131455290+david-mackessy@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:15:07 +0000 Subject: [PATCH] feat: Only allow duplicate Category Option Combos for merge [DHIS2-18838] (#19857) * feat: Start new validation for COC merge [DHIS2-18838] * feat: Add validation to restrict merges [DHIS2-18838] * WIP handling duplicate coc refs natively * feat: Int tests working for duplicate COC * feat: Update e2e tests * feat: Add unit tests for duplicate logic * feat: Update removal technique for CO and CC. No SQL required. * feat: clean up and javadoc * feat: Amend error code --- .../dhis/category/CategoryComboStore.java | 12 - .../dhis/category/CategoryOptionStore.java | 11 - .../org/hisp/dhis/feedback/ErrorCode.java | 4 + .../CategoryOptionComboMergeService.java | 26 +- ...tadataCategoryOptionComboMergeHandler.java | 44 +- .../CategoryOptionComboMergeServiceTest.java | 186 ++++ .../HibernateCategoryComboStore.java | 16 - .../HibernateCategoryOptionComboStore.java | 4 +- .../HibernateCategoryOptionStore.java | 15 - .../merge/CategoryOptionComboMergeTest.java | 371 +++----- .../dhis/category/CategoryComboStoreTest.java | 112 --- .../category/CategoryOptionStoreTest.java | 113 --- .../CategoryOptionComboMergeServiceTest.java | 797 ++++++++---------- 13 files changed, 699 insertions(+), 1012 deletions(-) create mode 100644 dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeServiceTest.java delete mode 100644 dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryComboStoreTest.java delete mode 100644 dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionStoreTest.java diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryComboStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryComboStore.java index 1eb0aa76c45d..c21d38bc9b40 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryComboStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryComboStore.java @@ -27,25 +27,13 @@ */ package org.hisp.dhis.category; -import java.util.Collection; import java.util.List; -import javax.annotation.Nonnull; import org.hisp.dhis.common.DataDimensionType; import org.hisp.dhis.common.IdentifiableObjectStore; -import org.hisp.dhis.common.UID; /** * @author Lars Helge Overland */ public interface CategoryComboStore extends IdentifiableObjectStore { List getCategoryCombosByDimensionType(DataDimensionType dataDimensionType); - - /** - * Retrieve all {@link CategoryCombo}s with {@link CategoryOptionCombo} {@link UID}s - * - * @param uids {@link CategoryOptionCombo} {@link UID}s - * @return {@link CategoryCombo}s with references to {@link CategoryOptionCombo} {@link UID}s - * passed in - */ - List getByCategoryOptionCombo(@Nonnull Collection uids); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionStore.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionStore.java index 63097c630f88..48e546efa7cc 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionStore.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/category/CategoryOptionStore.java @@ -27,10 +27,8 @@ */ package org.hisp.dhis.category; -import java.util.Collection; import java.util.List; import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; import org.hisp.dhis.common.IdentifiableObjectStore; import org.hisp.dhis.common.UID; import org.hisp.dhis.user.UserDetails; @@ -49,13 +47,4 @@ public interface CategoryOptionStore extends IdentifiableObjectStore getCategoryOptions(Category category); List getDataWriteCategoryOptions(Category category, UserDetails userDetails); - - /** - * Retrieve all {@link CategoryOption}s with {@link CategoryOptionCombo} {@link UID}s - * - * @param uids {@link CategoryOptionCombo} {@link UID}s - * @return {@link CategoryOption}s with references to {@link CategoryOptionCombo} {@link UID}s - * passed in - */ - List getByCategoryOptionCombo(@Nonnull Collection uids); } diff --git a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java index ee9c28317ca4..ae2dac150de3 100644 --- a/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java +++ b/dhis-2/dhis-api/src/main/java/org/hisp/dhis/feedback/ErrorCode.java @@ -102,6 +102,10 @@ public enum ErrorCode { E1533("{0} {1} does not exist: `{2}`"), E1534("dataMergeStrategy field must be specified. With value `DISCARD` or `LAST_UPDATED`"), + /* CategoryOptionCombo merge */ + E1540( + "CategoryOptionCombos must be duplicates (same cat combo, same cat options, different UID) in order to merge"), + /* DataElement merge */ E1550("All source ValueTypes must match target ValueType: `{0}`. Other ValueTypes found: `{1}`"), E1551( diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeService.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeService.java index c99ad4f42131..c85734458abf 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeService.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeService.java @@ -73,9 +73,29 @@ public MergeRequest validate(@Nonnull MergeParams params, @Nonnull MergeReport m if (params.getDataMergeStrategy() == null) { mergeReport.addErrorMessage(new ErrorMessage(ErrorCode.E1534)); } + + if (mergeReport.hasErrorMessages()) return request; + + // only allow merge if COCs are duplicate + List sources = + categoryService.getCategoryOptionCombosByUid(request.getSources()); + CategoryOptionCombo target = + categoryService.getCategoryOptionCombo(request.getTarget().getValue()); + if (!catOptCombosAreDuplicates(sources, target)) { + mergeReport.addErrorMessage(new ErrorMessage(ErrorCode.E1540)); + } return request; } + protected static boolean catOptCombosAreDuplicates( + List sources, CategoryOptionCombo target) { + boolean allSourcesEqualTarget = sources.stream().allMatch(source -> source.equals(target)); + boolean allSourceUidsAreDifferentThanTarget = + sources.stream().noneMatch(source -> source.getUid().equals(target.getUid())); + + return allSourcesEqualTarget && allSourceUidsAreDifferentThanTarget; + } + @Override public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mergeReport) { log.info("Performing CategoryOptionCombo merge"); @@ -105,8 +125,8 @@ public MergeReport merge(@Nonnull MergeRequest request, @Nonnull MergeReport mer private void handleDeleteSources(List sources, MergeReport mergeReport) { log.info("Deleting source CategoryOptionCombos"); for (CategoryOptionCombo source : sources) { - mergeReport.addDeletedSource(source.getUid()); categoryService.deleteCategoryOptionCombo(source); + mergeReport.addDeletedSource(source.getUid()); } } @@ -114,8 +134,8 @@ private void handleDeleteSources(List sources, MergeReport private void initMergeHandlers() { metadataMergeHandlers = List.of( - metadataMergeHandler::handleCategoryOptions, - metadataMergeHandler::handleCategoryCombos, + (sources, target) -> metadataMergeHandler.handleCategoryOptions(sources), + (sources, target) -> metadataMergeHandler.handleCategoryCombos(), metadataMergeHandler::handlePredictors, metadataMergeHandler::handleDataElementOperands, metadataMergeHandler::handleMinMaxDataElements, diff --git a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/MetadataCategoryOptionComboMergeHandler.java b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/MetadataCategoryOptionComboMergeHandler.java index b32b36aa5fde..aa65138abf7f 100644 --- a/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/MetadataCategoryOptionComboMergeHandler.java +++ b/dhis-2/dhis-services/dhis-service-administration/src/main/java/org/hisp/dhis/merge/category/optioncombo/MetadataCategoryOptionComboMergeHandler.java @@ -29,13 +29,13 @@ import java.util.List; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hisp.dhis.category.CategoryCombo; -import org.hisp.dhis.category.CategoryComboStore; +import org.hisp.dhis.category.CategoryComboDeletionHandler; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; -import org.hisp.dhis.category.CategoryOptionStore; import org.hisp.dhis.common.BaseIdentifiableObject; import org.hisp.dhis.common.UID; import org.hisp.dhis.datadimensionitem.DataDimensionItemStore; @@ -61,8 +61,6 @@ @RequiredArgsConstructor public class MetadataCategoryOptionComboMergeHandler { - private final CategoryOptionStore categoryOptionStore; - private final CategoryComboStore categoryComboStore; private final DataElementOperandStore dataElementOperandStore; private final DataDimensionItemStore dataDimensionItemStore; private final MinMaxDataElementStore minMaxDataElementStore; @@ -72,41 +70,23 @@ public class MetadataCategoryOptionComboMergeHandler { private final ExpressionStore expressionStore; /** - * Remove sources from {@link CategoryOption} and add target to {@link CategoryOption} + * Remove all {@link CategoryOption}s from source {@link CategoryOptionCombo}s * * @param sources to be removed - * @param target to add */ - public void handleCategoryOptions(List sources, CategoryOptionCombo target) { - log.info("Merging source category options"); - List categoryOptions = - categoryOptionStore.getByCategoryOptionCombo( - UID.of(sources.stream().map(BaseIdentifiableObject::getUid).toList())); - - categoryOptions.forEach( - co -> { - co.addCategoryOptionCombo(target); - co.removeCategoryOptionCombos(sources); - }); + public void handleCategoryOptions(@Nonnull List sources) { + for (CategoryOptionCombo coc : sources) coc.removeAllCategoryOptions(); + log.info("Removed all category option references for source category option combos"); } /** - * Remove sources from {@link CategoryCombo} and add target to {@link CategoryCombo} - * - * @param sources to be removed - * @param target to add + * Although nothing is done but log in this method, it is worth having to expose how the {@link + * CategoryCombo}s refs are being handled and also show that this has been thought about. This + * helps keep it up front and not hidden. The easiest way to remove this relationship is to let + * the {@link CategoryComboDeletionHandler} look after it. */ - public void handleCategoryCombos(List sources, CategoryOptionCombo target) { - log.info("Merging source category combos"); - List categoryCombos = - categoryComboStore.getByCategoryOptionCombo( - UID.of(sources.stream().map(BaseIdentifiableObject::getUid).toList())); - - categoryCombos.forEach( - cc -> { - cc.addCategoryOptionCombo(target); - cc.removeCategoryOptionCombos(sources); - }); + public void handleCategoryCombos() { + log.info("Category combo references will be removed when the category option combo is deleted"); } /** diff --git a/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeServiceTest.java b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeServiceTest.java new file mode 100644 index 000000000000..5e1eddc418f6 --- /dev/null +++ b/dhis-2/dhis-services/dhis-service-administration/src/test/java/org/hisp/dhis/merge/category/optioncombo/CategoryOptionComboMergeServiceTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2004-2025, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.hisp.dhis.merge.category.optioncombo; + +import static org.hisp.dhis.merge.category.optioncombo.CategoryOptionComboMergeService.catOptCombosAreDuplicates; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.Set; +import org.hisp.dhis.category.CategoryCombo; +import org.hisp.dhis.category.CategoryOption; +import org.hisp.dhis.category.CategoryOptionCombo; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +@TestInstance(Lifecycle.PER_CLASS) +class CategoryOptionComboMergeServiceTest { + + private CategoryCombo cc1; + private CategoryCombo cc2; + private CategoryOption co1; + private CategoryOption co2; + private CategoryOption co3; + private CategoryOptionCombo coc1; + private CategoryOptionCombo coc2; + private CategoryOptionCombo coc3; + + @BeforeAll + public void setup() { + cc1 = new CategoryCombo(); + cc1.setName("cc1"); + cc1.setUid("UIDcatcom01"); + cc1.setCode("code1"); + + cc2 = new CategoryCombo(); + cc2.setName("cc2"); + cc2.setUid("UIDcatcom02"); + cc2.setCode("code2"); + + co1 = new CategoryOption(); + co1.setName("co1"); + co1.setUid("UIDcatopt01"); + co1.setCode("co1"); + co1.setShortName("co1"); + co1.setDescription("co1"); + + co2 = new CategoryOption(); + co2.setName("co2"); + co2.setUid("UIDcatopt02"); + co2.setCode("co2"); + co2.setShortName("co2"); + co2.setDescription("co2"); + + co3 = new CategoryOption(); + co3.setName("co3"); + co3.setUid("UIDcatopt03"); + co3.setCode("co3"); + co3.setShortName("co3"); + co3.setDescription("co3"); + + coc1 = new CategoryOptionCombo(); + coc1.setName("coc1"); + coc1.setUid("UIDcoc00001"); + + coc2 = new CategoryOptionCombo(); + coc2.setName("coc2"); + coc2.setUid("UIDcoc00002"); + + coc3 = new CategoryOptionCombo(); + coc3.setName("coc3"); + coc3.setUid("UIDcoc00003"); + } + + @Test + @DisplayName("COC with same CC and COs are detected as duplicates") + void sameCcSameCoTest() { + coc1.setCategoryCombo(cc1); + coc2.setCategoryCombo(cc1); + coc3.setCategoryCombo(cc1); + coc1.setCategoryOptions(Set.of(co1, co2)); + coc2.setCategoryOptions(Set.of(co1, co2)); + coc3.setCategoryOptions(Set.of(co1, co2)); + + assertTrue(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } + + @Test + @DisplayName("COC with same CC and different number of COs are not detected as duplicates") + void sameCcDiffCoTest() { + coc1.setCategoryCombo(cc1); + coc2.setCategoryCombo(cc1); + coc3.setCategoryCombo(cc1); + coc1.setCategoryOptions(Set.of(co1, co2)); + coc2.setCategoryOptions(Set.of(co1)); + coc3.setCategoryOptions(Set.of(co1, co2)); + + assertFalse(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } + + @Test + @DisplayName("COC with different CC and same COs are not detected as duplicates") + void diffCcSameCoTest() { + coc1.setCategoryCombo(cc1); + coc2.setCategoryCombo(cc2); + coc3.setCategoryCombo(cc1); + coc1.setCategoryOptions(Set.of(co1, co2)); + coc2.setCategoryOptions(Set.of(co1, co2)); + coc3.setCategoryOptions(Set.of(co1, co2)); + + assertFalse(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } + + @Test + @DisplayName("COC with different CC and different COs are not detected as duplicates") + void diffCcDiffCoTest() { + coc1.setCategoryCombo(cc1); + coc2.setCategoryCombo(cc2); + coc3.setCategoryCombo(cc1); + coc1.setCategoryOptions(Set.of(co2)); + coc2.setCategoryOptions(Set.of(co1, co2)); + coc3.setCategoryOptions(Set.of(co1)); + + assertFalse(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } + + @Test + @DisplayName("COC with different UIDs, same CC and same COs are detected as duplicates") + void diffUidsCcCoTest() { + coc1.setCategoryCombo(cc1); + coc1.setUid("diff"); + coc2.setCategoryCombo(cc1); + coc2.setUid("other"); + coc3.setCategoryCombo(cc1); + coc3.setUid("more"); + coc1.setCategoryOptions(Set.of(co1, co2)); + coc2.setCategoryOptions(Set.of(co1, co2)); + coc3.setCategoryOptions(Set.of(co1, co2)); + + assertTrue(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } + + @Test + @DisplayName("COC with same UIDs, same CC and same COs are not detected as duplicates") + void sameUidsCcCoTest() { + coc1.setCategoryCombo(cc1); + coc1.setUid("same"); + coc2.setCategoryCombo(cc1); + coc2.setUid("same"); + coc3.setCategoryCombo(cc1); + coc3.setUid("same"); + coc1.setCategoryOptions(Set.of(co1, co2)); + coc2.setCategoryOptions(Set.of(co1, co2)); + coc3.setCategoryOptions(Set.of(co1, co2)); + + assertFalse(catOptCombosAreDuplicates(List.of(coc1, coc2), coc3)); + } +} diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryComboStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryComboStore.java index 65f215f77fb7..4041df7038e5 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryComboStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryComboStore.java @@ -29,13 +29,10 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; -import java.util.Collection; import java.util.List; -import javax.annotation.Nonnull; import org.hisp.dhis.category.CategoryCombo; import org.hisp.dhis.category.CategoryComboStore; import org.hisp.dhis.common.DataDimensionType; -import org.hisp.dhis.common.UID; import org.hisp.dhis.common.hibernate.HibernateIdentifiableObjectStore; import org.hisp.dhis.security.acl.AclService; import org.springframework.context.ApplicationEventPublisher; @@ -66,17 +63,4 @@ public List getCategoryCombosByDimensionType(DataDimensionType da .addPredicate(root -> builder.equal(root.get("dataDimensionType"), dataDimensionType)) .addPredicate(root -> builder.equal(root.get("name"), "default"))); } - - @Override - public List getByCategoryOptionCombo(@Nonnull Collection uids) { - if (uids.isEmpty()) return List.of(); - return getQuery( - """ - select distinct cc from CategoryCombo cc - join cc.optionCombos coc - where coc.uid in :uids - """) - .setParameter("uids", UID.toValueList(uids)) - .getResultList(); - } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java index d596cb1e07d9..f2c380bacb59 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionComboStore.java @@ -152,8 +152,8 @@ public List getCategoryOptionCombosByCategoryOption( if (categoryOptions.isEmpty()) return List.of(); return getQuery( """ - select distinct coc from CategoryOption co - join co.categoryOptionCombos coc + select distinct coc from CategoryOptionCombo coc + join coc.categoryOptions co where co.uid in :categoryOptions """, CategoryOptionCombo.class) diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionStore.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionStore.java index 6528ad1e6c50..63937a9ced9a 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionStore.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/category/hibernate/HibernateCategoryOptionStore.java @@ -29,9 +29,7 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.criteria.CriteriaBuilder; -import java.util.Collection; import java.util.List; -import javax.annotation.Nonnull; import org.hisp.dhis.category.Category; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionStore; @@ -95,17 +93,4 @@ public List getDataWriteCategoryOptions( .addPredicate( root -> builder.equal(root.join("categories").get("id"), category.getId()))); } - - @Override - public List getByCategoryOptionCombo(@Nonnull Collection uids) { - if (uids.isEmpty()) return List.of(); - return getQuery( - """ - select distinct co from CategoryOption co - join co.categoryOptionCombos coc - where coc.uid in :uids - """) - .setParameter("uids", UID.toValueList(uids)) - .getResultList(); - } } diff --git a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionComboMergeTest.java b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionComboMergeTest.java index 2d6b53290a39..085d88579094 100644 --- a/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionComboMergeTest.java +++ b/dhis-2/dhis-test-e2e/src/test/java/org/hisp/dhis/merge/CategoryOptionComboMergeTest.java @@ -34,6 +34,7 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -81,7 +82,6 @@ class CategoryOptionComboMergeTest extends ApiTest { private String sourceUid2; private String targetUid; private String randomCocUid1; - private String randomCocUid2; private String mergeUserId; @BeforeAll @@ -110,6 +110,37 @@ public void before() { public void setup() { loginActions.loginAsSuperUser(); setupMetadata(); + maintenanceApiActions + .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) + .validateStatus(204); + targetUid = getCocWithOptions("1", "3"); + sourceUid1 = createDuplicateCoc("dupl1cate"); + sourceUid2 = createDuplicateCoc("dupli2ate"); + + int cocTotal = + categoryOptionComboApiActions + .get("?filter=categoryCombo.id:in:[CatComUid01]") + .validate() + .extract() + .jsonPath() + .getInt("pager.total"); + assertEquals(6, cocTotal); + } + + public void checkCocCountAfter(int expected) { + loginActions.loginAsSuperUser(); + maintenanceApiActions + .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) + .validateStatus(204); + + int cocTotal = + categoryOptionComboApiActions + .get("?filter=categoryCombo.id:in:[CatComUid01]") + .validate() + .extract() + .jsonPath() + .getInt("pager.total"); + assertEquals(expected, cocTotal); } @AfterAll @@ -124,30 +155,43 @@ public void resetSuperUserOrgUnit() { "YuQRtpLP10I"); } + private String createDuplicateCoc(String name) { + return categoryOptionComboApiActions + .post( + """ + { + "name": "%s", + "categoryCombo": { + "id": "CatComUid01" + }, + "categoryOptions": [ + { + "id": "CatOptUid01" + }, + { + "id": "CatOptUid03" + } + ] + } + """ + .formatted(name)) + .validateStatus(201) + .extractUid(); + } + @Test @DisplayName( "Valid CategoryOptionCombo merge completes successfully with all source CategoryOptionCombo refs replaced with target CategoryOptionCombo") void validCategoryOptionComboMergeTest() { - // given - // generate category option combos - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1B", "2B"); - targetUid = getCocWithOptions("3A", "4B"); - // confirm state before merge ValidatableResponse preMergeState = categoryOptionComboApiActions.get(targetUid).validateStatus(200).validate(); preMergeState - .body("categoryCombo", hasEntry("id", "CatComUid02")) + .body("categoryCombo", hasEntry("id", "CatComUid01")) .body("categoryOptions", hasSize(equalTo(2))) - .body("categoryOptions", hasItem(hasEntry("id", "CatOptUid4B"))) - .body("categoryOptions", hasItem(hasEntry("id", "CatOptUid3A"))); + .body("categoryOptions", hasItem(hasEntry("id", "CatOptUid01"))) + .body("categoryOptions", hasItem(hasEntry("id", "CatOptUid03"))); String dataElement = setupDataElement("test de 1"); // import visualization to persist data dimension item which has ref to source coc @@ -170,23 +214,22 @@ void validCategoryOptionComboMergeTest() { .body("response.mergeReport.mergeType", equalTo("CategoryOptionCombo")) .body("response.mergeReport.sourcesDeleted", hasItems(sourceUid1, sourceUid2)); + // sources don't exist categoryOptionComboApiActions.get(sourceUid1).validateStatus(404); categoryOptionComboApiActions.get(sourceUid2).validateStatus(404); + + // target has expected state ValidatableResponse postMergeState = categoryOptionComboApiActions.get(targetUid).validateStatus(200).validate(); postMergeState - .body("categoryCombo", hasEntry("id", "CatComUid02")) - .body("categoryOptions", hasSize(equalTo(6))) + .body("categoryCombo", hasEntry("id", "CatComUid01")) + .body("categoryOptions", hasSize(equalTo(2))) .body( "categoryOptions", - hasItems( - hasEntry("id", "CatOptUid1A"), - hasEntry("id", "CatOptUid2B"), - hasEntry("id", "CatOptUid3A"), - hasEntry("id", "CatOptUid4B"), - hasEntry("id", "CatOptUid2A"), - hasEntry("id", "CatOptUid1B"))); + hasItems(hasEntry("id", "CatOptUid01"), hasEntry("id", "CatOptUid03"))); + + checkCocCountAfter(4); } @Test @@ -194,17 +237,7 @@ void validCategoryOptionComboMergeTest() { "CategoryOptionCombo merge completes successfully with DataValues (cat opt combo) handled correctly") void cocMergeDataValuesTest() { // Given - // Generate category option combos - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // Get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1A", "2B"); - targetUid = getCocWithOptions("3A", "4A"); - randomCocUid1 = getCocWithOptions("1B", "2A"); - randomCocUid2 = getCocWithOptions("1B", "2B"); + randomCocUid1 = getCocWithOptions("2", "4"); addOrgUnitAccessForUser(loginActions.getLoggedInUserId(), "OrgUnitUid1"); addOrgUnitAccessForUser(mergeUserId, "OrgUnitUid1"); @@ -226,7 +259,7 @@ void cocMergeDataValuesTest() { .validateStatus(200) .validate(); - preMergeState.body("dataValues", hasSize(14)); + preMergeState.body("dataValues", hasSize(13)); Set uniqueDates = new HashSet<>(preMergeState.extract().jsonPath().getList("dataValues.lastUpdated")); assertTrue(uniqueDates.size() > 1, "There should be more than 1 unique date present"); @@ -261,7 +294,7 @@ void cocMergeDataValuesTest() { .validateStatus(200) .validate(); - postMergeState.body("dataValues", hasSize(8)); + postMergeState.body("dataValues", hasSize(7)); // Check for expected values List datValues = postMergeState.extract().jsonPath().getList("dataValues.value"); @@ -282,6 +315,8 @@ void cocMergeDataValuesTest() { assertTrue(dvCocs.contains(targetUid), "Target COC is present"); assertFalse(dvCocs.contains(sourceUid1), "Source COC 1 should not be present"); assertFalse(dvCocs.contains(sourceUid2), "Source COC 2 should not be present"); + + checkCocCountAfter(4); } @Test @@ -289,17 +324,7 @@ void cocMergeDataValuesTest() { "CategoryOptionCombo merge completes successfully with DataValues (attr opt combo) handled correctly") void aocMergeDataValuesTest() { // Given - // Generate category option combos - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // Get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1A", "2B"); - targetUid = getCocWithOptions("3A", "4A"); - randomCocUid1 = getCocWithOptions("1B", "2A"); - randomCocUid2 = getCocWithOptions("1B", "2B"); + randomCocUid1 = getCocWithOptions("2", "4"); addOrgUnitAccessForUser(loginActions.getLoggedInUserId(), "OrgUnitUid2"); addOrgUnitAccessForUser(mergeUserId, "OrgUnitUid2"); @@ -321,7 +346,7 @@ void aocMergeDataValuesTest() { .validateStatus(200) .validate(); - preMergeState.body("dataValues", hasSize(14)); + preMergeState.body("dataValues", hasSize(13)); Set uniqueDates = new HashSet<>(preMergeState.extract().jsonPath().getList("dataValues.lastUpdated")); assertTrue(uniqueDates.size() > 1, "There should be more than 1 unique date present"); @@ -378,22 +403,24 @@ void aocMergeDataValuesTest() { assertTrue(dvAocs.contains(targetUid), "Target COC is present"); assertFalse(dvAocs.contains(sourceUid1), "Source COC 1 should not be present"); assertFalse(dvAocs.contains(sourceUid2), "Source COC 2 should not be present"); + + checkCocCountAfter(4); } private void addDataValuesCoc() { dataValueSetActions .post( - dataValueSetImportCoc(sourceUid1, sourceUid2, targetUid, randomCocUid1, randomCocUid2), + dataValueSetImportCoc(sourceUid1, sourceUid2, targetUid, randomCocUid1), getDataValueQueryParams()) .validateStatus(200) .validate() - .body("response.importCount.imported", equalTo(14)); + .body("response.importCount.imported", equalTo(13)); } private void addDataValuesAoc() { dataValueSetActions .post( - dataValueSetImportAoc(sourceUid1, sourceUid2, targetUid, randomCocUid1, randomCocUid2), + dataValueSetImportAoc(sourceUid1, sourceUid2, targetUid, randomCocUid1), getDataValueQueryParams()) .validateStatus(200); } @@ -530,21 +557,21 @@ void categoryOptionComboMergeNoRequiredAuthTest() { "message", equalTo( "Access is denied, requires one Authority from [F_CATEGORY_OPTION_COMBO_MERGE]")); + + cleanUpDuplicates(); + } + + private void cleanUpDuplicates() { + loginActions.loginAsSuperUser(); + categoryOptionComboApiActions.delete(sourceUid1).validateStatus(200); + categoryOptionComboApiActions.delete(sourceUid2).validateStatus(200); + checkCocCountAfter(4); } @Test @DisplayName("Category Option Combo merge fails when min max DE DB unique key constraint met") void dbConstraintMinMaxTest() { // given - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1B", "2B"); - targetUid = getCocWithOptions("3A", "4B"); - String dataElement = setupDataElement("DE test 2"); setupMinMaxDataElements(sourceUid1, sourceUid2, targetUid, dataElement); @@ -564,6 +591,8 @@ void dbConstraintMinMaxTest() { .body("status", equalTo("ERROR")) .body("message", containsString("ERROR: duplicate key value violates unique constraint")) .body("message", containsString("minmaxdataelement_unique_key")); + + cleanUpDuplicates(); } @Test @@ -571,15 +600,6 @@ void dbConstraintMinMaxTest() { "Indicators with COC source refs in their numerator or denominator are updated with target COC ref") void indicatorsNumeratorDenominatorTest() { // given - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1B", "2B"); - targetUid = getCocWithOptions("3A", "4B"); - // indicators with mix of source COC in numerator, denominator String indicatorType = setupIndicatorType("1"); String indicator1 = setupIndicator("1", indicatorType, sourceUid1, "num2", "denom1", "denom2"); @@ -609,6 +629,8 @@ void indicatorsNumeratorDenominatorTest() { checkIndicatorValues(3, indicator3, targetUid, targetUid, targetUid, targetUid); checkIndicatorValues(4, indicator4, targetUid, "num2", targetUid, "denom2"); checkIndicatorValues(5, indicator5, "randomUID1", "randomUID2", "randomUID3", "randomUID4"); + + checkCocCountAfter(4); } @Test @@ -616,15 +638,6 @@ void indicatorsNumeratorDenominatorTest() { "Expressions with COC source refs in their expression are updated with target COC ref") void expressionTest() { // given - maintenanceApiActions - .post("categoryOptionComboUpdate", new QueryParamsBuilder().build()) - .validateStatus(204); - - // get cat opt combo uids for sources and target, after generating - sourceUid1 = getCocWithOptions("1A", "2A"); - sourceUid2 = getCocWithOptions("1B", "2B"); - targetUid = getCocWithOptions("3A", "4B"); - // indicators with mix of source COC in numerator, denominator String validationRule1 = setupExpressionInValidationRule("1", sourceUid1, "leftSide2", "rightSide1", "rightSide2"); @@ -656,6 +669,8 @@ void expressionTest() { checkExpressionValues(3, validationRule3, targetUid, targetUid, targetUid, "rightSide2"); checkExpressionValues(4, validationRule4, targetUid, "leftSide2", "rightSide1", "rightSide2"); checkExpressionValues(5, validationRule5, "leftSide1", "leftSide2", "rightSide1", "rightSide2"); + + checkCocCountAfter(4); } private void checkIndicatorValues( @@ -810,7 +825,6 @@ private JsonObject getMergeBody(String dataMergeStrategy) { } private String getCocWithOptions(String co1, String co2) { - return categoryOptionComboApiActions .get( new QueryParamsBuilder() @@ -827,61 +841,9 @@ private String metadata() { { "categoryOptions": [ { - "id": "CatOptUid1A", - "name": "cat opt 1A", - "shortName": "cat opt 1A", - "organisationUnits": [ - { - "id": "OrgUnitUid1" - }, - { - "id": "OrgUnitUid2" - } - ] - }, - { - "id": "CatOptUid1B", - "name": "cat opt 1B", - "shortName": "cat opt 1B", - "organisationUnits": [ - { - "id": "OrgUnitUid1" - }, - { - "id": "OrgUnitUid2" - } - ] - }, - { - "id": "CatOptUid2A", - "name": "cat opt 2A", - "shortName": "cat opt 2A", - "organisationUnits": [ - { - "id": "OrgUnitUid1" - }, - { - "id": "OrgUnitUid2" - } - ] - }, - { - "id": "CatOptUid2B", - "name": "cat opt 2B", - "shortName": "cat opt 2B", - "organisationUnits": [ - { - "id": "OrgUnitUid1" - }, - { - "id": "OrgUnitUid2" - } - ] - }, - { - "id": "CatOptUid3A", - "name": "cat opt 3A", - "shortName": "cat opt 3A", + "id": "CatOptUid01", + "name": "cat opt 1", + "shortName": "cat opt 1", "organisationUnits": [ { "id": "OrgUnitUid1" @@ -892,9 +854,9 @@ private String metadata() { ] }, { - "id": "CatOptUid3B", - "name": "cat opt 3B", - "shortName": "cat opt 3B", + "id": "CatOptUid02", + "name": "cat opt 2", + "shortName": "cat opt 2", "organisationUnits": [ { "id": "OrgUnitUid1" @@ -905,9 +867,9 @@ private String metadata() { ] }, { - "id": "CatOptUid4A", - "name": "cat opt 4A", - "shortName": "cat opt 4A", + "id": "CatOptUid03", + "name": "cat opt 3", + "shortName": "cat opt 3", "organisationUnits": [ { "id": "OrgUnitUid1" @@ -918,9 +880,9 @@ private String metadata() { ] }, { - "id": "CatOptUid4B", - "name": "cat opt 4B", - "shortName": "cat opt 4B", + "id": "CatOptUid04", + "name": "cat opt 4", + "shortName": "cat opt 4", "organisationUnits": [ { "id": "OrgUnitUid1" @@ -939,10 +901,10 @@ private String metadata() { "dataDimensionType": "DISAGGREGATION", "categoryOptions": [ { - "id": "CatOptUid1A" + "id": "CatOptUid01" }, { - "id": "CatOptUid1B" + "id": "CatOptUid02" } ] }, @@ -953,38 +915,10 @@ private String metadata() { "dataDimensionType": "DISAGGREGATION", "categoryOptions": [ { - "id": "CatOptUid2A" - }, - { - "id": "CatOptUid2B" - } - ] - }, - { - "id": "CategoUid03", - "name": "cat 3", - "shortName": "cat 3", - "dataDimensionType": "DISAGGREGATION", - "categoryOptions": [ - { - "id": "CatOptUid3A" - }, - { - "id": "CatOptUid3B" - } - ] - }, - { - "id": "CategoUid04", - "name": "cat 4", - "shortName": "cat 4", - "dataDimensionType": "DISAGGREGATION", - "categoryOptions": [ - { - "id": "CatOptUid4A" + "id": "CatOptUid03" }, { - "id": "CatOptUid4B" + "id": "CatOptUid04" } ] } @@ -1035,10 +969,10 @@ private String metadata() { "dataDimensionType": "DISAGGREGATION", "categoryOptions": [ { - "id": "CatOptUid1A" + "id": "CatOptUid01" }, { - "id": "CatOptUid1B" + "id": "CatOptUid02" } ] }, @@ -1049,38 +983,10 @@ private String metadata() { "dataDimensionType": "DISAGGREGATION", "categoryOptions": [ { - "id": "CatOptUid2A" + "id": "CatOptUid03" }, { - "id": "CatOptUid2B" - } - ] - }, - { - "id": "CatOptGrp03", - "name": "cog 3", - "shortName": "cog 3", - "dataDimensionType": "DISAGGREGATION", - "categoryOptions": [ - { - "id": "CatOptUid3A" - }, - { - "id": "CatOptUid3B" - } - ] - }, - { - "id": "CatOptGrp04", - "name": "cog 4", - "shortName": "cog 4", - "dataDimensionType": "DISAGGREGATION", - "categoryOptions": [ - { - "id": "CatOptUid4A" - }, - { - "id": "CatOptUid4B" + "id": "CatOptUid04" } ] } @@ -1098,19 +1004,6 @@ private String metadata() { "id": "CategoUid02" } ] - }, - { - "id": "CatComUid02", - "name": "cat combo 2", - "dataDimensionType": "DISAGGREGATION", - "categories": [ - { - "id": "CategoUid03" - }, - { - "id": "CategoUid04" - } - ] } ], "dataElements": [ @@ -1128,11 +1021,7 @@ private String metadata() { } private JsonObject dataValueSetImportCoc( - String source1Coc, - String source2Coc, - String targetCoc, - String randomCoc1, - String randomCoc2) { + String source1Coc, String source2Coc, String targetCoc, String randomCoc1) { return JsonParserUtils.toJsonObject( """ { @@ -1253,15 +1142,6 @@ private JsonObject dataValueSetImportCoc( "attributeOptionCombo": "HllvX50cXC0", "value": "random 1, DV 1 - not impacted", "comment": "random 1, DV 1 - not impacted" - }, - { - "dataElement": "deUid000001", - "period": "202408", - "orgUnit": "OrgUnitUid1", - "categoryOptionCombo": "%s", - "attributeOptionCombo": "HllvX50cXC0", - "value": "random 2, DV 2 - not impacted", - "comment": "random 2, DV 2 - not impacted" } ] } @@ -1279,16 +1159,11 @@ private JsonObject dataValueSetImportCoc( targetCoc, targetCoc, targetCoc, - randomCoc1, - randomCoc2)); + randomCoc1)); } private JsonObject dataValueSetImportAoc( - String source1Coc, - String source2Coc, - String targetCoc, - String randomCoc1, - String randomCoc2) { + String source1Coc, String source2Coc, String targetCoc, String randomCoc1) { return JsonParserUtils.toJsonObject( """ { @@ -1409,15 +1284,6 @@ private JsonObject dataValueSetImportAoc( "attributeOptionCombo": "%s", "value": "random 1, DV 1 - not impacted", "comment": "random 1, DV 1 - not impacted" - }, - { - "dataElement": "deUid000001", - "period": "202408", - "orgUnit": "OrgUnitUid2", - "categoryOptionCombo": "HllvX50cXC0", - "attributeOptionCombo": "%s", - "value": "random 2, DV 2 - not impacted", - "comment": "random 2, DV 2 - not impacted" } ] } @@ -1435,8 +1301,7 @@ private JsonObject dataValueSetImportAoc( targetCoc, targetCoc, targetCoc, - randomCoc1, - randomCoc2)); + randomCoc1)); } private JsonObject dataValueSetImportUpdateAoc( diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryComboStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryComboStoreTest.java deleted file mode 100644 index 3868569b7471..000000000000 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryComboStoreTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2004-2024, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.hisp.dhis.category; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.common.UID; -import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -@Transactional -class CategoryComboStoreTest extends PostgresIntegrationTestBase { - @Autowired private CategoryComboStore categoryComboStore; - - @Test - @DisplayName("Retrieving CategoryCombos by CategoryOptionCombos returns the expected entries") - void getCatOptionComboTest() { - // given - CategoryOption co1 = createCategoryOption("1A", CodeGenerator.generateUid()); - CategoryOption co2 = createCategoryOption("1B", CodeGenerator.generateUid()); - CategoryOption co3 = createCategoryOption("2A", CodeGenerator.generateUid()); - CategoryOption co4 = createCategoryOption("2B", CodeGenerator.generateUid()); - CategoryOption co5 = createCategoryOption("3A", CodeGenerator.generateUid()); - CategoryOption co6 = createCategoryOption("4A", CodeGenerator.generateUid()); - categoryService.addCategoryOption(co1); - categoryService.addCategoryOption(co2); - categoryService.addCategoryOption(co3); - categoryService.addCategoryOption(co4); - categoryService.addCategoryOption(co5); - categoryService.addCategoryOption(co6); - - Category c1 = createCategory('1', co1, co2); - Category c2 = createCategory('2', co3, co4); - Category c3 = createCategory('3', co5); - Category c4 = createCategory('4', co6); - categoryService.addCategory(c1); - categoryService.addCategory(c2); - categoryService.addCategory(c3); - categoryService.addCategory(c4); - - CategoryCombo cc1 = createCategoryCombo('1', c1, c2); - CategoryCombo cc2 = createCategoryCombo('2', c3, c4); - categoryService.addCategoryCombo(cc1); - categoryService.addCategoryCombo(cc2); - - categoryService.generateOptionCombos(cc1); - categoryService.generateOptionCombos(cc2); - - CategoryOptionCombo coc1 = getCocWithOptions("1A", "2B"); - CategoryOptionCombo coc2 = getCocWithOptions("2A", "1B"); - - // when - List catCombosByCategoryOptionCombo = - categoryComboStore.getByCategoryOptionCombo(UID.of(coc1.getUid(), coc2.getUid())); - - // then - assertEquals(1, catCombosByCategoryOptionCombo.size(), "1 CategoryCombo should be present"); - List categoryCombos = - catCombosByCategoryOptionCombo.stream().map(BaseIdentifiableObject::getUid).toList(); - - assertTrue( - categoryCombos.contains(cc1.getUid()), - "Retrieved CategoryCombo UID should equal the expected value"); - } - - private CategoryOptionCombo getCocWithOptions(String co1, String co2) { - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - return allCategoryOptionCombos.stream() - .filter( - coc -> { - List categoryOptions = - coc.getCategoryOptions().stream().map(BaseIdentifiableObject::getName).toList(); - return categoryOptions.containsAll(List.of(co1, co2)); - }) - .toList() - .get(0); - } -} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionStoreTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionStoreTest.java deleted file mode 100644 index b5b92e5e69fd..000000000000 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/category/CategoryOptionStoreTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2004-2024, University of Oslo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.hisp.dhis.category; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.List; -import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.common.CodeGenerator; -import org.hisp.dhis.common.UID; -import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.transaction.annotation.Transactional; - -@Transactional -class CategoryOptionStoreTest extends PostgresIntegrationTestBase { - @Autowired private CategoryOptionStore categoryOptionStore; - - @Test - @DisplayName("Retrieving CategoryOptions by CategoryOptionCombos returns the expected entries") - void getCatOptionComboTest() { - // given - CategoryOption co1 = createCategoryOption("1A", CodeGenerator.generateUid()); - CategoryOption co2 = createCategoryOption("1B", CodeGenerator.generateUid()); - CategoryOption co3 = createCategoryOption("2A", CodeGenerator.generateUid()); - CategoryOption co4 = createCategoryOption("2B", CodeGenerator.generateUid()); - CategoryOption co5 = createCategoryOption("3A", CodeGenerator.generateUid()); - CategoryOption co6 = createCategoryOption("4A", CodeGenerator.generateUid()); - categoryService.addCategoryOption(co1); - categoryService.addCategoryOption(co2); - categoryService.addCategoryOption(co3); - categoryService.addCategoryOption(co4); - categoryService.addCategoryOption(co5); - categoryService.addCategoryOption(co6); - - Category c1 = createCategory('1', co1, co2); - Category c2 = createCategory('2', co3, co4); - Category c3 = createCategory('3', co5); - Category c4 = createCategory('4', co6); - categoryService.addCategory(c1); - categoryService.addCategory(c2); - categoryService.addCategory(c3); - categoryService.addCategory(c4); - - CategoryCombo cc1 = createCategoryCombo('1', c1, c2); - CategoryCombo cc2 = createCategoryCombo('2', c3, c4); - categoryService.addCategoryCombo(cc1); - categoryService.addCategoryCombo(cc2); - - categoryService.generateOptionCombos(cc1); - categoryService.generateOptionCombos(cc2); - - CategoryOptionCombo coc1 = getCocWithOptions("1A", "2B"); - CategoryOptionCombo coc2 = getCocWithOptions("2A", "1B"); - - // when - List catOptionsByCategoryOptionCombo = - categoryOptionStore.getByCategoryOptionCombo(UID.of(coc1.getUid(), coc2.getUid())); - - // then - assertEquals(4, catOptionsByCategoryOptionCombo.size(), "4 CategoryOptions should be present"); - List categoryOptions = - catOptionsByCategoryOptionCombo.stream().map(BaseIdentifiableObject::getUid).toList(); - - assertTrue( - categoryOptions.containsAll( - List.of(co1.getUid(), co2.getUid(), co3.getUid(), co4.getUid())), - "Retrieved CategoryOption UIDs should have expected UIDs"); - } - - private CategoryOptionCombo getCocWithOptions(String co1, String co2) { - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - return allCategoryOptionCombos.stream() - .filter( - coc -> { - List categoryOptions = - coc.getCategoryOptions().stream().map(BaseIdentifiableObject::getName).toList(); - return categoryOptions.containsAll(List.of(co1, co2)); - }) - .toList() - .get(0); - } -} diff --git a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionComboMergeServiceTest.java b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionComboMergeServiceTest.java index e32e2b902301..5187fc934b23 100644 --- a/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionComboMergeServiceTest.java +++ b/dhis-2/dhis-test-integration/src/test/java/org/hisp/dhis/merge/category/CategoryOptionComboMergeServiceTest.java @@ -28,27 +28,27 @@ package org.hisp.dhis.merge.category; import static org.hisp.dhis.dataapproval.DataApprovalAction.APPROVE; +import static org.hisp.dhis.feedback.ErrorCode.E1540; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.hisp.dhis.audit.AuditOperationType; -import org.hisp.dhis.category.Category; import org.hisp.dhis.category.CategoryCombo; -import org.hisp.dhis.category.CategoryComboStore; +import org.hisp.dhis.category.CategoryManager; import org.hisp.dhis.category.CategoryOption; import org.hisp.dhis.category.CategoryOptionCombo; -import org.hisp.dhis.category.CategoryOptionStore; +import org.hisp.dhis.category.CategoryOptionComboStore; import org.hisp.dhis.category.CategoryService; import org.hisp.dhis.common.BaseIdentifiableObject; -import org.hisp.dhis.common.CodeGenerator; import org.hisp.dhis.common.IdentifiableObjectManager; import org.hisp.dhis.common.UID; import org.hisp.dhis.dataapproval.DataApproval; @@ -71,6 +71,7 @@ import org.hisp.dhis.datavalue.DataValueStore; import org.hisp.dhis.expression.Expression; import org.hisp.dhis.feedback.ConflictException; +import org.hisp.dhis.feedback.ErrorMessage; import org.hisp.dhis.feedback.MergeReport; import org.hisp.dhis.merge.DataMergeStrategy; import org.hisp.dhis.merge.MergeParams; @@ -93,6 +94,8 @@ import org.hisp.dhis.sms.command.SMSCommand; import org.hisp.dhis.sms.command.code.SMSCode; import org.hisp.dhis.sms.command.hibernate.SMSCommandStore; +import org.hisp.dhis.test.TestBase; +import org.hisp.dhis.test.api.TestCategoryMetadata; import org.hisp.dhis.test.integration.PostgresIntegrationTestBase; import org.hisp.dhis.trackedentity.TrackedEntity; import org.hisp.dhis.trackedentity.TrackedEntityType; @@ -117,8 +120,7 @@ class CategoryOptionComboMergeServiceTest extends PostgresIntegrationTestBase { @Autowired private CategoryService categoryService; - @Autowired private CategoryOptionStore categoryOptionStore; - @Autowired private CategoryComboStore categoryComboStore; + @Autowired private CategoryOptionComboStore categoryOptionComboStore; @Autowired private DataElementOperandStore dataElementOperandStore; @Autowired private MinMaxDataElementStore minMaxDataElementStore; @Autowired private PredictorStore predictorStore; @@ -132,12 +134,13 @@ class CategoryOptionComboMergeServiceTest extends PostgresIntegrationTestBase { @Autowired private DataApprovalAuditStore dataApprovalAuditStore; @Autowired private DataApprovalStore dataApprovalStore; @Autowired private EventStore eventStore; + @Autowired private CategoryManager categoryManager; - private CategoryCombo cc1; - private CategoryOptionCombo cocSource1; - private CategoryOptionCombo cocSource2; + private TestCategoryMetadata categoryMetadata; private CategoryOptionCombo cocTarget; private CategoryOptionCombo cocRandom; + private CategoryOptionCombo cocDuplicate; + private static int catCounter; private OrganisationUnit ou1; private OrganisationUnit ou2; private OrganisationUnit ou3; @@ -151,46 +154,14 @@ class CategoryOptionComboMergeServiceTest extends PostgresIntegrationTestBase { @BeforeEach public void setUp() { - // 8 category options - CategoryOption co1A = createCategoryOption("1A", CodeGenerator.generateUid()); - CategoryOption co1B = createCategoryOption("1B", CodeGenerator.generateUid()); - CategoryOption co2A = createCategoryOption("2A", CodeGenerator.generateUid()); - CategoryOption co2B = createCategoryOption("2B", CodeGenerator.generateUid()); - CategoryOption co3A = createCategoryOption("3A", CodeGenerator.generateUid()); - CategoryOption co3B = createCategoryOption("3B", CodeGenerator.generateUid()); - CategoryOption co4A = createCategoryOption("4A", CodeGenerator.generateUid()); - CategoryOption co4B = createCategoryOption("4B", CodeGenerator.generateUid()); - categoryService.addCategoryOption(co1A); - categoryService.addCategoryOption(co1B); - categoryService.addCategoryOption(co2A); - categoryService.addCategoryOption(co2B); - categoryService.addCategoryOption(co3A); - categoryService.addCategoryOption(co3B); - categoryService.addCategoryOption(co4A); - categoryService.addCategoryOption(co4B); - - // 4 categories (each with 2 category options) - Category cat1 = createCategory('1', co1A, co1B); - Category cat2 = createCategory('2', co2A, co2B); - Category cat3 = createCategory('3', co3A, co3B); - Category cat4 = createCategory('4', co4A, co4B); - categoryService.addCategory(cat1); - categoryService.addCategory(cat2); - categoryService.addCategory(cat3); - categoryService.addCategory(cat4); - - cc1 = createCategoryCombo('1', cat1, cat2); - CategoryCombo cc2 = createCategoryCombo('2', cat3, cat4); - categoryService.addCategoryCombo(cc1); - categoryService.addCategoryCombo(cc2); - - categoryService.generateOptionCombos(cc1); - categoryService.generateOptionCombos(cc2); - - cocSource1 = getCocWithOptions("1A", "2A"); - cocSource2 = getCocWithOptions("1B", "2B"); - cocTarget = getCocWithOptions("3A", "4B"); - cocRandom = getCocWithOptions("3B", "4A"); + categoryMetadata = TestBase.setupCategoryMetadata("cocm " + ++catCounter); + cocTarget = categoryMetadata.coc3(); + cocRandom = categoryMetadata.coc4(); + + cocDuplicate = new CategoryOptionCombo(); + cocDuplicate.setCategoryCombo(cocTarget.getCategoryCombo()); + cocDuplicate.setCategoryOptions(new HashSet<>(cocTarget.getCategoryOptions())); + manager.save(cocDuplicate); ou1 = createOrganisationUnit('A'); ou2 = createOrganisationUnit('B'); @@ -214,6 +185,27 @@ public void setUp() { periodService.addPeriod(p1); periodService.addPeriod(p2); periodService.addPeriod(p3); + + entityManager.flush(); + entityManager.clear(); + } + + @Test + @DisplayName("Merging non-duplicate COCs results in an error") + void nonDuplicateResultsInErrorTest() { + MergeParams mergeParams = getMergeParams(); + mergeParams.setSources(Set.of(UID.of(cocRandom))); + + ConflictException conflictException = + assertThrows( + ConflictException.class, + () -> categoryOptionComboMergeService.processMerge(mergeParams)); + + assertTrue( + conflictException.getMergeReport().getMergeErrors().stream() + .map(ErrorMessage::getMessage) + .collect(Collectors.toSet()) + .contains(E1540.getMessage())); } // ----------------------------- @@ -227,44 +219,63 @@ void categoryOptionRefsReplacedSourcesDeletedTest() throws ConflictException { categoryService.getAllCategoryOptionCombos(); List allCategoryOptions = categoryService.getAllCategoryOptions(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); - assertEquals(9, allCategoryOptions.size(), "9 COs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "6 COCs including 1 default"); + assertEquals(5, allCategoryOptions.size(), "5 COs including 1 default"); - List coSourcesBefore = - categoryOptionStore.getByCategoryOptionCombo( - List.of(UID.of(cocSource1.getUid()), UID.of(cocSource2.getUid()))); - List coTargetBefore = - categoryOptionStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget.getUid()))); + long coSourcesBefore = + categoryOptionComboStore.getAll().stream() + .filter(coc -> coc.getId() == cocDuplicate.getId()) + .mapToLong(coc -> coc.getCategoryOptions().size()) + .sum(); + long coTargetBefore = + categoryOptionComboStore.getAll().stream() + .filter(coc -> coc.getId() == cocTarget.getId()) + .mapToLong(coc -> coc.getCategoryOptions().size()) + .sum(); assertEquals( - 4, - coSourcesBefore.size(), - "Expect 4 category options with source category option combo refs"); + 2, coSourcesBefore, "Expect 2 category options with source category option combo refs"); assertEquals( - 2, - coTargetBefore.size(), - "Expect 2 category options with target category option combo refs"); + 2, coTargetBefore, "Expect 2 category options with target category option combo refs"); // when MergeParams mergeParams = getMergeParams(); MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); + // then - List coSourcesAfter = - categoryOptionStore.getByCategoryOptionCombo( - List.of(UID.of(cocSource1), UID.of(cocSource2))); - List coTargetAfter = - categoryOptionStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); + long coSourcesAfter = + categoryOptionComboStore.getAll().stream() + .filter(coc -> coc.getId() == cocDuplicate.getId()) + .mapToLong(coc -> coc.getCategoryOptions().size()) + .sum(); + long coTargetAfter = + categoryOptionComboStore.getAll().stream() + .filter(coc -> coc.getId() == cocTarget.getId()) + .mapToLong(coc -> coc.getCategoryOptions().size()) + .sum(); assertFalse(report.hasErrorMessages()); - assertEquals( - 0, coSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); - assertEquals( - 6, coTargetAfter.size(), "Expect 6 entries with target category option combo refs"); + assertEquals(0, coSourcesAfter, "Expect 0 entries with source category option combo refs"); + assertEquals(2, coTargetAfter, "Expect 2 entries with target category option combo refs"); assertTrue( - categoryService.getCategoryOptionCombosByUid(UID.of(cocSource1, cocSource2)).isEmpty(), + categoryService + .getCategoryOptionCombosByUid(List.of(UID.of(cocDuplicate.getUid()))) + .isEmpty(), "There should be no source COCs after deletion during merge"); + + assertCocCountAfterAutoGenerate(5); + } + + private void assertCocCountAfterAutoGenerate(int expectedCocCount) { + entityManager.flush(); + entityManager.clear(); + categoryManager.addAndPruneAllOptionCombos(); + List allCategoryOptionCombos = + categoryService.getAllCategoryOptionCombos(); + assertEquals(expectedCocCount, allCategoryOptionCombos.size()); } // ----------------------------- @@ -278,56 +289,56 @@ void categoryComboRefsReplacedSourcesDeletedTest() throws ConflictException { categoryService.getAllCategoryOptionCombos(); List allCategoryCombos = categoryService.getAllCategoryCombos(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); - assertEquals(3, allCategoryCombos.size(), "3 CCs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "6 COCs including 1 default"); + assertEquals(2, allCategoryCombos.size(), "2 CCs including 1 default"); - List ccSourcesBefore = - categoryComboStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); - List ccTargetBefore = - categoryComboStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); + long ccSourcesBefore = + categoryService.getAllCategoryOptionCombos().stream() + .filter(coc -> coc.getId() == cocDuplicate.getId()) + .filter(coc -> coc.getCategoryCombo().getId() == categoryMetadata.cc1().getId()) + .count(); + long ccTargetBefore = + categoryService.getAllCategoryOptionCombos().stream() + .filter(coc -> coc.getId() == cocTarget.getId()) + .filter(coc -> coc.getCategoryCombo().getId() == categoryMetadata.cc1().getId()) + .count(); assertEquals( - 1, - ccSourcesBefore.size(), - "Expect 1 category combo with source category option combo refs"); - assertEquals( - 1, ccTargetBefore.size(), "Expect 1 category combo with target category option combo refs"); + 1, ccSourcesBefore, "Expect 1 category combo with source category option combo refs"); assertEquals( - 4, - ccTargetBefore.get(0).getOptionCombos().size(), - "Expect 4 COCs with target category combo"); + 1, ccTargetBefore, "Expect 1 category combo with target category option combo refs"); // when MergeParams mergeParams = getMergeParams(); MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); + // then List allCOCsAfter = categoryService.getAllCategoryOptionCombos(); List allCCsAfter = categoryService.getAllCategoryCombos(); - assertEquals(7, allCOCsAfter.size(), "7 COCs including 1 default"); - assertEquals(3, allCCsAfter.size(), "3 CCs including 1 default"); + assertEquals(5, allCOCsAfter.size(), "5 COCs including 1 default"); + assertEquals(2, allCCsAfter.size(), "2 CCs including 1 default"); - // then - List ccSourcesAfter = - categoryComboStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); - List ccTargetAfter = - categoryComboStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget))); - CategoryCombo catCombo1 = categoryComboStore.getByUid(cc1.getUid()); + long ccSourcesAfter = + allCOCsAfter.stream() + .filter(coc -> coc.getId() == cocDuplicate.getId()) + .filter(coc -> coc.getCategoryCombo().getId() == categoryMetadata.cc1().getId()) + .count(); + long ccTargetAfter = + allCOCsAfter.stream() + .filter(coc -> coc.getId() == cocTarget.getId()) + .filter(coc -> coc.getCategoryCombo().getId() == categoryMetadata.cc1().getId()) + .count(); assertFalse(report.hasErrorMessages()); - assertEquals( - 0, ccSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); - assertEquals( - 1, ccTargetAfter.size(), "Expect 2 entries with target category option combo refs"); - assertEquals(5, catCombo1.getOptionCombos().size(), "Expect 5 COCs for CC1"); - assertEquals( - 4, - ccTargetAfter.get(0).getOptionCombos().size(), - "Expect 4 COCs with target category combo"); + assertEquals(0, ccSourcesAfter, "Expect 0 entries with source category option combo refs"); + assertEquals(1, ccTargetAfter, "Expect 1 entry with target category option combo refs"); assertTrue( - categoryService.getCategoryOptionCombosByUid(UID.of(cocSource1, cocSource2)).isEmpty(), + categoryService.getCategoryOptionCombosByUid(List.of(UID.of(cocDuplicate))).isEmpty(), "There should be no source COCs after deletion during merge"); + assertCocCountAfterAutoGenerate(5); } // ----------------------------- @@ -339,11 +350,11 @@ void categoryComboRefsReplacedSourcesDeletedTest() throws ConflictException { void dataElementOperandRefsReplacedSourcesDeletedTest() throws ConflictException { DataElementOperand deo1 = new DataElementOperand(); deo1.setDataElement(de1); - deo1.setCategoryOptionCombo(cocSource1); + deo1.setCategoryOptionCombo(cocDuplicate); DataElementOperand deo2 = new DataElementOperand(); deo2.setDataElement(de2); - deo2.setCategoryOptionCombo(cocSource2); + deo2.setCategoryOptionCombo(cocDuplicate); DataElementOperand deo3 = new DataElementOperand(); deo3.setDataElement(de3); @@ -356,10 +367,10 @@ void dataElementOperandRefsReplacedSourcesDeletedTest() throws ConflictException List allCategoryOptionCombos = categoryService.getAllCategoryOptionCombos(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "5 COCs including 1 default"); List deoSourcesBefore = - dataElementOperandStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataElementOperandStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List deoTargetBefore = dataElementOperandStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -379,16 +390,17 @@ void dataElementOperandRefsReplacedSourcesDeletedTest() throws ConflictException List allCOCsAfter = categoryService.getAllCategoryOptionCombos(); // then - assertEquals(7, allCOCsAfter.size(), "7 COCs including 1 default"); + assertEquals(5, allCOCsAfter.size(), "5 COCs including 1 default"); assertFalse(report.hasErrorMessages(), "there should be no merge errors"); List deoSourcesAfter = - dataElementOperandStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataElementOperandStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List deoTargetAfter = dataElementOperandStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); assertEquals( 0, deoSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); assertEquals( 3, deoTargetAfter.size(), "Expect 3 entries with target category option combo refs"); + assertCocCountAfterAutoGenerate(5); } // ----------------------------- @@ -403,8 +415,8 @@ void minMaxDataElementRefsReplacedSourcesDeletedTest() throws ConflictException OrganisationUnit ou3 = createOrganisationUnit('3'); manager.save(List.of(ou1, ou2, ou3)); - MinMaxDataElement mmde1 = new MinMaxDataElement(de1, ou1, cocSource1, 0, 100, false); - MinMaxDataElement mmde2 = new MinMaxDataElement(de2, ou2, cocSource2, 0, 100, false); + MinMaxDataElement mmde1 = new MinMaxDataElement(de1, ou1, cocDuplicate, 0, 100, false); + MinMaxDataElement mmde2 = new MinMaxDataElement(de2, ou2, cocDuplicate, 0, 100, false); MinMaxDataElement mmde3 = new MinMaxDataElement(de3, ou3, cocTarget, 0, 100, false); minMaxDataElementStore.save(mmde1); minMaxDataElementStore.save(mmde2); @@ -414,10 +426,10 @@ void minMaxDataElementRefsReplacedSourcesDeletedTest() throws ConflictException List allCategoryOptionCombos = categoryService.getAllCategoryOptionCombos(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "5 COCs including 1 default"); List mmdeSourcesBefore = - minMaxDataElementStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + minMaxDataElementStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List mmdeTargetBefore = minMaxDataElementStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -436,11 +448,11 @@ void minMaxDataElementRefsReplacedSourcesDeletedTest() throws ConflictException List allCOCsAfter = categoryService.getAllCategoryOptionCombos(); - assertEquals(7, allCOCsAfter.size(), "7 COCs including 1 default"); + assertEquals(5, allCOCsAfter.size(), "5 COCs including 1 default"); // then List mmdeSourcesAfter = - minMaxDataElementStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + minMaxDataElementStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List mmdeTargetAfter = minMaxDataElementStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -449,6 +461,7 @@ void minMaxDataElementRefsReplacedSourcesDeletedTest() throws ConflictException 0, mmdeSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); assertEquals( 3, mmdeTargetAfter.size(), "Expect 3 entries with target category option combo refs"); + assertCocCountAfterAutoGenerate(5); } // ---------------------- @@ -467,7 +480,7 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { Predictor p1 = createPredictor( de1, - cocSource1, + cocDuplicate, "1", exp1, exp1, @@ -480,7 +493,7 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { Predictor p2 = createPredictor( de2, - cocSource2, + cocDuplicate, "2", exp2, exp2, @@ -509,10 +522,10 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { List allCategoryOptionCombos = categoryService.getAllCategoryOptionCombos(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "6 COCs including 1 default"); List pSourcesBefore = - predictorStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + predictorStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List pTargetBefore = predictorStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -524,14 +537,15 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { // when MergeParams mergeParams = getMergeParams(); MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); List allCOCsAfter = categoryService.getAllCategoryOptionCombos(); - assertEquals(7, allCOCsAfter.size(), "7 COCs including 1 default"); + assertEquals(5, allCOCsAfter.size(), "5 COCs including 1 default"); // then List pSourcesAfter = - predictorStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + predictorStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List pTargetAfter = predictorStore.getByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -539,6 +553,7 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { assertEquals( 0, pSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); assertEquals(3, pTargetAfter.size(), "Expect 3 entries with target category option combo refs"); + assertCocCountAfterAutoGenerate(5); } // -------------------- @@ -549,11 +564,11 @@ void predictorRefsReplacedSourcesDeletedTest() throws ConflictException { void smsCodeRefsReplacedSourcesDeletedTest() throws ConflictException { SMSCode smsCode1 = new SMSCode(); smsCode1.setDataElement(de1); - smsCode1.setOptionId(cocSource1); + smsCode1.setOptionId(cocDuplicate); SMSCode smsCode2 = new SMSCode(); smsCode2.setDataElement(de2); - smsCode2.setOptionId(cocSource2); + smsCode2.setOptionId(cocDuplicate); SMSCode smsCode3 = new SMSCode(); smsCode3.setDataElement(de3); @@ -568,10 +583,10 @@ void smsCodeRefsReplacedSourcesDeletedTest() throws ConflictException { List allCategoryOptionCombos = categoryService.getAllCategoryOptionCombos(); - assertEquals(9, allCategoryOptionCombos.size(), "9 COCs including 1 default"); + assertEquals(6, allCategoryOptionCombos.size(), "5 COCs including 1 default"); List cSourcesBefore = - smsCommandStore.getCodesByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + smsCommandStore.getCodesByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List cTargetBefore = smsCommandStore.getCodesByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -584,11 +599,11 @@ void smsCodeRefsReplacedSourcesDeletedTest() throws ConflictException { List allCOCsAfter = categoryService.getAllCategoryOptionCombos(); - assertEquals(7, allCOCsAfter.size(), "7 COCs including 1 default"); + assertEquals(5, allCOCsAfter.size(), "5 COCs including 1 default"); // then List cSourcesAfter = - smsCommandStore.getCodesByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + smsCommandStore.getCodesByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List cTargetAfter = smsCommandStore.getCodesByCategoryOptionCombo(Set.of(UID.of(cocTarget.getUid()))); @@ -596,61 +611,19 @@ void smsCodeRefsReplacedSourcesDeletedTest() throws ConflictException { assertEquals( 0, cSourcesAfter.size(), "Expect 0 entries with source category option combo refs"); assertEquals(3, cTargetAfter.size(), "Expect 3 entries with target category option combo refs"); + assertCocCountAfterAutoGenerate(5); } // ------------------------------------- // -- DataValue Category Option Combo -- // ------------------------------------- - @Test - @DisplayName( - "Non-duplicate DataValues with references to source COCs are replaced with target COC using LAST_UPDATED strategy") - void dataValueMergeCocLastUpdatedTest() throws ConflictException { - // given - DataValue dv1 = createDataValue(de1, p1, ou1, cocSource1, cocRandom, "value1"); - DataValue dv2 = createDataValue(de2, p2, ou1, cocSource2, cocRandom, "value2"); - DataValue dv3 = createDataValue(de3, p3, ou1, cocTarget, cocRandom, "value3"); - - dataValueStore.addDataValue(dv1); - dataValueStore.addDataValue(dv2); - dataValueStore.addDataValue(dv3); - - // params - MergeParams mergeParams = getMergeParams(); - mergeParams.setDataMergeStrategy(DataMergeStrategy.LAST_UPDATED); - - // when - MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); - - // then - List sourceItems = - dataValueStore.getAllDataValues().stream() - .filter( - dv -> - Set.of(cocSource1.getUid(), cocSource2.getUid()) - .contains(dv.getCategoryOptionCombo().getUid())) - .toList(); - List targetItems = - dataValueStore.getAllDataValues().stream() - .filter(dv -> dv.getCategoryOptionCombo().getUid().equals(cocTarget.getUid())) - .toList(); - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - assertFalse(report.hasErrorMessages()); - assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); - assertEquals(3, targetItems.size(), "Expect 3 entries with target COC refs"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); - assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); - } @Test @DisplayName("DataValues with references to source COCs are deleted using DISCARD strategy") void dataValueMergeCocDiscardTest() throws ConflictException { // given - DataValue dv1 = createDataValue(de1, p1, ou1, cocSource1, cocRandom, "value1"); - DataValue dv2 = createDataValue(de2, p2, ou1, cocSource2, cocRandom, "value2"); + DataValue dv1 = createDataValue(de1, p1, ou1, cocDuplicate, cocRandom, "value1"); + DataValue dv2 = createDataValue(de2, p2, ou1, cocDuplicate, cocRandom, "value2"); DataValue dv3 = createDataValue(de3, p3, ou1, cocTarget, cocRandom, "value3"); dataValueStore.addDataValue(dv1); @@ -663,14 +636,13 @@ void dataValueMergeCocDiscardTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = dataValueStore.getAllDataValues().stream() .filter( - dv -> - Set.of(cocSource1.getUid(), cocSource2.getUid()) - .contains(dv.getCategoryOptionCombo().getUid())) + dv -> Objects.equals(cocDuplicate.getUid(), dv.getCategoryOptionCombo().getUid())) .toList(); List targetItems = dataValueStore.getAllDataValues().stream() @@ -683,66 +655,26 @@ void dataValueMergeCocDiscardTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } // -------------------------------------- // -- DataValue Attribute Option Combo -- // -------------------------------------- - @Test - @DisplayName( - "Non-duplicate DataValues with references to source AOCs are replaced with target AOC using LAST_UPDATED strategy") - void dataValueMergeAocLastUpdatedTest() throws ConflictException { - // given - DataValue dv1 = createDataValue(de1, p1, ou1, cocRandom, cocSource1, "value1"); - DataValue dv2 = createDataValue(de2, p2, ou1, cocRandom, cocSource2, "value2"); - DataValue dv3 = createDataValue(de3, p3, ou1, cocRandom, cocTarget, "value3"); - - dataValueStore.addDataValue(dv1); - dataValueStore.addDataValue(dv2); - dataValueStore.addDataValue(dv3); - - // params - MergeParams mergeParams = getMergeParams(); - mergeParams.setDataMergeStrategy(DataMergeStrategy.LAST_UPDATED); - - // when - MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); - - // then - List sourceItems = - dataValueStore.getAllDataValues().stream() - .filter( - dv -> - Set.of(cocSource1.getUid(), cocSource2.getUid()) - .contains(dv.getAttributeOptionCombo().getUid())) - .toList(); - List targetItems = - dataValueStore.getAllDataValues().stream() - .filter(dv -> dv.getAttributeOptionCombo().getUid().equals(cocTarget.getUid())) - .toList(); - - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - assertFalse(report.hasErrorMessages()); - assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); - assertEquals(3, targetItems.size(), "Expect 3 entries with target COC refs"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); - assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); - } - @Test @DisplayName("DataValues with references to source AOCs are deleted, using DISCARD strategy") void dataValueMergeAocDiscardTest() throws ConflictException { // given - DataValue dv1 = createDataValue(de1, p1, ou1, cocRandom, cocSource1, "value1"); - DataValue dv2 = createDataValue(de2, p2, ou1, cocRandom, cocSource2, "value2"); + DataValue dv1 = createDataValue(de1, p1, ou1, cocRandom, cocDuplicate, "value1"); + DataValue dv2 = createDataValue(de2, p2, ou1, cocRandom, cocDuplicate, "value2"); DataValue dv3 = createDataValue(de3, p3, ou1, cocRandom, cocTarget, "value3"); dataValueStore.addDataValue(dv1); @@ -755,14 +687,13 @@ void dataValueMergeAocDiscardTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = dataValueStore.getAllDataValues().stream() .filter( - dv -> - Set.of(cocSource1.getUid(), cocSource2.getUid()) - .contains(dv.getAttributeOptionCombo().getUid())) + dv -> Objects.equals(cocDuplicate.getUid(), dv.getAttributeOptionCombo().getUid())) .toList(); List targetItems = dataValueStore.getAllDataValues().stream() @@ -775,10 +706,15 @@ void dataValueMergeAocDiscardTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } // ------------------------ @@ -789,10 +725,10 @@ void dataValueMergeAocDiscardTest() throws ConflictException { "DataValueAudits with references to source COCs are not changed or deleted when sources not deleted") void dataValueAuditMergeTest() throws ConflictException { // given - DataValueAudit dva1 = createDataValueAudit(cocSource1, "1", p1); - DataValueAudit dva2 = createDataValueAudit(cocSource1, "2", p1); - DataValueAudit dva3 = createDataValueAudit(cocSource2, "1", p1); - DataValueAudit dva4 = createDataValueAudit(cocSource2, "2", p1); + DataValueAudit dva1 = createDataValueAudit(cocDuplicate, "1", p1); + DataValueAudit dva2 = createDataValueAudit(cocDuplicate, "2", p1); + DataValueAudit dva3 = createDataValueAudit(cocDuplicate, "1", p1); + DataValueAudit dva4 = createDataValueAudit(cocDuplicate, "2", p1); DataValueAudit dva5 = createDataValueAudit(cocTarget, "1", p1); dataValueAuditStore.addDataValueAudit(dva1); @@ -805,25 +741,21 @@ void dataValueAuditMergeTest() throws ConflictException { MergeParams mergeParams = getMergeParams(); mergeParams.setDeleteSources(false); - // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); // then - DataValueAuditQueryParams source1DvaQueryParams = getQueryParams(cocSource1); - DataValueAuditQueryParams source2DvaQueryParams = getQueryParams(cocSource2); + DataValueAuditQueryParams source1DvaQueryParams = getQueryParams(cocDuplicate); DataValueAuditQueryParams targetDvaQueryParams = getQueryParams(cocTarget); List source1Audits = dataValueAuditStore.getDataValueAudits(source1DvaQueryParams); - List source2Audits = - dataValueAuditStore.getDataValueAudits(source2DvaQueryParams); List targetItems = dataValueAuditStore.getDataValueAudits(targetDvaQueryParams); assertFalse(report.hasErrorMessages()); - assertEquals( - 4, source1Audits.size() + source2Audits.size(), "Expect 4 entries with source COC refs"); + assertEquals(4, source1Audits.size(), "Expect 2 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref"); + assertCocCountAfterAutoGenerate(6); } @Test @@ -831,10 +763,10 @@ void dataValueAuditMergeTest() throws ConflictException { "DataValueAudits with references to source COCs are deleted when sources are deleted") void dataValueAuditMergeDeleteTest() throws ConflictException { // given - DataValueAudit dva1 = createDataValueAudit(cocSource1, "1", p1); - DataValueAudit dva2 = createDataValueAudit(cocSource1, "2", p1); - DataValueAudit dva3 = createDataValueAudit(cocSource2, "1", p1); - DataValueAudit dva4 = createDataValueAudit(cocSource2, "2", p1); + DataValueAudit dva1 = createDataValueAudit(cocDuplicate, "1", p1); + DataValueAudit dva2 = createDataValueAudit(cocDuplicate, "2", p1); + DataValueAudit dva3 = createDataValueAudit(cocDuplicate, "1", p1); + DataValueAudit dva4 = createDataValueAudit(cocDuplicate, "2", p1); DataValueAudit dva5 = createDataValueAudit(cocTarget, "1", p1); dataValueAuditStore.addDataValueAudit(dva1); @@ -850,21 +782,18 @@ void dataValueAuditMergeDeleteTest() throws ConflictException { MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); // then - DataValueAuditQueryParams source1DvaQueryParams = getQueryParams(cocSource1); - DataValueAuditQueryParams source2DvaQueryParams = getQueryParams(cocSource2); + DataValueAuditQueryParams source1DvaQueryParams = getQueryParams(cocDuplicate); DataValueAuditQueryParams targetDvaQueryParams = getQueryParams(cocTarget); List source1Audits = dataValueAuditStore.getDataValueAudits(source1DvaQueryParams); - List source2Audits = - dataValueAuditStore.getDataValueAudits(source2DvaQueryParams); List targetItems = dataValueAuditStore.getDataValueAudits(targetDvaQueryParams); assertFalse(report.hasErrorMessages()); - assertEquals( - 0, source1Audits.size() + source2Audits.size(), "Expect 0 entries with source COC refs"); + assertEquals(0, source1Audits.size(), "Expect 0 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref"); + assertCocCountAfterAutoGenerate(5); } // ------------------------ @@ -888,20 +817,16 @@ void dataApprovalAuditMergeTest() throws ConflictException { DataApprovalWorkflow daw = new DataApprovalWorkflow(); daw.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw.setName("DAW"); - daw.setCategoryCombo(cc1); + daw.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw); - DataApprovalAudit daa1 = createDataApprovalAudit(cocSource1, level1, daw, p1); - DataApprovalAudit daa2 = createDataApprovalAudit(cocSource1, level2, daw, p2); - DataApprovalAudit daa3 = createDataApprovalAudit(cocSource2, level1, daw, p1); - DataApprovalAudit daa4 = createDataApprovalAudit(cocSource2, level2, daw, p2); - DataApprovalAudit daa5 = createDataApprovalAudit(cocTarget, level1, daw, p1); + DataApprovalAudit daa1 = createDataApprovalAudit(cocDuplicate, level1, daw, p1); + DataApprovalAudit daa2 = createDataApprovalAudit(cocDuplicate, level2, daw, p2); + DataApprovalAudit daa3 = createDataApprovalAudit(cocTarget, level1, daw, p1); dataApprovalAuditStore.save(daa1); dataApprovalAuditStore.save(daa2); dataApprovalAuditStore.save(daa3); - dataApprovalAuditStore.save(daa4); - dataApprovalAuditStore.save(daa5); // params MergeParams mergeParams = getMergeParams(); @@ -909,6 +834,7 @@ void dataApprovalAuditMergeTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then DataApprovalAuditQueryParams targetDaaQueryParams = @@ -916,12 +842,18 @@ void dataApprovalAuditMergeTest() throws ConflictException { .setAttributeOptionCombos(new HashSet<>(Collections.singletonList(cocTarget))) .setLevels(Set.of(level1)); - List sourceAudits = dataApprovalAuditStore.getAll(); + DataApprovalAuditQueryParams sourceDaaQueryParams = + new DataApprovalAuditQueryParams() + .setAttributeOptionCombos(new HashSet<>(Collections.singletonList(cocDuplicate))) + .setLevels(Set.of(level1, level2)); + + List sourceAudits = + dataApprovalAuditStore.getDataApprovalAudits(sourceDaaQueryParams); List targetItems = dataApprovalAuditStore.getDataApprovalAudits(targetDaaQueryParams); assertFalse(report.hasErrorMessages()); - assertEquals(5, sourceAudits.size(), "Expect 4 entries with source COC refs"); + assertEquals(2, sourceAudits.size(), "Expect 2 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref"); } @@ -938,13 +870,13 @@ void dataApprovalAuditMergeDeleteTest() throws ConflictException { DataApprovalWorkflow daw = new DataApprovalWorkflow(); daw.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw.setName("DAW"); - daw.setCategoryCombo(cc1); + daw.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw); - DataApprovalAudit daa1 = createDataApprovalAudit(cocSource1, dataApprovalLevel, daw, p1); - DataApprovalAudit daa2 = createDataApprovalAudit(cocSource1, dataApprovalLevel, daw, p1); - DataApprovalAudit daa3 = createDataApprovalAudit(cocSource2, dataApprovalLevel, daw, p1); - DataApprovalAudit daa4 = createDataApprovalAudit(cocSource2, dataApprovalLevel, daw, p1); + DataApprovalAudit daa1 = createDataApprovalAudit(cocDuplicate, dataApprovalLevel, daw, p1); + DataApprovalAudit daa2 = createDataApprovalAudit(cocDuplicate, dataApprovalLevel, daw, p1); + DataApprovalAudit daa3 = createDataApprovalAudit(cocDuplicate, dataApprovalLevel, daw, p1); + DataApprovalAudit daa4 = createDataApprovalAudit(cocDuplicate, dataApprovalLevel, daw, p1); DataApprovalAudit daa5 = createDataApprovalAudit(cocTarget, dataApprovalLevel, daw, p1); dataApprovalAuditStore.save(daa1); @@ -962,7 +894,7 @@ void dataApprovalAuditMergeDeleteTest() throws ConflictException { // then DataApprovalAuditQueryParams source1DaaQueryParams = new DataApprovalAuditQueryParams() - .setAttributeOptionCombos(new HashSet<>(Arrays.asList(cocSource1, cocSource2))); + .setAttributeOptionCombos(new HashSet<>(Collections.singletonList(cocDuplicate))); DataApprovalAuditQueryParams targetDaaQueryParams = new DataApprovalAuditQueryParams() .setAttributeOptionCombos(new HashSet<>(Collections.singletonList(cocTarget))); @@ -975,6 +907,7 @@ void dataApprovalAuditMergeDeleteTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceAudits.size(), "Expect 0 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref"); + assertCocCountAfterAutoGenerate(5); } // ----------------------- @@ -998,17 +931,17 @@ void dataApprovalMergeCocLastUpdatedTest() throws ConflictException { DataApprovalWorkflow daw1 = new DataApprovalWorkflow(); daw1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw1.setName("DAW1"); - daw1.setCategoryCombo(cc1); + daw1.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw1); DataApprovalWorkflow daw2 = new DataApprovalWorkflow(); daw2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw2.setName("DAW2"); - daw2.setCategoryCombo(cc1); + daw2.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw2); - DataApproval da1 = createDataApproval(cocSource1, level1, daw1, p1, ou1); - DataApproval da2 = createDataApproval(cocSource2, level2, daw1, p2, ou1); + DataApproval da1 = createDataApproval(cocDuplicate, level1, daw1, p1, ou1); + DataApproval da2 = createDataApproval(cocDuplicate, level2, daw1, p2, ou1); DataApproval da3 = createDataApproval(cocTarget, level2, daw2, p2, ou2); DataApproval da4 = createDataApproval(cocRandom, level2, daw2, p3, ou3); @@ -1019,7 +952,7 @@ void dataApprovalMergeCocLastUpdatedTest() throws ConflictException { // pre-merge state List sourcesPreMerge = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetPreMerge = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); assertEquals(2, sourcesPreMerge.size(), "Expect 2 entries with source COC refs"); @@ -1031,10 +964,11 @@ void dataApprovalMergeCocLastUpdatedTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1044,10 +978,15 @@ void dataApprovalMergeCocLastUpdatedTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(3, targetItems.size(), "Expect 3 entries with target COC refs"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1068,23 +1007,19 @@ void duplicateDataApprovalMergeCocLastUpdatedTest() throws ConflictException { DataApprovalWorkflow daw1 = new DataApprovalWorkflow(); daw1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw1.setName("DAW1"); - daw1.setCategoryCombo(cc1); + daw1.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw1); DataApprovalWorkflow daw2 = new DataApprovalWorkflow(); daw2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw2.setName("DAW2"); - daw2.setCategoryCombo(cc1); + daw2.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw2); - DataApproval da1a = createDataApproval(cocSource1, level1, daw1, p1, ou1); + DataApproval da1a = createDataApproval(cocDuplicate, level1, daw1, p1, ou1); da1a.setLastUpdated(DateUtils.parseDate("2024-6-8")); - DataApproval da1b = createDataApproval(cocSource1, level1, daw1, p2, ou1); + DataApproval da1b = createDataApproval(cocDuplicate, level1, daw1, p2, ou1); da1b.setLastUpdated(DateUtils.parseDate("2024-10-8")); - DataApproval da2a = createDataApproval(cocSource2, level1, daw1, p1, ou1); - da2a.setLastUpdated(DateUtils.parseDate("2024-6-8")); - DataApproval da2b = createDataApproval(cocSource2, level1, daw1, p2, ou1); - da2b.setLastUpdated(DateUtils.parseDate("2024-10-8")); DataApproval da3a = createDataApproval(cocTarget, level1, daw1, p1, ou1); da3a.setLastUpdated(DateUtils.parseDate("2024-12-8")); DataApproval da3b = createDataApproval(cocTarget, level1, daw1, p2, ou1); @@ -1094,8 +1029,6 @@ void duplicateDataApprovalMergeCocLastUpdatedTest() throws ConflictException { dataApprovalStore.addDataApproval(da1a); dataApprovalStore.addDataApproval(da1b); - dataApprovalStore.addDataApproval(da2a); - dataApprovalStore.addDataApproval(da2b); dataApprovalStore.addDataApproval(da3a); dataApprovalStore.addDataApproval(da3b); dataApprovalStore.addDataApproval(da4a); @@ -1103,10 +1036,10 @@ void duplicateDataApprovalMergeCocLastUpdatedTest() throws ConflictException { // pre-merge state List sourcesPreMerge = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetPreMerge = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); - assertEquals(4, sourcesPreMerge.size(), "Expect 4 entries with source COC refs"); + assertEquals(2, sourcesPreMerge.size(), "Expect 2 entries with source COC refs"); assertEquals(2, targetPreMerge.size(), "Expect 2 entries with target COC refs"); // params @@ -1115,10 +1048,11 @@ void duplicateDataApprovalMergeCocLastUpdatedTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1127,17 +1061,22 @@ void duplicateDataApprovalMergeCocLastUpdatedTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); - assertEquals(2, targetItems.size(), "Expect 2 entries with target COC refs"); + assertEquals(2, targetItems.size(), "Expect 4 entries with target COC refs"); assertEquals( Set.of("2024-12-08", "2024-12-09"), targetItems.stream() .map(da -> DateUtils.toMediumDate(da.getLastUpdated())) .collect(Collectors.toSet()), "target items should contain the original target Data Approvals lastUpdated dates"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1158,23 +1097,19 @@ void duplicateAndNonDuplicateDataApprovalMergeTest() throws ConflictException { DataApprovalWorkflow daw1 = new DataApprovalWorkflow(); daw1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw1.setName("DAW1"); - daw1.setCategoryCombo(cc1); + daw1.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw1); DataApprovalWorkflow daw2 = new DataApprovalWorkflow(); daw2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw2.setName("DAW2"); - daw2.setCategoryCombo(cc1); + daw2.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw2); - DataApproval da1a = createDataApproval(cocSource1, level1, daw1, p1, ou1); + DataApproval da1a = createDataApproval(cocDuplicate, level1, daw1, p1, ou1); da1a.setLastUpdated(DateUtils.parseDate("2024-12-8")); - DataApproval da1b = createDataApproval(cocSource1, level1, daw1, p2, ou1); + DataApproval da1b = createDataApproval(cocDuplicate, level1, daw1, p2, ou1); da1b.setLastUpdated(DateUtils.parseDate("2024-10-8")); - DataApproval da2a = createDataApproval(cocSource2, level1, daw1, p1, ou1); - da2a.setLastUpdated(DateUtils.parseDate("2024-6-8")); - DataApproval da2b = createDataApproval(cocSource2, level1, daw1, p2, ou1); - da2b.setLastUpdated(DateUtils.parseDate("2024-10-8")); DataApproval da3a = createDataApproval(cocTarget, level1, daw1, p1, ou1); da3a.setLastUpdated(DateUtils.parseDate("2024-12-1")); DataApproval da3b = createDataApproval(cocTarget, level1, daw1, p2, ou1); @@ -1184,8 +1119,6 @@ void duplicateAndNonDuplicateDataApprovalMergeTest() throws ConflictException { dataApprovalStore.addDataApproval(da1a); dataApprovalStore.addDataApproval(da1b); - dataApprovalStore.addDataApproval(da2a); - dataApprovalStore.addDataApproval(da2b); dataApprovalStore.addDataApproval(da3a); dataApprovalStore.addDataApproval(da3b); dataApprovalStore.addDataApproval(da4a); @@ -1193,10 +1126,10 @@ void duplicateAndNonDuplicateDataApprovalMergeTest() throws ConflictException { // pre-merge state List sourcesPreMerge = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetPreMerge = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); - assertEquals(4, sourcesPreMerge.size(), "Expect 4 entries with source COC refs"); + assertEquals(2, sourcesPreMerge.size(), "Expect 2 entries with source COC refs"); assertEquals(2, targetPreMerge.size(), "Expect 2 entries with target COC refs"); // params @@ -1208,7 +1141,7 @@ void duplicateAndNonDuplicateDataApprovalMergeTest() throws ConflictException { // then List sourceItems = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1224,10 +1157,15 @@ void duplicateAndNonDuplicateDataApprovalMergeTest() throws ConflictException { .map(da -> DateUtils.toMediumDate(da.getLastUpdated())) .collect(Collectors.toSet()), "target items should contain the original target Data Approvals lastUpdated dates"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1248,23 +1186,19 @@ void duplicateDataApprovalSourceLastUpdatedTest() throws ConflictException { DataApprovalWorkflow daw1 = new DataApprovalWorkflow(); daw1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw1.setName("DAW1"); - daw1.setCategoryCombo(cc1); + daw1.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw1); DataApprovalWorkflow daw2 = new DataApprovalWorkflow(); daw2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw2.setName("DAW2"); - daw2.setCategoryCombo(cc1); + daw2.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw2); - DataApproval da1a = createDataApproval(cocSource1, level1, daw1, p1, ou1); + DataApproval da1a = createDataApproval(cocDuplicate, level1, daw1, p1, ou1); da1a.setLastUpdated(DateUtils.parseDate("2024-12-03")); - DataApproval da1b = createDataApproval(cocSource1, level1, daw1, p2, ou1); - da1b.setLastUpdated(DateUtils.parseDate("2024-12-01")); - DataApproval da2a = createDataApproval(cocSource2, level1, daw1, p1, ou1); - da2a.setLastUpdated(DateUtils.parseDate("2024-11-01")); - DataApproval da2b = createDataApproval(cocSource2, level1, daw1, p2, ou1); - da2b.setLastUpdated(DateUtils.parseDate("2024-12-08")); + DataApproval da1b = createDataApproval(cocDuplicate, level1, daw1, p2, ou1); + da1b.setLastUpdated(DateUtils.parseDate("2024-12-08")); DataApproval da3a = createDataApproval(cocTarget, level1, daw1, p1, ou1); da3a.setLastUpdated(DateUtils.parseDate("2024-06-08")); DataApproval da3b = createDataApproval(cocTarget, level1, daw1, p2, ou1); @@ -1274,8 +1208,6 @@ void duplicateDataApprovalSourceLastUpdatedTest() throws ConflictException { dataApprovalStore.addDataApproval(da1a); dataApprovalStore.addDataApproval(da1b); - dataApprovalStore.addDataApproval(da2a); - dataApprovalStore.addDataApproval(da2b); dataApprovalStore.addDataApproval(da3a); dataApprovalStore.addDataApproval(da3b); dataApprovalStore.addDataApproval(da4a); @@ -1283,10 +1215,10 @@ void duplicateDataApprovalSourceLastUpdatedTest() throws ConflictException { // pre-merge state List sourcesPreMerge = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetPreMerge = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); - assertEquals(4, sourcesPreMerge.size(), "Expect 4 entries with source COC refs"); + assertEquals(2, sourcesPreMerge.size(), "Expect 2 entries with source COC refs"); assertEquals(2, targetPreMerge.size(), "Expect 2 entries with target COC refs"); // params @@ -1295,10 +1227,11 @@ void duplicateDataApprovalSourceLastUpdatedTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1314,10 +1247,15 @@ void duplicateDataApprovalSourceLastUpdatedTest() throws ConflictException { .map(da -> DateUtils.toMediumDate(da.getLastUpdated())) .collect(Collectors.toSet()), "target items should contain the original source Data Approvals lastUpdated dates"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1338,22 +1276,22 @@ void dataApprovalMergeCocDiscardTest() throws ConflictException { DataApprovalWorkflow daw1 = new DataApprovalWorkflow(); daw1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw1.setName("DAW1"); - daw1.setCategoryCombo(cc1); + daw1.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw1); DataApprovalWorkflow daw2 = new DataApprovalWorkflow(); daw2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); daw2.setName("DAW2"); - daw2.setCategoryCombo(cc1); + daw2.setCategoryCombo(categoryMetadata.cc1()); manager.save(daw2); - DataApproval da1a = createDataApproval(cocSource1, level1, daw1, p1, ou1); + DataApproval da1a = createDataApproval(cocDuplicate, level1, daw1, p1, ou1); da1a.setLastUpdated(DateUtils.parseDate("2024-12-03")); - DataApproval da1b = createDataApproval(cocSource1, level1, daw1, p2, ou1); + DataApproval da1b = createDataApproval(cocDuplicate, level1, daw1, p2, ou1); da1b.setLastUpdated(DateUtils.parseDate("2024-12-01")); - DataApproval da2a = createDataApproval(cocSource2, level1, daw1, p1, ou1); + DataApproval da2a = createDataApproval(cocDuplicate, level2, daw1, p1, ou1); da2a.setLastUpdated(DateUtils.parseDate("2024-11-01")); - DataApproval da2b = createDataApproval(cocSource2, level1, daw1, p2, ou1); + DataApproval da2b = createDataApproval(cocDuplicate, level2, daw1, p2, ou1); da2b.setLastUpdated(DateUtils.parseDate("2024-12-08")); DataApproval da3a = createDataApproval(cocTarget, level1, daw1, p1, ou1); da3a.setLastUpdated(DateUtils.parseDate("2024-06-08")); @@ -1373,7 +1311,7 @@ void dataApprovalMergeCocDiscardTest() throws ConflictException { // pre merge state List sourceItemsBefore = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItemsBefore = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1386,10 +1324,11 @@ void dataApprovalMergeCocDiscardTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - dataApprovalStore.getByCategoryOptionCombo(UID.of(cocSource1, cocSource2)); + dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = dataApprovalStore.getByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1399,10 +1338,15 @@ void dataApprovalMergeCocDiscardTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(2, targetItems.size(), "Expect 2 entry with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } // ----------------------------- @@ -1423,9 +1367,9 @@ void eventMergeTest() throws ConflictException { manager.save(stage); Event e1 = createEvent(stage, enrollment, ou1); - e1.setAttributeOptionCombo(cocSource1); + e1.setAttributeOptionCombo(cocDuplicate); Event e2 = createEvent(stage, enrollment, ou1); - e2.setAttributeOptionCombo(cocSource2); + e2.setAttributeOptionCombo(cocDuplicate); Event e3 = createEvent(stage, enrollment, ou1); e3.setAttributeOptionCombo(cocTarget); Event e4 = createEvent(stage, enrollment, ou1); @@ -1454,56 +1398,13 @@ void eventMergeTest() throws ConflictException { .collect(Collectors.toSet()) .containsAll(Set.of(cocTarget.getUid(), cocRandom.getUid())), "All events should only have references to the target coc and the random coc"); - assertEquals(9, allCategoryOptionCombos.size(), "Expect 9 COCs present"); + assertEquals(6, allCategoryOptionCombos.size(), "Expect 6 COCs present"); assertTrue( allCategoryOptionCombos.stream() .map(BaseIdentifiableObject::getUid) .collect(Collectors.toSet()) - .containsAll(Set.of(cocSource1.getUid(), cocSource2.getUid(), cocTarget.getUid()))); - } - - @Test - @DisplayName( - "Event eventDataValues references to source COCs are deleted using DISCARD, source COCs are deleted") - void eventMergeSourcesDeletedTest() throws ConflictException { - // given - TrackedEntityType entityType = createTrackedEntityType('T'); - manager.save(entityType); - TrackedEntity trackedEntity = createTrackedEntity(ou1, entityType); - manager.save(trackedEntity); - Enrollment enrollment = createEnrollment(program, trackedEntity, ou1); - manager.save(enrollment); - ProgramStage stage = createProgramStage('s', 2); - manager.save(stage); - - Event e1 = createEvent(stage, enrollment, ou1); - e1.setAttributeOptionCombo(cocSource1); - Event e2 = createEvent(stage, enrollment, ou1); - e2.setAttributeOptionCombo(cocSource2); - Event e3 = createEvent(stage, enrollment, ou1); - e3.setAttributeOptionCombo(cocTarget); - Event e4 = createEvent(stage, enrollment, ou1); - e4.setAttributeOptionCombo(cocRandom); - - manager.save(List.of(e1, e2, e3, e4)); - - // params - MergeParams mergeParams = getMergeParams(); - - // when - MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); - - // then - List allEvents = eventStore.getAll(); - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - assertFalse(report.hasErrorMessages()); - assertEquals(2, allEvents.size(), "Expect 2 entries still"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); - assertTrue(allCategoryOptionCombos.contains(cocTarget), "target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "source COC should not be present"); + .containsAll(Set.of(cocDuplicate.getUid(), cocTarget.getUid()))); + assertCocCountAfterAutoGenerate(5); } // -------------------------------- @@ -1518,8 +1419,8 @@ void cdsrMergeCocDiscardTest() throws ConflictException { ds1.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); manager.save(ds1); - CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocSource1); - CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p1, cocSource2); + CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocDuplicate); + CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p2, cocDuplicate); CompleteDataSetRegistration cdsr3 = createCdsr(ds1, ou1, p1, cocTarget); CompleteDataSetRegistration cdsr4 = createCdsr(ds1, ou1, p1, cocRandom); completeDataSetRegistrationStore.saveCompleteDataSetRegistration(cdsr1); @@ -1533,11 +1434,11 @@ void cdsrMergeCocDiscardTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - completeDataSetRegistrationStore.getAllByCategoryOptionCombo( - UID.of(cocSource1, cocSource2)); + completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1547,10 +1448,15 @@ void cdsrMergeCocDiscardTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(1, targetItems.size(), "Expect 1 entry with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1566,8 +1472,8 @@ void cdsrMergeNoDuplicatesTest() throws ConflictException { ds2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); manager.save(ds2); - CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocSource1); - CompleteDataSetRegistration cdsr2 = createCdsr(ds2, ou1, p3, cocSource2); + CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocDuplicate); + CompleteDataSetRegistration cdsr2 = createCdsr(ds2, ou1, p3, cocDuplicate); CompleteDataSetRegistration cdsr3 = createCdsr(ds1, ou3, p2, cocTarget); CompleteDataSetRegistration cdsr4 = createCdsr(ds2, ou2, p1, cocRandom); completeDataSetRegistrationStore.saveWithoutUpdatingLastUpdated(cdsr1); @@ -1581,11 +1487,11 @@ void cdsrMergeNoDuplicatesTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - completeDataSetRegistrationStore.getAllByCategoryOptionCombo( - UID.of(cocSource1, cocSource2)); + completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1595,10 +1501,15 @@ void cdsrMergeNoDuplicatesTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(3, targetItems.size(), "Expect 3 entries with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1614,9 +1525,9 @@ void cdsrMergeDuplicatesTargetLastUpdatedTest() throws ConflictException { ds2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); manager.save(ds2); - CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocSource1); + CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocDuplicate); cdsr1.setLastUpdated(DateUtils.parseDate("2024-11-01")); - CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p1, cocSource2); + CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p2, cocDuplicate); cdsr2.setLastUpdated(DateUtils.parseDate("2024-10-01")); CompleteDataSetRegistration cdsr3 = createCdsr(ds1, ou1, p1, cocTarget); cdsr3.setLastUpdated(DateUtils.parseDate("2024-12-05")); @@ -1632,11 +1543,11 @@ void cdsrMergeDuplicatesTargetLastUpdatedTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - completeDataSetRegistrationStore.getAllByCategoryOptionCombo( - UID.of(cocSource1, cocSource2)); + completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1645,17 +1556,22 @@ void cdsrMergeDuplicatesTargetLastUpdatedTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); - assertEquals(1, targetItems.size(), "Expect 1 entries with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(2, targetItems.size(), "Expect 2 entries with target COC ref only"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertEquals( - Set.of("2024-12-05"), + Set.of("2024-12-05", "2024-10-01"), targetItems.stream() .map(da -> DateUtils.toMediumDate(da.getLastUpdated())) .collect(Collectors.toSet()), "target items should contain target Data Approvals lastUpdated dates"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1671,9 +1587,9 @@ void cdsrMergeDuplicatesSourcesLastUpdatedTest() throws ConflictException { ds2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); manager.save(ds2); - CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocSource1); + CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocDuplicate); cdsr1.setLastUpdated(DateUtils.parseDate("2024-10-01")); - CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p1, cocSource2); + CompleteDataSetRegistration cdsr2 = createCdsr(ds1, ou1, p2, cocDuplicate); cdsr2.setLastUpdated(DateUtils.parseDate("2024-11-01")); CompleteDataSetRegistration cdsr3 = createCdsr(ds1, ou1, p1, cocTarget); cdsr3.setLastUpdated(DateUtils.parseDate("2024-05-05")); @@ -1689,11 +1605,11 @@ void cdsrMergeDuplicatesSourcesLastUpdatedTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - completeDataSetRegistrationStore.getAllByCategoryOptionCombo( - UID.of(cocSource1, cocSource2)); + completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1702,17 +1618,22 @@ void cdsrMergeDuplicatesSourcesLastUpdatedTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); - assertEquals(1, targetItems.size(), "Expect 1 entries with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(2, targetItems.size(), "Expect 2 entries with target COC ref only"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertEquals( - Set.of("2024-11-01"), + Set.of("2024-11-01", "2024-10-01"), targetItems.stream() .map(da -> DateUtils.toMediumDate(da.getLastUpdated())) .collect(Collectors.toSet()), "target items should contain source registration lastUpdated dates"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } @Test @@ -1728,9 +1649,9 @@ void cdsrMergeDuplicatesNonDuplicatesTest() throws ConflictException { ds2.setPeriodType(PeriodType.getPeriodType(PeriodTypeEnum.MONTHLY)); manager.save(ds2); - CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocSource1); + CompleteDataSetRegistration cdsr1 = createCdsr(ds1, ou1, p1, cocDuplicate); cdsr1.setLastUpdated(DateUtils.parseDate("2024-10-01")); - CompleteDataSetRegistration cdsr2 = createCdsr(ds2, ou2, p1, cocSource2); + CompleteDataSetRegistration cdsr2 = createCdsr(ds2, ou2, p1, cocDuplicate); cdsr2.setLastUpdated(DateUtils.parseDate("2024-11-11")); CompleteDataSetRegistration cdsr3 = createCdsr(ds1, ou1, p1, cocTarget); cdsr3.setLastUpdated(DateUtils.parseDate("2024-12-05")); @@ -1746,11 +1667,11 @@ void cdsrMergeDuplicatesNonDuplicatesTest() throws ConflictException { // when MergeReport report = categoryOptionComboMergeService.processMerge(mergeParams); + entityManager.flush(); // then List sourceItems = - completeDataSetRegistrationStore.getAllByCategoryOptionCombo( - UID.of(cocSource1, cocSource2)); + completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocDuplicate))); List targetItems = completeDataSetRegistrationStore.getAllByCategoryOptionCombo(List.of(UID.of(cocTarget))); @@ -1760,7 +1681,7 @@ void cdsrMergeDuplicatesNonDuplicatesTest() throws ConflictException { assertFalse(report.hasErrorMessages()); assertEquals(0, sourceItems.size(), "Expect 0 entries with source COC refs"); assertEquals(2, targetItems.size(), "Expect 2 entries with target COC ref only"); - assertEquals(7, allCategoryOptionCombos.size(), "Expect 7 COCs present"); + assertEquals(5, allCategoryOptionCombos.size(), "Expect 5 COCs present"); assertEquals( Set.of("2024-12-05", "2024-11-11"), targetItems.stream() @@ -1768,8 +1689,13 @@ void cdsrMergeDuplicatesNonDuplicatesTest() throws ConflictException { .collect(Collectors.toSet()), "target items should contain target & source registration lastUpdated dates"); assertTrue(allCategoryOptionCombos.contains(cocTarget), "Target COC should be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource1), "Source COC should not be present"); - assertFalse(allCategoryOptionCombos.contains(cocSource2), "Source COC should not be present"); + assertFalse( + allCategoryOptionCombos.stream() + .map(BaseIdentifiableObject::getUid) + .collect(Collectors.toSet()) + .contains(cocDuplicate.getUid()), + "Source COC should not be present"); + assertCocCountAfterAutoGenerate(5); } private CompleteDataSetRegistration createCdsr( @@ -1785,28 +1711,13 @@ private CompleteDataSetRegistration createCdsr( private MergeParams getMergeParams() { MergeParams mergeParams = new MergeParams(); - mergeParams.setSources(UID.of(List.of(cocSource1.getUid(), cocSource2.getUid()))); + mergeParams.setSources(UID.of(List.of(cocDuplicate.getUid()))); mergeParams.setTarget(UID.of(cocTarget.getUid())); mergeParams.setDataMergeStrategy(DataMergeStrategy.DISCARD); mergeParams.setDeleteSources(true); return mergeParams; } - private CategoryOptionCombo getCocWithOptions(String co1, String co2) { - List allCategoryOptionCombos = - categoryService.getAllCategoryOptionCombos(); - - return allCategoryOptionCombos.stream() - .filter( - coc -> { - List categoryOptions = - coc.getCategoryOptions().stream().map(BaseIdentifiableObject::getName).toList(); - return categoryOptions.containsAll(List.of(co1, co2)); - }) - .toList() - .get(0); - } - private DataValueAudit createDataValueAudit(CategoryOptionCombo coc, String value, Period p) { DataValueAudit dva = new DataValueAudit(); dva.setDataElement(de1);