From b7bc6a918c53c2b6b34ce3bb5fe623724961b3a3 Mon Sep 17 00:00:00 2001 From: Tomek Date: Wed, 10 Jul 2024 10:14:02 +0200 Subject: [PATCH] OLMIS-7953 Copied solution with multiple program ids to make application logging performance better. (#113) * Cherry-pick (modified) from OAM-218 solution, commit 794d23f. OAM-218: Many program ids for GET API for orderables. (#109) * OAM-218: WIP * OAM-218: Change programId param to repeatable * OAM-218: Changed getting all orderables to get for many program codes. * OAM-213: Added fixes. * OAM-213: Changed to have multiple "program" API parameter. * OAM-213: Corrected tests. * OAM-213: Corrected some tests and added some constants. * OAM-213: Applied some corrections. * OAM-218: Name corrected. * OAM-218: Fix for tests. * OAM-218: Fix for test. * OAM-218: Fix for repository tests (by pwargulak). * OAM-218: Fixes after review. --------- Co-authored-by: Piotr Wargulak Co-authored-by: Szymon Radziszewski * OLMIS-7953: added test for coverage * OLMIS-7953: Added coverage test for orderable repository (in progress). * OLMIS-7953: Added coverage test for orderable repository (finished). * OLMIS-7953: huge coverage test * OLMIS-7953: Changed to stream solution after code review. * OLMIS-7953: Fixed correction. --------- Co-authored-by: Piotr Wargulak Co-authored-by: Szymon Radziszewski --- ...rovedProductRepositoryIntegrationTest.java | 92 ++++-- .../OrderableRepositoryIntegrationTest.java | 55 ++-- .../DataImportServiceIntegrationTest.java | 2 +- .../DataImportControllerIntegrationTest.java | 2 +- .../OrderableControllerIntegrationTest.java | 87 +++--- .../referencedata/dto/ApprovedProductDto.java | 2 +- .../referencedata/dto/OrderableDto.java | 6 +- .../custom/OrderableRepositoryCustom.java | 2 +- .../custom/impl/OrderableRepositoryImpl.java | 94 +++--- .../service/OrderableService.java | 5 +- .../export/OrderableImportPersister.java | 2 +- .../export/TradeItemImportPersister.java | 2 +- .../web/OrderableController.java | 75 ++--- .../web/OrderableSearchParams.java | 8 +- .../web/QueryOrderableSearchParams.java | 15 +- .../referencedata/web/SearchParams.java | 4 + src/main/resources/api-definition.yaml | 2 +- .../impl/OrderableRepositoryImplTest.java | 283 ++++++++++++++++++ .../service/OrderableServiceTest.java | 7 +- .../export/OrderableImportPersisterTest.java | 2 +- .../ProgramOrderableDataBuilder.java | 2 +- .../web/OrderableControllerTest.java | 109 +++++++ .../web/QueryOrderableSearchParamsTest.java | 46 ++- 23 files changed, 695 insertions(+), 209 deletions(-) create mode 100644 src/test/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImplTest.java create mode 100644 src/test/java/org/openlmis/referencedata/web/OrderableControllerTest.java diff --git a/src/integration-test/java/org/openlmis/referencedata/repository/FacilityTypeApprovedProductRepositoryIntegrationTest.java b/src/integration-test/java/org/openlmis/referencedata/repository/FacilityTypeApprovedProductRepositoryIntegrationTest.java index 44336f8fc..2fa2e9d7a 100644 --- a/src/integration-test/java/org/openlmis/referencedata/repository/FacilityTypeApprovedProductRepositoryIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/referencedata/repository/FacilityTypeApprovedProductRepositoryIntegrationTest.java @@ -164,7 +164,7 @@ public void setUp() { orderableDisplayCategoryRepository.save(orderableDisplayCategory); ProgramOrderable programOrderableFullSupply = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program) .withoutProduct() .buildAsNew(); @@ -177,7 +177,7 @@ public void setUp() { orderableRepository.saveAndFlush(orderableFullSupply); ProgramOrderable programOrderable1 = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program2) .withoutProduct() .buildAsNew(); @@ -190,7 +190,7 @@ public void setUp() { orderableRepository.save(orderable1); ProgramOrderable programOrderable2 = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program2) .withoutProduct() .buildAsNew(); @@ -203,7 +203,7 @@ public void setUp() { orderableRepository.save(orderable2); ProgramOrderable programOrderableNonFullSupply = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program) .withoutProduct() .asNonFullSupply() @@ -257,10 +257,16 @@ public void shouldGetFullAndNonFullSupply() { List orderableIds = emptyList(); - Page page = ftapRepository - .searchProducts(facility.getId(), program.getId(), null, orderableIds, null, - null, null, pageable - ); + Page page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + null, + orderableIds, + null, + null, + null, + pageable); assertThat(page.getContent(), hasSize(2)); } @@ -275,10 +281,16 @@ public void shouldGetFullAndNonFullSupplyFilteredByOrderableIds() { List orderableIds = Lists .newArrayList(orderableFullSupply.getId(), orderableNonFullSupply.getId()); - Page page = ftapRepository - .searchProducts(facility.getId(), program.getId(), null, orderableIds, null, - null, null, pageable - ); + Page page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + null, + orderableIds, + null, + null, + null, + pageable); assertThat(page.getContent(), hasSize(2)); assertEquals(page.getContent().get(0).getOrderableId(), orderableFullSupply.getId()); @@ -295,10 +307,16 @@ public void shouldPaginate() { pageable = PageRequest.of(0, 1); List orderableIds = emptyList(); - Page page = ftapRepository - .searchProducts(facility.getId(), program.getId(), null, orderableIds, null, - null, null, pageable - ); + Page page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + null, + orderableIds, + null, + null, + null, + pageable); assertThat(page.getContent(), hasSize(1)); @@ -312,10 +330,16 @@ public void shouldGetFullSupply() { List orderableIds = emptyList(); - Page page = ftapRepository - .searchProducts(facility.getId(), program.getId(), true, orderableIds, null, - null, null, pageable - ); + Page page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + true, + orderableIds, + null, + null, + null, + pageable); assertThat(page.getContent(), hasSize(1)); @@ -342,10 +366,16 @@ public void shouldGetNonFullSupply() { List orderableIds = emptyList(); - Page page = ftapRepository - .searchProducts(facility.getId(), program.getId(), false, orderableIds, null, - null, null, pageable - ); + Page page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + false, + orderableIds, + null, + null, + null, + pageable); // At this point we have no non-full supply products assertEquals(0, page.getContent().size()); @@ -353,10 +383,16 @@ public void shouldGetNonFullSupply() { // Create a non-full supply product saveAndGetProduct(facilityType1, false); - page = ftapRepository - .searchProducts(facility.getId(), program.getId(), false, orderableIds, null, - null, null, pageable - ); + page = + ftapRepository.searchProducts( + facility.getId(), + program.getId(), + false, + orderableIds, + null, + null, + null, + pageable); // We should be able to find non-full supply product we have created assertEquals(1, page.getContent().size()); diff --git a/src/integration-test/java/org/openlmis/referencedata/repository/OrderableRepositoryIntegrationTest.java b/src/integration-test/java/org/openlmis/referencedata/repository/OrderableRepositoryIntegrationTest.java index 991f2a50e..845cf8046 100644 --- a/src/integration-test/java/org/openlmis/referencedata/repository/OrderableRepositoryIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/referencedata/repository/OrderableRepositoryIntegrationTest.java @@ -91,24 +91,18 @@ public class OrderableRepositoryIntegrationTest { private static final String EACH = "each"; private static final String ORDERABLE_NAME = "abc"; private static final String SOME_CODE = "some-code"; - + private final AtomicInteger instanceNumber = new AtomicInteger(0); + private final PageRequest pageable = PageRequest.of(0, Integer.MAX_VALUE, Sort.Direction.ASC, + "fullProductName"); @Autowired private OrderableRepository repository; - @Autowired private ProgramRepository programRepository; - @Autowired private OrderableDisplayCategoryRepository orderableDisplayCategoryRepository; - @Autowired private EntityManager entityManager; - private AtomicInteger instanceNumber = new AtomicInteger(0); - - private PageRequest pageable = PageRequest.of(0, Integer.MAX_VALUE, Sort.Direction.ASC, - "fullProductName"); - private int getNextInstanceNumber() { return this.instanceNumber.incrementAndGet(); } @@ -123,13 +117,13 @@ public void shouldNotAllowForDuplicatedActiveProgramOrderables() { Orderable orderable = new OrderableDataBuilder().buildAsNew(); ProgramOrderable programOrderable = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program) .withProduct(orderable) .buildAsNew(); ProgramOrderable programOrderableDuplicated = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory2) + .withOrderableDisplayCategory(orderableDisplayCategory2) .withProgram(program) .withProduct(orderable) .buildAsNew(); @@ -154,13 +148,13 @@ public void shouldAllowForDuplicatedProgramOrderablesIfTheyAreInactive() { Orderable orderable = new OrderableDataBuilder().buildAsNew(); ProgramOrderable programOrderable = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program) .withProduct(orderable) .buildAsNew(); ProgramOrderable programOrderableDuplicated = new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory2) + .withOrderableDisplayCategory(orderableDisplayCategory2) .withProgram(program) .withProduct(orderable) .asInactive() @@ -344,7 +338,7 @@ public void shouldFindOrderablesByProgram() { // when Page foundOrderables = repository.search( - new TestSearchParams(null, null, programCode, null), + new TestSearchParams(null, null, Collections.singleton(programCode), null), pageable); // then @@ -403,7 +397,7 @@ public void shouldFindOrderablesByAllParams() { Page foundOrderables = repository.search( new TestSearchParams( validOrderable.getProductCode().toString(), NAME, - validProgram.getCode().toString(), null), + Collections.singleton(validProgram.getCode().toString()), null), pageable); // then @@ -431,8 +425,8 @@ public void shouldFindFirstByVersionNumberAndProductCodeIgnoreCase() { String lowercaseCode = uppercaseCode.toLowerCase(); Orderable orderable = new OrderableDataBuilder() - .withProductCode(Code.code(uppercaseCode)) - .buildAsNew(); + .withProductCode(Code.code(uppercaseCode)) + .buildAsNew(); assertNull(repository.findFirstByVersionNumberAndProductCodeIgnoreCase(lowercaseCode, 1L)); assertNull(repository.findFirstByVersionNumberAndProductCodeIgnoreCase(uppercaseCode, 1L)); @@ -471,11 +465,11 @@ public void shouldNotAllowProductCodeDuplicateCaseInsensitive() { String productCode = "abcdef"; Orderable orderable1 = new OrderableDataBuilder() - .withProductCode(Code.code(productCode)) - .buildAsNew(); + .withProductCode(Code.code(productCode)) + .buildAsNew(); Orderable orderable2 = new OrderableDataBuilder() - .withProductCode(Code.code(productCode.toLowerCase())) - .buildAsNew(); + .withProductCode(Code.code(productCode.toLowerCase())) + .buildAsNew(); repository.save(orderable1); repository.save(orderable2); @@ -711,7 +705,7 @@ public void shouldFindLastUpdatedDateFromOrderablesRetrievedByIds() { Timestamp timestamp = repository.findLatestModifiedDateByIds(ids); ZonedDateTime lastUpdated = ZonedDateTime.of(timestamp.toLocalDateTime(), - ZoneId.of(ZoneId.systemDefault().toString())); + ZoneId.of(ZoneId.systemDefault().toString())); //then assertEquals(lastUpdated, orderable3.getLastUpdated()); @@ -733,7 +727,7 @@ public void shouldFindLastUpdatedDateFromAllOrderables() { //when Timestamp timestamp = repository.findLatestModifiedDateOfAll(); ZonedDateTime lastUpdated = ZonedDateTime.of(timestamp.toLocalDateTime(), - ZoneId.of(ZoneId.systemDefault().toString())); + ZoneId.of(ZoneId.systemDefault().toString())); //then assertEquals(lastUpdated, orderable3.getLastUpdated()); @@ -757,8 +751,8 @@ public void shouldFindLastUpdatedDateFromOrderablesRetrievedByParams() { new TestSearchParams(orderable3.getProductCode().toString(), orderable3.getFullProductName(), null, Sets.newHashSet(Pair.of(orderable1.getId(), orderable1.getVersionNumber()), - Pair.of(orderable2.getId(), orderable2.getVersionNumber()), - Pair.of(orderable3.getId(), orderable3.getVersionNumber())))); + Pair.of(orderable2.getId(), orderable2.getVersionNumber()), + Pair.of(orderable3.getId(), orderable3.getVersionNumber())))); //then assertEquals(lastUpdated, orderable3.getLastUpdated().withZoneSameLocal(ZoneId.of("GMT"))); @@ -783,7 +777,7 @@ public void shouldReturnOrderableWitAllProgramsWhenSearchingByProgramCode() { // when Page foundOrderables = repository.search( - new TestSearchParams(null, null, programCode, null), + new TestSearchParams(null, null, Collections.singleton(programCode), null), pageable); // then @@ -799,10 +793,11 @@ public void shouldReturnOrderableWitAllProgramsWhenSearchingByProgramCode() { } private void searchOrderablesAndCheckResults(String code, String name, Program program, - Orderable orderable, int expectedSize) { + Orderable orderable, int expectedSize) { String programCode = null == program ? null : program.getCode().toString(); Page foundOrderables = repository - .search(new TestSearchParams(code, name, programCode, null), pageable); + .search(new TestSearchParams(code, name, Collections.singleton(programCode), null), + pageable); assertEquals(expectedSize, foundOrderables.getTotalElements()); @@ -817,7 +812,7 @@ private ProgramOrderable createProgramOrderable(Program program, Orderable order "some-code"); return new ProgramOrderableDataBuilder() - .withOrderabeDisplayCategory(orderableDisplayCategory) + .withOrderableDisplayCategory(orderableDisplayCategory) .withProgram(program) .withProduct(orderable) .buildAsNew(); @@ -895,7 +890,7 @@ private static final class TestSearchParams implements SearchParams { private String code; private String name; - private String programCode; + private Set programCodes; private Set> identityPairs; @Override diff --git a/src/integration-test/java/org/openlmis/referencedata/service/DataImportServiceIntegrationTest.java b/src/integration-test/java/org/openlmis/referencedata/service/DataImportServiceIntegrationTest.java index 256e624a3..bee342d35 100644 --- a/src/integration-test/java/org/openlmis/referencedata/service/DataImportServiceIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/referencedata/service/DataImportServiceIntegrationTest.java @@ -421,7 +421,7 @@ private OrderableDisplayCategory createAndPersistOrderableDisplayCategory(String private ProgramOrderable createAndPersistProgramOrderable( Program program, Orderable orderable, OrderableDisplayCategory orderableDisplayCategory) { ProgramOrderable programOrderable = new ProgramOrderableDataBuilder() - .withProgram(program).withProduct(orderable).withOrderabeDisplayCategory( + .withProgram(program).withProduct(orderable).withOrderableDisplayCategory( orderableDisplayCategory).buildAsNew(); programOrderableRepository.saveAndFlush(programOrderable); return programOrderable; diff --git a/src/integration-test/java/org/openlmis/referencedata/web/DataImportControllerIntegrationTest.java b/src/integration-test/java/org/openlmis/referencedata/web/DataImportControllerIntegrationTest.java index 7b94d27a0..b2a3ee22e 100644 --- a/src/integration-test/java/org/openlmis/referencedata/web/DataImportControllerIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/referencedata/web/DataImportControllerIntegrationTest.java @@ -40,7 +40,7 @@ public class DataImportControllerIntegrationTest extends BaseWebIntegrationTest private static final MultipartFile file = new MockMultipartFile( "orderable.csv", "test-data".getBytes()); private final Orderable orderable = new OrderableDataBuilder().build(); - private final OrderableDto orderableDto = OrderableDto.newInstance(orderable); + private final OrderableDto orderableDto = OrderableDto.newInstances(orderable); @Before @Override diff --git a/src/integration-test/java/org/openlmis/referencedata/web/OrderableControllerIntegrationTest.java b/src/integration-test/java/org/openlmis/referencedata/web/OrderableControllerIntegrationTest.java index 81009c788..b1c93e7d4 100644 --- a/src/integration-test/java/org/openlmis/referencedata/web/OrderableControllerIntegrationTest.java +++ b/src/integration-test/java/org/openlmis/referencedata/web/OrderableControllerIntegrationTest.java @@ -23,6 +23,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -463,7 +464,7 @@ public void shouldRetrieveAllOrderables() { .header(HttpHeaders.LAST_MODIFIED, modifiedDate.format(RFC_7231_FORMAT)) .extract().as(PageDto.class); - checkIfEquals(response, OrderableDto.newInstance(items)); + checkIfEquals(response, OrderableDto.newInstances(items)); assertThat(RAML_ASSERT_MESSAGE, restAssured.getLastReport(), RamlMatchers.hasNoViolations()); } @@ -491,7 +492,7 @@ public void shouldRetrieveAllOrderablesIfAnyResourceWasModified() { .header(HttpHeaders.LAST_MODIFIED, modifiedDate.format(RFC_7231_FORMAT)) .extract().as(PageDto.class); - checkIfEquals(response, OrderableDto.newInstance(items)); + checkIfEquals(response, OrderableDto.newInstances(items)); assertThat(RAML_ASSERT_MESSAGE, restAssured.getLastReport(), RamlMatchers.hasNoViolations()); } @@ -512,7 +513,7 @@ public void shouldReturnEmptyPageIfNoOrderableWithLastUpdatedDateWasFound() { .statusCode(HttpStatus.SC_OK) .extract().as(PageDto.class); - checkIfEquals(response, OrderableDto.newInstance(Collections.emptyList())); + checkIfEquals(response, OrderableDto.newInstances(Collections.emptyList())); assertThat(RAML_ASSERT_MESSAGE, restAssured.getLastReport(), RamlMatchers.hasNoViolations()); } @@ -545,7 +546,8 @@ public void shouldReturnNotModifiedAndNoResponseBodyIfNoOrderableWasModified() { public void shouldSearchOrderables() { final String code = "some-code"; final String name = "some-name"; - final String programCode = "program-code"; + final String programCode1 = "program-code1"; + final String programCode2 = "program-code2"; final List items = Collections.singletonList(orderable); UUID orderableId2 = UUID.randomUUID(); @@ -563,7 +565,8 @@ public void shouldSearchOrderables() { .header(HttpHeaders.AUTHORIZATION, getTokenHeader()) .parameter(CODE, code) .parameter(NAME, name) - .parameter(PROGRAM_CODE, programCode) + .parameter(PROGRAM_CODE, programCode1) + .parameter(PROGRAM_CODE, programCode2) .parameter(ID, orderableId) .parameter(ID, orderableId2) .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -574,7 +577,7 @@ public void shouldSearchOrderables() { .header(HttpHeaders.LAST_MODIFIED, modifiedDate.format(RFC_7231_FORMAT)) .extract().as(PageDto.class); - checkIfEquals(response, OrderableDto.newInstance(items)); + checkIfEquals(response, OrderableDto.newInstances(items)); verify(orderableService) .searchOrderables(searchParamsArgumentCaptor.capture(), any(Pageable.class)); @@ -582,7 +585,10 @@ public void shouldSearchOrderables() { QueryOrderableSearchParams value = searchParamsArgumentCaptor.getValue(); assertEquals(code, value.getCode()); assertEquals(name, value.getName()); - assertEquals(programCode, value.getProgramCode()); + Set programCodes = new HashSet<>(); + programCodes.add(programCode1); + programCodes.add(programCode2); + assertEquals(programCodes, value.getProgramCodes()); assertEquals(new HashSet<>(Arrays.asList(orderableId, orderableId2)), value.getIds()); assertThat(RAML_ASSERT_MESSAGE, restAssured.getLastReport(), RamlMatchers.hasNoViolations()); @@ -751,14 +757,14 @@ public void shouldPostSearchOrderables() { orderableDto.getId(), orderableDto.getVersionNumber())), 0, 10); - given(orderableRepository - .search(eq(searchParams), any(Pageable.class))) - .willReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))); + doReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))) + .when(orderableRepository) + .search(eq(searchParams), any(Pageable.class)); + + doReturn(modifiedDate) + .when(orderableService) + .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class)); - when(orderableService - .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), - any(Profiler.class))) - .thenReturn(modifiedDate); PageDto response = restAssured .given() @@ -787,13 +793,13 @@ public void shouldPostSearchOrderablesIfAnyResourceWasModified() { orderableDto.getId(), orderableDto.getVersionNumber())), 0, 10); - given(orderableRepository - .search(eq(searchParams), any(Pageable.class))) - .willReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))); + doReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))) + .when(orderableRepository) + .search(eq(searchParams), any(Pageable.class)); - when(orderableService - .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class))) - .thenReturn(modifiedDate); + doReturn(modifiedDate) + .when(orderableService) + .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class)); PageDto response = restAssured .given() @@ -823,13 +829,13 @@ public void shouldReturnNotModifiedAndNoResponseBodyWhenNoResourcesWereModified( orderableDto.getId(), orderableDto.getVersionNumber())), 0, 10); - given(orderableRepository - .search(eq(searchParams), any(Pageable.class))) - .willReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))); + doReturn(Pagination.getPage(Lists.newArrayList(orderable), PageRequest.of(0, 10))) + .when(orderableRepository) + .search(eq(searchParams), any(Pageable.class)); - when(orderableService - .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class))) - .thenReturn(modifiedDate); + doReturn(modifiedDate) + .when(orderableService) + .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class)); restAssured .given() @@ -855,13 +861,13 @@ public void postSearchShouldReturnEmptyPageWhenNoOrderablesFound() { orderableDto.getId(), orderableDto.getVersionNumber())), 0, 10); - given(orderableRepository - .search(eq(searchParams), any(Pageable.class))) - .willReturn(Pagination.getPage(Lists.newArrayList(), PageRequest.of(0, 10))); + doReturn(Pagination.getPage(Lists.newArrayList(), PageRequest.of(0, 10))) + .when(orderableRepository) + .search(eq(searchParams), any(Pageable.class)); - when(orderableService - .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class))) - .thenReturn(modifiedDate); + doReturn(modifiedDate) + .when(orderableService) + .getLatestLastUpdatedDate(any(QueryOrderableSearchParams.class), any(Profiler.class)); PageDto response = restAssured .given() @@ -875,7 +881,7 @@ public void postSearchShouldReturnEmptyPageWhenNoOrderablesFound() { .header(HttpHeaders.LAST_MODIFIED, modifiedDate.format(RFC_7231_FORMAT)) .extract().as(PageDto.class); - checkIfEquals(response, OrderableDto.newInstance(Collections.emptyList())); + checkIfEquals(response, OrderableDto.newInstances(Collections.emptyList())); assertThat(RAML_ASSERT_MESSAGE, restAssured.getLastReport(), RamlMatchers.hasNoViolations()); } @@ -938,16 +944,17 @@ private void checkIfEquals(PageDto response, List expected) { List pageContent = response.getContent(); assertEquals(expected.size(), pageContent.size()); for (int i = 0; i < pageContent.size(); i++) { - Map retrieved = (LinkedHashMap) pageContent.get(i); - assertEquals(expected.get(i).getFullProductName(), + Map retrieved = (LinkedHashMap) pageContent.get(i); + OrderableDto expectedOrderableDto = expected.get(i); + assertEquals(expectedOrderableDto.getFullProductName(), retrieved.get("fullProductName")); - assertEquals(expected.get(i).getProductCode(), + assertEquals(expectedOrderableDto.getProductCode(), retrieved.get("productCode")); - assertEquals(expected.get(i).getNetContent().intValue(), + assertEquals(expectedOrderableDto.getNetContent().intValue(), retrieved.get("netContent")); - assertEquals(expected.get(i).getPackRoundingThreshold().intValue(), + assertEquals(expectedOrderableDto.getPackRoundingThreshold().intValue(), retrieved.get("packRoundingThreshold")); - assertEquals(expected.get(i).getRoundToZero(), + assertEquals(expectedOrderableDto.getRoundToZero(), retrieved.get("roundToZero")); } } @@ -987,7 +994,7 @@ private void mockDecreasingResponseTime(int requestCount) { return orderable; }); } - + private OrderableChildDto getChildFromOrderableDto(OrderableDto orderableDto) { if (orderableDto.getChildren().size() > 0) { return orderableDto.getChildren().iterator().next(); diff --git a/src/main/java/org/openlmis/referencedata/dto/ApprovedProductDto.java b/src/main/java/org/openlmis/referencedata/dto/ApprovedProductDto.java index 7f5224998..ec466f071 100644 --- a/src/main/java/org/openlmis/referencedata/dto/ApprovedProductDto.java +++ b/src/main/java/org/openlmis/referencedata/dto/ApprovedProductDto.java @@ -72,7 +72,7 @@ public void setOrderable(OrderableDto orderable) { @JsonIgnore public void setOrderable(Orderable orderable) { - this.orderable = OrderableDto.newInstance(orderable); + this.orderable = OrderableDto.newInstances(orderable); } @Override diff --git a/src/main/java/org/openlmis/referencedata/dto/OrderableDto.java b/src/main/java/org/openlmis/referencedata/dto/OrderableDto.java index 9bb88b7eb..919a61ee0 100644 --- a/src/main/java/org/openlmis/referencedata/dto/OrderableDto.java +++ b/src/main/java/org/openlmis/referencedata/dto/OrderableDto.java @@ -95,9 +95,9 @@ public final class OrderableDto extends BaseDto implements Orderable.Importer, * @param orderables list of {@link Orderable} * @return new list of OrderableDto. */ - public static List newInstance(Iterable orderables) { + public static List newInstances(Iterable orderables) { List orderableDtos = new LinkedList<>(); - orderables.forEach(oe -> orderableDtos.add(newInstance(oe))); + orderables.forEach(oe -> orderableDtos.add(newInstances(oe))); return orderableDtos; } @@ -107,7 +107,7 @@ public static List newInstance(Iterable orderables) { * @param po instance of Orderable. * @return new instance of OrderableDto. */ - public static OrderableDto newInstance(Orderable po) { + public static OrderableDto newInstances(Orderable po) { if (po == null) { return null; } diff --git a/src/main/java/org/openlmis/referencedata/repository/custom/OrderableRepositoryCustom.java b/src/main/java/org/openlmis/referencedata/repository/custom/OrderableRepositoryCustom.java index 18e3acebd..0ade3deca 100644 --- a/src/main/java/org/openlmis/referencedata/repository/custom/OrderableRepositoryCustom.java +++ b/src/main/java/org/openlmis/referencedata/repository/custom/OrderableRepositoryCustom.java @@ -35,7 +35,7 @@ interface SearchParams { String getName(); - String getProgramCode(); + Set getProgramCodes(); Set> getIdentityPairs(); diff --git a/src/main/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImpl.java b/src/main/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImpl.java index d4ede9785..6dafb7834 100644 --- a/src/main/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImpl.java +++ b/src/main/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImpl.java @@ -27,6 +27,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -64,54 +66,42 @@ public class OrderableRepositoryImpl extends IdentitiesSearchableRepository implements OrderableRepositoryCustom { + static final String FULL_PRODUCT_NAME = "fullProductName"; + static final String PRODUCT_CODE = "productCode"; + static final String VERSION_NUMBER = "versionNumber"; + static final String ID = "id"; + static final String PROGRAM_ORDERABLES = "programOrderables"; + static final String IDENTITY = "identity"; + static final String PROGRAM = "program"; + static final String CODE = "code"; private static final XLogger XLOGGER = XLoggerFactory.getXLogger(OrderableRepositoryImpl.class); - private static final String FROM_ORDERABLES_TABLE = " FROM referencedata.orderables AS o"; - private static final String NATIVE_PROGRAM_ORDERABLE_JOIN = " JOIN referencedata.program_orderables AS po" + " ON o.id = po.orderableId AND o.versionNumber = po.orderableVersionNumber"; - private static final String NATIVE_PROGRAM_ORDERABLE_INNER_JOIN = " INNER" + NATIVE_PROGRAM_ORDERABLE_JOIN; - private static final String NATIVE_PROGRAM_JOIN = " JOIN referencedata.programs AS p" + " ON p.id = po.programId"; - private static final String NATIVE_PROGRAM_INNER_JOIN = " INNER" + NATIVE_PROGRAM_JOIN; - private static final String NATIVE_LATEST_ORDERABLE_INNER_JOIN = " INNER JOIN (SELECT id, MAX (versionNumber) AS versionNumber" + " FROM referencedata.orderables GROUP BY id) AS latest" + " ON o.id = latest.id AND o.versionNumber = latest.versionNumber"; - - private static final String NATIVE_SELECT_LAST_UPDATED = "SELECT o.lastupdated " + static final String NATIVE_SELECT_LAST_UPDATED = "SELECT o.lastupdated " + FROM_ORDERABLES_TABLE + NATIVE_LATEST_ORDERABLE_INNER_JOIN; - - private static final String NATIVE_COUNT_LAST_UPDATED = "SELECT COUNT(*) " + static final String NATIVE_COUNT_LAST_UPDATED = "SELECT COUNT(*) " + FROM_ORDERABLES_TABLE + NATIVE_LATEST_ORDERABLE_INNER_JOIN; - private static final String ORDER_BY_LAST_UPDATED_DESC_LIMIT_1 = " ORDER BY o.lastupdated" + " DESC LIMIT 1"; - private static final String WHERE = " WHERE "; private static final String AND = " AND "; - private static final String ID = "id"; - private static final String IDENTITY = "identity"; private static final String GMT = "GMT"; - private static final String VERSION_NUMBER = "versionNumber"; - private static final String FULL_PRODUCT_NAME = "fullProductName"; - private static final String PROGRAM = "program"; - private static final String CODE = "code"; private static final String ORDERABLE = "orderable"; - private static final String PROGRAM_ORDERABLES = "programOrderables"; - private static final String PRODUCT_CODE = "productCode"; private static final String LATEST_ORDERABLE_ALIAS = "latest"; - private static final String TRADE_ITEM = "tradeItem"; - @PersistenceContext private EntityManager entityManager; @Autowired @@ -147,7 +137,7 @@ public Page search(SearchParams searchParams, Pageable pageable) { if (total < 1) { profiler.stop().log(); - return Pagination.getPage(Collections.emptyList(), pageable,0); + return Pagination.getPage(Collections.emptyList(), pageable, 0); } profiler.start("GET_VERSION_IDENTITY"); @@ -192,7 +182,8 @@ public ZonedDateTime findLatestModifiedDateByParams(SearchParams searchParams) { @Override TypedQuery prepareQuery(SearchParams searchParams, CriteriaQuery query, - boolean count, Collection identities, Pageable pageable) { + boolean count, Collection identities, + Pageable pageable) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); Root root = query.from(Orderable.class); @@ -228,23 +219,25 @@ TypedQuery prepareQuery(SearchParams searchParams, CriteriaQuery query } private Predicate prepareParams(Root root, CriteriaQuery query, - SearchParams searchParams, Collection identities) { + SearchParams searchParams, + Collection identities) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); Predicate where = builder.conjunction(); if (null != searchParams) { - if (null != searchParams.getProgramCode()) { + Set programCodes = getProgramCodesLowerCase(searchParams); + if (!programCodes.isEmpty()) { Join poJoin = root.join(PROGRAM_ORDERABLES, JoinType.INNER); Join programJoin = poJoin.join(PROGRAM, JoinType.INNER); - where = builder.and(where, builder.equal(builder.lower(programJoin.get(CODE).get(CODE)), - searchParams.getProgramCode().toLowerCase())); + where = builder.and(where, builder.lower(programJoin.get(CODE).get(CODE)) + .in(programCodes)); } if (isEmpty(identities)) { Subquery latestOrderablesQuery = createSubQuery(query, builder); where = builder.and(where, builder.in(builder.concat( - root.get(IDENTITY).get(ID).as(String.class), - root.get(IDENTITY).get(VERSION_NUMBER)).as(String.class)) + root.get(IDENTITY).get(ID).as(String.class), + root.get(IDENTITY).get(VERSION_NUMBER)).as(String.class)) .value(latestOrderablesQuery)); } else { where = builder.and(where, builder.in(root.get(IDENTITY)).value(identities)); @@ -262,14 +255,24 @@ private Predicate prepareParams(Root root, CriteriaQuery query } else { Subquery latestOrderablesQuery = createSubQuery(query, builder); where = builder.and(where, builder.in(builder.concat( - root.get(IDENTITY).get(ID).as(String.class), - root.get(IDENTITY).get(VERSION_NUMBER)).as(String.class)) + root.get(IDENTITY).get(ID).as(String.class), + root.get(IDENTITY).get(VERSION_NUMBER)).as(String.class)) .value(latestOrderablesQuery)); } return where; } + private Set getProgramCodesLowerCase(SearchParams searchParams) { + return Optional.ofNullable(searchParams) + .map(SearchParams::getProgramCodes) + .orElse(Collections.emptySet()) + .stream() + .filter(Objects::nonNull) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + private Subquery createSubQuery(CriteriaQuery query, CriteriaBuilder builder) { Subquery latestOrderablesQuery = query.subquery(String.class); Root latestOrderablesRoot = latestOrderablesQuery.from(Orderable.class); @@ -291,10 +294,11 @@ private Query getLastUpdatedQuery(SearchParams searchParams, boolean count) { String queryCondition; if (null != searchParams) { - if (null != searchParams.getProgramCode()) { + Set programCodes = getProgramCodesLowerCase(searchParams); + if (!isEmpty(programCodes)) { builder.append(NATIVE_PROGRAM_ORDERABLE_INNER_JOIN + NATIVE_PROGRAM_INNER_JOIN); - queryCondition = "LOWER (p.code) LIKE '%" - + searchParams.getProgramCode().toLowerCase() + "%'"; + queryCondition = "LOWER (p.code) IN (" + + generateProgramCodesText(programCodes) + ")"; wheres.add(queryCondition); } @@ -318,8 +322,23 @@ private Query getLastUpdatedQuery(SearchParams searchParams, boolean count) { if (!count) { builder.append(ORDER_BY_LAST_UPDATED_DESC_LIMIT_1); } - XLOGGER.info("QueryParamString: " + builder.toString()); - return entityManager.createNativeQuery(builder.toString()); + String builderText = builder.toString(); + XLOGGER.info("QueryParamString: " + builderText); + return entityManager.createNativeQuery(builderText); + } + + private String generateProgramCodesText(Set programCodesLowerCase) { + + return programCodesLowerCase.stream() + .map(programCode -> { + StringBuilder builder = new StringBuilder(); + builder + .append('\'') + .append(programCode) + .append('\''); + return builder.toString(); + }) + .collect(Collectors.joining(", ")); } private List retrieveOrderables(Collection identities) { @@ -347,6 +366,7 @@ private List retrieveOrderables(CriteriaQuery criteriaQuer /** * Returns identity pairs which correspond to supplied trade item ids. + * * @param tradeItemId Ids of trade items * @return Identity pairs matching supplied trade item ids */ diff --git a/src/main/java/org/openlmis/referencedata/service/OrderableService.java b/src/main/java/org/openlmis/referencedata/service/OrderableService.java index 548934428..df74eb874 100644 --- a/src/main/java/org/openlmis/referencedata/service/OrderableService.java +++ b/src/main/java/org/openlmis/referencedata/service/OrderableService.java @@ -73,9 +73,10 @@ public Page searchOrderables(@NotNull QueryOrderableSearchParams quer if (LOGGER.isInfoEnabled()) { String code = queryMap.getCode(); String name = queryMap.getName(); - String programCode = queryMap.getProgramCode(); + Set programCodes = queryMap.getProgramCodes(); - LOGGER.info("search by code {}, name {}, and program code {}", code, name, programCode); + LOGGER.info("search by code {}, name {}, and program codes: {}", code, name, + programCodes.toArray(new String[programCodes.size()])); } return orderableRepository.search(queryMap, pageable); diff --git a/src/main/java/org/openlmis/referencedata/service/export/OrderableImportPersister.java b/src/main/java/org/openlmis/referencedata/service/export/OrderableImportPersister.java index ccb575ef9..4359e627c 100644 --- a/src/main/java/org/openlmis/referencedata/service/export/OrderableImportPersister.java +++ b/src/main/java/org/openlmis/referencedata/service/export/OrderableImportPersister.java @@ -46,7 +46,7 @@ public List processAndPersist(InputStream dataStream) { List persistedObjects = orderableRepository.saveAll( createOrUpdate(importedDtos)); - return OrderableDto.newInstance(persistedObjects); + return OrderableDto.newInstances(persistedObjects); } @Override diff --git a/src/main/java/org/openlmis/referencedata/service/export/TradeItemImportPersister.java b/src/main/java/org/openlmis/referencedata/service/export/TradeItemImportPersister.java index 4ebb8d993..5f6c2d933 100644 --- a/src/main/java/org/openlmis/referencedata/service/export/TradeItemImportPersister.java +++ b/src/main/java/org/openlmis/referencedata/service/export/TradeItemImportPersister.java @@ -59,7 +59,7 @@ public List processAndPersist(InputStream dataStream) { createOrUpdate(importedDtos) ); - return new ArrayList<>(OrderableDto.newInstance(persistedObjects)); + return new ArrayList<>(OrderableDto.newInstances(persistedObjects)); } @Override diff --git a/src/main/java/org/openlmis/referencedata/web/OrderableController.java b/src/main/java/org/openlmis/referencedata/web/OrderableController.java index b43c32deb..4a2055d77 100644 --- a/src/main/java/org/openlmis/referencedata/web/OrderableController.java +++ b/src/main/java/org/openlmis/referencedata/web/OrderableController.java @@ -22,7 +22,6 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; - import org.apache.commons.lang3.tuple.Pair; import org.openlmis.referencedata.domain.Orderable; import org.openlmis.referencedata.dto.OrderableDto; @@ -69,8 +68,11 @@ @RestController public class OrderableController extends BaseController { - private static final XLogger XLOGGER = XLoggerFactory.getXLogger(OrderableController.class); public static final String RESOURCE_PATH = "/orderables"; + private static final XLogger XLOGGER = XLoggerFactory.getXLogger(OrderableController.class); + private static final String NAME = "name"; + private static final String CODE = "code"; + private static final String PROGRAM_CODE = "program"; @Autowired private OrderableRepository repository; @@ -114,21 +116,21 @@ public ResponseEntity create( repository.save(orderable); OrderableCreatePostProcessor orderableCreatePostProcessor = extensionManager.getExtension( - ExtensionPointId.ORDERABLE_CREATE_POST_POINT_ID, OrderableCreatePostProcessor.class); + ExtensionPointId.ORDERABLE_CREATE_POST_POINT_ID, OrderableCreatePostProcessor.class); orderableCreatePostProcessor.process(orderable); profiler.stop().log(); return ResponseEntity.ok() - .headers(buildLastModifiedHeader(orderable.getLastUpdated())) - .body(OrderableDto.newInstance(orderable)); + .headers(buildLastModifiedHeader(orderable.getLastUpdated())) + .body(OrderableDto.newInstances(orderable)); } /** * Update an Orderable. * - * @param id the id of the Orderable to update. - * @param orderableDto the contents of how the Orderable should be updated. + * @param id the id of the Orderable to update. + * @param orderableDto the contents of how the Orderable should be updated. * @param bindingResult the result of validation. * @return the orderable that was updated. */ @@ -157,15 +159,15 @@ public ResponseEntity update( .save(orderableBuilder.newOrderable(orderableDto, foundOrderable)); OrderableUpdatePostProcessor orderableUpdatePostProcessor = extensionManager.getExtension( - ExtensionPointId.ORDERABLE_UPDATE_POST_POINT_ID, OrderableUpdatePostProcessor.class); + ExtensionPointId.ORDERABLE_UPDATE_POST_POINT_ID, OrderableUpdatePostProcessor.class); orderableUpdatePostProcessor.process(savedOrderable); XLOGGER.warn("Orderable updated: down stream services may not support versioned orderables: {}", id); return ResponseEntity.ok() - .headers(buildLastModifiedHeader(savedOrderable.getLastUpdated())) - .body(OrderableDto.newInstance(savedOrderable)); + .headers(buildLastModifiedHeader(savedOrderable.getLastUpdated())) + .body(OrderableDto.newInstances(savedOrderable)); } /** @@ -174,7 +176,7 @@ public ResponseEntity update( * param doesn't have value, it will search for empty value in database. * * @param queryParams request parameters (code, name, program, ids). - * @param pageable object used to encapsulate the pagination related values: page and size. + * @param pageable object used to encapsulate the pagination related values: page and size. * @return a page of orderables */ @GetMapping(RESOURCE_PATH) @@ -192,7 +194,7 @@ public ResponseEntity> findAll( if (lastUpdated == null) { Page emptyPage = Pagination.getPage(Collections.emptyList(), pageable, 0); return ResponseEntity.ok() - .body(emptyPage); + .body(emptyPage); } if (ifModifiedDate == null @@ -205,7 +207,7 @@ public ResponseEntity> findAll( profiler.start("ORDERABLE_PAGINATION"); Page page = Pagination.getPage( - OrderableDto.newInstance(orderablesPage.getContent()), + OrderableDto.newInstances(orderablesPage.getContent()), pageable, orderablesPage.getTotalElements()); @@ -232,7 +234,7 @@ public ResponseEntity> findAll( public ResponseEntity> searchOrderables( @RequestBody OrderableSearchParams body, @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) - String ifModifiedDate) { + String ifModifiedDate) { Profiler profiler = new Profiler("ORDERABLES_SEARCH_POST"); profiler.setLogger(XLOGGER); @@ -240,12 +242,12 @@ public ResponseEntity> searchOrderables( profiler.start("GET_LATEST_LAST_UPDATED_DATE"); ZonedDateTime lastUpdated = orderableService - .getLatestLastUpdatedDate(getQueryOrderableSearchParams(body), profiler); + .getLatestLastUpdatedDate(getQueryOrderableSearchParams(body), profiler); if (lastUpdated == null) { Page emptyPage = Pagination.getPage(Collections.emptyList(), pageable, 0); return ResponseEntity.ok() - .body(emptyPage); + .body(emptyPage); } if (ifModifiedDate == null @@ -255,7 +257,7 @@ public ResponseEntity> searchOrderables( profiler.start("EXPORT_TO_DTO"); Page page = Pagination.getPage( - OrderableDto.newInstance(orderablesPage.getContent()), + OrderableDto.newInstances(orderablesPage.getContent()), pageable, orderablesPage.getTotalElements()); @@ -283,7 +285,7 @@ public ResponseEntity getChosenOrderable( @PathVariable("id") UUID productId, @RequestParam(required = false, value = "versionNumber") Long versionNumber, @RequestHeader(value = HttpHeaders.IF_MODIFIED_SINCE, required = false) - String ifModifiedDate) { + String ifModifiedDate) { Profiler profiler = new Profiler("GET_ORDERABLE"); profiler.setLogger(XLOGGER); @@ -303,22 +305,25 @@ public ResponseEntity getChosenOrderable( return (ifModifiedDate == null || orderable.wasModifiedSince(parseHttpDateToZonedDateTime(ifModifiedDate))) ? ResponseEntity.ok() - .headers(buildLastModifiedHeader(orderable.getLastUpdated())) - .body(OrderableDto.newInstance(orderable)) + .headers(buildLastModifiedHeader(orderable.getLastUpdated())) + .body(OrderableDto.newInstances(orderable)) : ResponseEntity.status(HttpStatus.NOT_MODIFIED) - .headers(buildLastModifiedHeader(orderable.getLastUpdated())) - .build(); + .headers(buildLastModifiedHeader(orderable.getLastUpdated())) + .build(); } } /** * Get the audit information related to orderable. - * @param author The author of the changes which should be returned. - * If null or empty, changes are returned regardless of author. + * + * @param author The author of the changes which should be returned. + * If null or empty, changes are returned regardless of author. * @param changedPropertyName The name of the property about which changes should be returned. - * If null or empty, changes associated with any and all properties are returned. - * @param page A Pageable object that allows client to optionally add "page" (page number) - * and "size" (page size) query parameters to the request. + * If null or empty, changes associated with any and all properties + * are returned. + * @param page A Pageable object that allows client to optionally add "page" + * (page number) + * and "size" (page size) query parameters to the request. */ @RequestMapping(value = RESOURCE_PATH + "/{id}/auditLog", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) @@ -327,10 +332,10 @@ public ResponseEntity getOrderableAuditLog( @PathVariable("id") UUID id, @RequestParam(name = "author", required = false, defaultValue = "") String author, @RequestParam(name = "changedPropertyName", required = false, defaultValue = "") - String changedPropertyName, + String changedPropertyName, //Because JSON is all we formally support, returnJSON is excluded from our JavaDoc @RequestParam(name = "returnJSON", required = false, defaultValue = "true") - boolean returnJson, + boolean returnJson, Pageable page) { rightService.checkAdminRight(ORDERABLES_MANAGE); @@ -357,9 +362,9 @@ private QueryOrderableSearchParams getQueryOrderableSearchParams( OrderableSearchParams searchParams) { Set ids = searchParams.getIdentityPairs() - .stream() - .map(Pair::getKey) - .collect(Collectors.toSet()); + .stream() + .map(Pair::getKey) + .collect(Collectors.toSet()); LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); @@ -368,9 +373,9 @@ private QueryOrderableSearchParams getQueryOrderableSearchParams( queryMap.add("id", id.toString()); } } - queryMap.add("name", searchParams.getName()); - queryMap.add("code", searchParams.getCode()); - queryMap.add("program", searchParams.getProgramCode()); + queryMap.add(NAME, searchParams.getName()); + queryMap.add(CODE, searchParams.getCode()); + queryMap.add(PROGRAM_CODE, searchParams.getProgramCode()); return new QueryOrderableSearchParams(queryMap); } diff --git a/src/main/java/org/openlmis/referencedata/web/OrderableSearchParams.java b/src/main/java/org/openlmis/referencedata/web/OrderableSearchParams.java index 1149aaf43..950a537df 100644 --- a/src/main/java/org/openlmis/referencedata/web/OrderableSearchParams.java +++ b/src/main/java/org/openlmis/referencedata/web/OrderableSearchParams.java @@ -16,12 +16,10 @@ package org.openlmis.referencedata.web; import com.fasterxml.jackson.annotation.JsonIgnore; - import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; - import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -67,4 +65,10 @@ String getInvalidVersionIdentityErrorMessage() { public Set getTradeItemId() { return Collections.emptySet(); } + + @Override + @JsonIgnore + public Set getProgramCodes() { + return Collections.singleton(programCode); + } } diff --git a/src/main/java/org/openlmis/referencedata/web/QueryOrderableSearchParams.java b/src/main/java/org/openlmis/referencedata/web/QueryOrderableSearchParams.java index ac9c26d32..f1f9c3780 100644 --- a/src/main/java/org/openlmis/referencedata/web/QueryOrderableSearchParams.java +++ b/src/main/java/org/openlmis/referencedata/web/QueryOrderableSearchParams.java @@ -87,18 +87,15 @@ public String getName() { } /** - * Gets program code. + * Gets program codes. * - * @return {@link Code} value of program code or null if params doesn't contain "programCode" - * param. Empty Code for request param that has no value. + * @return {@link Code} values of program codes or empty collection if params doesn't contain + * "programCodes" param. No program code is included if a program code + * in request param is blank. */ @Override - public String getProgramCode() { - if (!queryParams.containsKey(PROGRAM_CODE)) { - return null; - } - - return defaultIfBlank(queryParams.getFirst(PROGRAM_CODE), EMPTY); + public Set getProgramCodes() { + return queryParams.getStrings(PROGRAM_CODE); } @Override diff --git a/src/main/java/org/openlmis/referencedata/web/SearchParams.java b/src/main/java/org/openlmis/referencedata/web/SearchParams.java index 47facbcf4..78ef106d5 100644 --- a/src/main/java/org/openlmis/referencedata/web/SearchParams.java +++ b/src/main/java/org/openlmis/referencedata/web/SearchParams.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -31,6 +32,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.openlmis.referencedata.exception.ValidationMessageException; import org.openlmis.referencedata.util.Message; import org.openlmis.referencedata.util.UuidUtil; @@ -161,7 +163,9 @@ public Set getStrings(String key) { return Optional.ofNullable(get(key)) .orElse(Collections.emptyList()) .stream() + .filter(Objects::nonNull) .map(value -> (String) value) + .filter(StringUtils::isNotBlank) .collect(toSet()); } diff --git a/src/main/resources/api-definition.yaml b/src/main/resources/api-definition.yaml index 28209dd01..23531b9eb 100644 --- a/src/main/resources/api-definition.yaml +++ b/src/main/resources/api-definition.yaml @@ -770,7 +770,7 @@ resourceTypes: displayName: program code type: string required: false - repeat: false + repeat: true responses: "200": headers: diff --git a/src/test/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImplTest.java b/src/test/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImplTest.java new file mode 100644 index 000000000..ef9fd060b --- /dev/null +++ b/src/test/java/org/openlmis/referencedata/repository/custom/impl/OrderableRepositoryImplTest.java @@ -0,0 +1,283 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.repository.custom.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.CODE; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.ID; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.IDENTITY; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.PRODUCT_CODE; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.PROGRAM; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.PROGRAM_ORDERABLES; +import static org.openlmis.referencedata.repository.custom.impl.OrderableRepositoryImpl.VERSION_NUMBER; + +import java.sql.Timestamp; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.IntStream; +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.Query; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.transform.DistinctRootEntityResultTransformer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.openlmis.referencedata.domain.Orderable; +import org.openlmis.referencedata.domain.VersionIdentity; +import org.openlmis.referencedata.web.QueryOrderableSearchParams; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +@RunWith(MockitoJUnitRunner.class) +public class OrderableRepositoryImplTest { + + public static final String PROGRAM_CODE_1 = "programCode1"; + public static final String PROGRAM_CODE_2 = "programCode2"; + @InjectMocks + private OrderableRepositoryImpl repository; + @Mock + private EntityManager entityManager; + + private static MultiValueMap prepareSampleMultiValueMap() { + MultiValueMap multiValueMap = new LinkedMultiValueMap<>(); + multiValueMap.add("name", "name"); + multiValueMap.add("code", "code"); + multiValueMap.add("program", PROGRAM_CODE_1); + multiValueMap.add("program", PROGRAM_CODE_2); + return multiValueMap; + } + + @Test + public void shouldFindLatestModifiedDateByParams() { + + //given + MultiValueMap multiValueMap = prepareSampleMultiValueMap(); + + ZonedDateTime now = ZonedDateTime.now(); + Query countQuery = mock(Query.class); + when(countQuery.getSingleResult()).thenReturn(1); + Query selectQuery = mock(Query.class); + when(selectQuery.getSingleResult()).thenReturn(Timestamp.from(now.toInstant())); + + when(entityManager.createNativeQuery( + contains(OrderableRepositoryImpl.NATIVE_COUNT_LAST_UPDATED))) + .thenReturn(countQuery); + when(entityManager.createNativeQuery( + contains(OrderableRepositoryImpl.NATIVE_SELECT_LAST_UPDATED))) + .thenReturn(selectQuery); + + //when + ZonedDateTime latestModifiedDateByParams = + repository.findLatestModifiedDateByParams(new QueryOrderableSearchParams(multiValueMap)); + + //then + assertEquals(latestModifiedDateByParams, now); + } + + @Test + public void shouldSearchForMultipleProgramsWithoutIdentityPairsAndWithoutTradeItemId() { + //given + int pageSize = 1; + Long offset = 1L; + + Pageable pageable = mock(Pageable.class); + when(pageable.getPageSize()).thenReturn(pageSize); + when(pageable.getOffset()).thenReturn(offset); + + Expression expression = mock(Expression.class); + CriteriaQuery newQuery = mock(CriteriaQuery.class); + + Root root = mock(Root.class); + + CriteriaQuery criteriaQuery = mock(CriteriaQuery.class); + + CriteriaBuilder criteriaBuilder = mock(CriteriaBuilderImpl.class); + when(entityManager.getCriteriaBuilder()).thenReturn(criteriaBuilder); + + //getTotal/getIdentities + when(criteriaBuilder.createQuery(any())) + .thenReturn(criteriaQuery); + + //getTotal/getIdentities->prepareQuery + when(criteriaQuery.from(Orderable.class)).thenReturn(root); + + when(criteriaBuilder.count(root)).thenReturn(expression); + when(criteriaQuery.select(expression)).thenReturn(newQuery); + + Path identityPath = mock(Path.class); + Path idPath = mock(Path.class); + Path versionNumberPath = mock(Path.class); + when(identityPath.get(ID)).thenReturn(idPath); + when(identityPath.get(VERSION_NUMBER)).thenReturn(versionNumberPath); + Expression idExpression = mock(Expression.class); + when(idPath.as(String.class)).thenReturn(idExpression); + + when(root.get(IDENTITY)).thenReturn(identityPath); + + Expression concatenatedExpression = mock(Expression.class); + when(criteriaBuilder.concat(idExpression, versionNumberPath)) + .thenReturn(concatenatedExpression); + Expression concatenatedExpressionAsString = mock(Expression.class); + when(concatenatedExpression.as(String.class)).thenReturn(concatenatedExpressionAsString); + when(criteriaBuilder.in(concatenatedExpressionAsString)) + .thenReturn(mock(CriteriaBuilder.In.class)); + + when(criteriaQuery.select(identityPath)).thenReturn(newQuery); + + //getTotal/getIdentities->prepareQuery->prepareParams + + when(criteriaBuilder.conjunction()).thenReturn(mock(Predicate.class)); + + Join ordProgOrdJoin = mock(Join.class); + when(root.join(PROGRAM_ORDERABLES, JoinType.INNER)) + .thenReturn(ordProgOrdJoin); + Join progOrdProgJoin = mock(Join.class); + when(ordProgOrdJoin.join(PROGRAM, JoinType.INNER)).thenReturn(progOrdProgJoin); + Path pathCode = mock(Path.class); + when(progOrdProgJoin.get(CODE)).thenReturn(pathCode); + Path pathCodeCode = mock(Path.class); + when(pathCode.get(CODE)).thenReturn(pathCodeCode); + Expression lowerExpression = mock(Expression.class); + when(criteriaBuilder.lower(pathCodeCode)).thenReturn(lowerExpression); + + //getTotal/getIdentities->prepareQuery->prepareParams->createSubQuery + + Subquery latestOrderableQuery = mock(Subquery.class); + when(newQuery.subquery(String.class)).thenReturn(latestOrderableQuery); + Root latestOrderableRoot = mock(Root.class); + when(latestOrderableQuery.from(Orderable.class)).thenReturn(latestOrderableRoot); + Path latestOrderableIdentityPath = mock(Path.class); + Path latestOrderableIdPath = mock(Path.class); + Path latestOrderableVersionNumberPath = mock(Path.class); + when(latestOrderableIdentityPath.get(ID)).thenReturn(latestOrderableIdPath); + when(latestOrderableIdentityPath.get(VERSION_NUMBER)) + .thenReturn(latestOrderableVersionNumberPath); + when(criteriaBuilder.max(latestOrderableVersionNumberPath)).thenReturn(mock(Expression.class)); + + when(latestOrderableRoot.get(IDENTITY)).thenReturn(latestOrderableIdentityPath); + + //end: getTotal/getIdentities->prepareQuery->prepareParams->createSubQuery + + when(root.get(PRODUCT_CODE)).thenReturn(mock(Path.class)); + + //end: getTotal/getIdentities->prepareQuery->prepareParams + + TypedQuery typedCountQuery = mock(TypedQuery.class); + when(typedCountQuery.getSingleResult()).thenReturn(1L); + when(entityManager.createQuery(newQuery)).thenReturn(typedCountQuery); + + + TypedQuery typedNotCountQuery = mock(TypedQuery.class, RETURNS_DEEP_STUBS); + when(typedNotCountQuery.setMaxResults(anyInt()).setFirstResult(anyInt())) + .thenReturn(typedNotCountQuery); + List versionIdentityList = new ArrayList<>(); + IntStream.range(0, 2).forEach(i -> + versionIdentityList.add(mock(VersionIdentity.class))); + when(typedNotCountQuery.getResultList()) + .thenReturn(versionIdentityList); + + when(entityManager.createQuery(criteriaQuery)).thenReturn(typedNotCountQuery); + + //end: getTotal/getIdentities->prepareQuery + //end: getTotal/getIdentities + + //retrieveOrderables + Predicate inPredicate = mock(Predicate.class); + CriteriaQuery orderableCriteriaQuery = mock(CriteriaQuery.class); + when(criteriaBuilder + .createQuery(Orderable.class)).thenReturn(orderableCriteriaQuery); + Root orderableRoot = mock(Root.class, RETURNS_DEEP_STUBS); + when(orderableCriteriaQuery + .from(Orderable.class)).thenReturn(orderableRoot); + Path orderablePath = mock(Path.class); + when(orderableRoot.get(IDENTITY)).thenReturn(orderablePath); + when(orderablePath + .in(any(List.class))).thenReturn(inPredicate); + when(orderableCriteriaQuery.select(orderableRoot)) + .thenReturn(orderableCriteriaQuery); + + //retrieveOrderables->retrieveOrderables + EntityGraph entityGraph = mock(EntityGraph.class); + when(entityManager.getEntityGraph(anyString())) + .thenReturn(entityGraph); + TypedQuery orderablesTypedQuery = mock(TypedQuery.class, RETURNS_DEEP_STUBS); + when(entityManager.createQuery(orderableCriteriaQuery)) + .thenReturn(orderablesTypedQuery); + org.hibernate.query.Query query = mock(org.hibernate.query.Query.class, RETURNS_DEEP_STUBS); + when(orderablesTypedQuery + .setHint(anyString(), anyBoolean()) + .setHint(anyString(), eq(entityGraph)) + .unwrap(org.hibernate.query.Query.class)) + .thenReturn(query); + when(query + .setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE) + .list()) + .thenReturn(Collections.singletonList(mock(Orderable.class))); + + //end: retrieveOrderables->retrieveOrderables + //end: retrieveOrderables + MultiValueMap multiValueMap = prepareSampleMultiValueMap(); + + QueryOrderableSearchParams params = new QueryOrderableSearchParams(multiValueMap); + + //when + Page resultPage = repository.search(params, pageable); + + //then + assertEquals(1L, resultPage.getTotalElements()); + assertEquals(1, resultPage.getTotalPages()); + + Set programCodes = new HashSet<>(); + programCodes.add(PROGRAM_CODE_1.toLowerCase()); + programCodes.add(PROGRAM_CODE_2.toLowerCase()); + ArgumentCaptor codesArgumentCaptor = ArgumentCaptor.forClass(Set.class); + verify(lowerExpression, times(2)).in(codesArgumentCaptor.capture()); + assertTrue(codesArgumentCaptor.getAllValues().stream() + .allMatch(codeList -> codeList.containsAll(programCodes))); + } +} diff --git a/src/test/java/org/openlmis/referencedata/service/OrderableServiceTest.java b/src/test/java/org/openlmis/referencedata/service/OrderableServiceTest.java index 69758ec1b..a750420ce 100644 --- a/src/test/java/org/openlmis/referencedata/service/OrderableServiceTest.java +++ b/src/test/java/org/openlmis/referencedata/service/OrderableServiceTest.java @@ -127,7 +127,8 @@ public void shouldThrowExceptionIfProvidedIdParameterIsMalformed() { public void shouldNotThrowValidationExceptionIfQueryMapCanBeParsed() { searchParams.add(CODE, "-1"); searchParams.add(NAME, "-1"); - searchParams.add(PROGRAM_CODE, "program-code"); + searchParams.add(PROGRAM_CODE, "programCode1"); + searchParams.add(PROGRAM_CODE, "programCode2"); orderableService.searchOrderables(new QueryOrderableSearchParams(searchParams), null); } @@ -185,6 +186,7 @@ public void shouldSearchForOrderables() { // given final String code = "ORD1"; final String name = "Orderable"; + final String programCode2 = "programCode2"; given(orderableRepository.search( any(SearchParams.class), @@ -195,6 +197,7 @@ public void shouldSearchForOrderables() { searchParams.add(CODE, code); searchParams.add(NAME, name); searchParams.add(PROGRAM_CODE, programCode); + searchParams.add(PROGRAM_CODE, programCode2); QueryOrderableSearchParams queryMap = new QueryOrderableSearchParams(searchParams); @@ -268,6 +271,7 @@ public void shouldReturnLatestModifiedDateWhenSearchingForOrderablesWithParams() // given final String code = "ORD1"; final String name = "Orderable"; + final String programCode2 = "programCode2"; given(orderableRepository.findLatestModifiedDateByParams( any(QueryOrderableSearchParams.class))) @@ -276,6 +280,7 @@ public void shouldReturnLatestModifiedDateWhenSearchingForOrderablesWithParams() searchParams.add(CODE, code); searchParams.add(NAME, name); searchParams.add(PROGRAM_CODE, programCode); + searchParams.add(PROGRAM_CODE, programCode2); QueryOrderableSearchParams queryMap = new QueryOrderableSearchParams(searchParams); diff --git a/src/test/java/org/openlmis/referencedata/service/export/OrderableImportPersisterTest.java b/src/test/java/org/openlmis/referencedata/service/export/OrderableImportPersisterTest.java index 305c03e8f..a59d815f7 100644 --- a/src/test/java/org/openlmis/referencedata/service/export/OrderableImportPersisterTest.java +++ b/src/test/java/org/openlmis/referencedata/service/export/OrderableImportPersisterTest.java @@ -57,7 +57,7 @@ public class OrderableImportPersisterTest { public void setUp() { dataStream = mock(InputStream.class); orderable = new OrderableDataBuilder().build(); - dto = OrderableDto.newInstance(orderable); + dto = OrderableDto.newInstances(orderable); } @Test diff --git a/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java b/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java index a6b8ac710..e930a6e75 100644 --- a/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java +++ b/src/test/java/org/openlmis/referencedata/testbuilder/ProgramOrderableDataBuilder.java @@ -98,7 +98,7 @@ public ProgramOrderableDataBuilder asNonFullSupply() { return this; } - public ProgramOrderableDataBuilder withOrderabeDisplayCategory( + public ProgramOrderableDataBuilder withOrderableDisplayCategory( OrderableDisplayCategory orderabeDisplayCategory) { this.orderableDisplayCategory = orderabeDisplayCategory; return this; diff --git a/src/test/java/org/openlmis/referencedata/web/OrderableControllerTest.java b/src/test/java/org/openlmis/referencedata/web/OrderableControllerTest.java new file mode 100644 index 000000000..bd263f2e2 --- /dev/null +++ b/src/test/java/org/openlmis/referencedata/web/OrderableControllerTest.java @@ -0,0 +1,109 @@ +/* + * This program is part of the OpenLMIS logistics management information system platform software. + * Copyright © 2017 VillageReach + * + * This program is free software: you can redistribute it and/or modify it under the terms + * of the GNU Affero General Public License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. You should have received a copy of + * the GNU Affero General Public License along with this program. If not, see + * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org. + */ + +package org.openlmis.referencedata.web; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.openlmis.referencedata.domain.Orderable; +import org.openlmis.referencedata.dto.OrderableDto; +import org.openlmis.referencedata.dto.VersionIdentityDto; +import org.openlmis.referencedata.repository.OrderableRepository; +import org.openlmis.referencedata.service.OrderableService; +import org.openlmis.referencedata.util.Pagination; +import org.slf4j.profiler.Profiler; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@RunWith(MockitoJUnitRunner.class) +public class OrderableControllerTest { + + @InjectMocks + private OrderableController orderableController; + + @Mock + private OrderableRepository repository; + + @Mock + private OrderableService orderableService; + + @Test + public void shouldSearch() { + //given + List versionIdentityDtos = new ArrayList<>(); + Long versionNumber = 123L; + VersionIdentityDto versionIdentityDto = new VersionIdentityDto(); + versionIdentityDto.setId(UUID.randomUUID()); + versionIdentityDto.setVersionNumber(versionNumber); + versionIdentityDtos.add(versionIdentityDto); + String code = "code"; + String name = "name"; + String programCode = "programCode"; + + OrderableSearchParams params = new OrderableSearchParams(); + params.setCode(code); + params.setName(name); + params.setProgramCode(programCode); + params.setIdentities(versionIdentityDtos); + int page = 0; + params.setPage(page); + int size = 10; + params.setSize(size); + + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime lastUpdated = now; + String ifModifiedNow = now.minusDays(1).format(BaseController.RFC_7231_FORMAT); + when(orderableService.getLatestLastUpdatedDate( + any(QueryOrderableSearchParams.class), + any(Profiler.class)) + ).thenReturn(lastUpdated); + Orderable mockOrderable = mock(Orderable.class); + Pageable pageable = params.getPageable(); + when(repository.search(params, pageable)) + .thenReturn(Pagination.getPage( + Collections.singletonList(mockOrderable), pageable, 1)); + + //when + ResponseEntity> responseEntity = + orderableController.searchOrderables(params, ifModifiedNow); + + //then + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + Page responseBody = responseEntity.getBody(); + assertEquals(1L, responseBody.getTotalElements()); + List orderableDtos = responseBody.get() + .collect(Collectors.toList()); + assertNotNull(orderableDtos); + assertEquals(1, orderableDtos.size()); + } + +} diff --git a/src/test/java/org/openlmis/referencedata/web/QueryOrderableSearchParamsTest.java b/src/test/java/org/openlmis/referencedata/web/QueryOrderableSearchParamsTest.java index b479c5779..6acdfda44 100644 --- a/src/test/java/org/openlmis/referencedata/web/QueryOrderableSearchParamsTest.java +++ b/src/test/java/org/openlmis/referencedata/web/QueryOrderableSearchParamsTest.java @@ -15,20 +15,30 @@ package org.openlmis.referencedata.web; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import java.util.HashSet; +import java.util.Set; +import org.hamcrest.MatcherAssert; import org.junit.Test; import org.springframework.util.LinkedMultiValueMap; public class QueryOrderableSearchParamsTest { + private static final String CODE = "code"; + private static final String NAME = "name"; + private static final String PROGRAM = "program"; + private static final String VALUE = "test"; + private static final String ANOTHER_VALUE = "anotherTest"; @Test public void getCodeShouldReturnValueForKeyCode() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("code", VALUE); + queryMap.add(CODE, VALUE); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); assertEquals(VALUE, searchParams.getCode()); @@ -45,7 +55,7 @@ public void getCodeShouldReturnNullIfMapDoesNotContainKeyCode() { @Test public void getCodeShouldReturnEmptyStringIfValueForRequestParamIsNull() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("code", null); + queryMap.add(CODE, null); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); assertEquals("", searchParams.getCode()); @@ -54,7 +64,7 @@ public void getCodeShouldReturnEmptyStringIfValueForRequestParamIsNull() { @Test public void getNameShouldReturnValueForKeyName() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("name", VALUE); + queryMap.add(NAME, VALUE); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); assertEquals(VALUE, searchParams.getName()); @@ -69,38 +79,48 @@ public void getNameShouldReturnNullIfMapDoesNotContainKeyName() { } @Test - public void getNameShouldReturnEmptyStringIfValueForRequestParamIsNull() { + public void getNameShouldReturnEmptyStringIfValueForRequestParameterIsNull() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("name", null); + queryMap.add(NAME, null); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); assertEquals("", searchParams.getName()); } + @Test - public void getProgramCodeShouldReturnValueForKeyProgram() { + public void getProgramCodesShouldReturnValueForKeyProgram() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("program", VALUE); + queryMap.add(PROGRAM, VALUE); + queryMap.add(PROGRAM, ANOTHER_VALUE); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); - assertEquals(VALUE, searchParams.getProgramCode()); + Set programCodes = new HashSet<>(); + programCodes.add(VALUE); + programCodes.add(ANOTHER_VALUE); + + assertEquals(programCodes, searchParams.getProgramCodes()); } @Test - public void getProgramShouldReturnNullIfMapDoesNotContainKeyProgram() { + public void getProgramCodesShouldReturnEmptyCollectionIfMapDoesNotContainKeyProgram() { QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(new LinkedMultiValueMap<>()); - assertNull(searchParams.getProgramCode()); + MatcherAssert.assertThat(searchParams.getProgramCodes(), hasSize(0)); } @Test - public void getProgramShouldReturnEmptyStringIfValueForRequestParamIsNull() { + public void getProgramCodesShouldReturnOnlyNotBlankProgramCodeRequestParameters() { LinkedMultiValueMap queryMap = new LinkedMultiValueMap<>(); - queryMap.add("program", null); + queryMap.add(PROGRAM, null); + queryMap.add(PROGRAM, ""); + queryMap.add(PROGRAM, " "); + queryMap.add(PROGRAM, VALUE); + queryMap.add(PROGRAM, ANOTHER_VALUE); QueryOrderableSearchParams searchParams = new QueryOrderableSearchParams(queryMap); - assertEquals("", searchParams.getProgramCode()); + MatcherAssert.assertThat(searchParams.getProgramCodes(), hasItems(VALUE, ANOTHER_VALUE)); } }