diff --git a/NEWS.md b/NEWS.md index 15aa5778..0c1c07c5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -18,9 +18,10 @@ * Fix offset handling when retrieving holdings from HoldingsIQ. ([MODKBEKBJ-825](https://folio-org.atlassian.net/browse/MODKBEKBJ-825)) * Fix error messages format received from HoldingsIQ. ([MODKBEKBJ-826](https://folio-org.atlassian.net/browse/MODKBEKBJ-826)) * Fix updater not returned on get access-types collection. ([MODKBEKBJ-842](https://folio-org.atlassian.net/browse/MODKBEKBJ-842)) -* Set "permissions: contents: read" in maven.yml ([FOLIO-4553](https://folio-org.atlassian.net/browse/FOLIO-4553)) +* Return 422 on post package validation errors ([MODKBEKBJ-862](https://folio-org.atlassian.net/browse/MODKBEKBJ-862)) ### Tech Dept +* Set "permissions: contents: read" in maven.yml ([FOLIO-4553](https://folio-org.atlassian.net/browse/FOLIO-4553)) * Description ([ISSUE](https://folio-org.atlassian.net/browse/ISSUE)) ### Dependencies diff --git a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java index 49e4ade0..6777a267 100644 --- a/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java +++ b/src/main/java/org/folio/rest/impl/EholdingsPackagesImpl.java @@ -125,6 +125,7 @@ public void postEholdingsPackages(String contentType, PackagePostRequest entity, template .addErrorMapper(NotFoundException.class, error400NotFoundMapper()) + .addErrorMapper(InputValidationException.class, error422InputValidationMapper()) .executeWithResult(Package.class); } diff --git a/src/test/java/org/folio/rest/impl/EholdingsPackagesImplTest.java b/src/test/java/org/folio/rest/impl/EholdingsPackagesImplTest.java index bf965faa..4ee5f59b 100644 --- a/src/test/java/org/folio/rest/impl/EholdingsPackagesImplTest.java +++ b/src/test/java/org/folio/rest/impl/EholdingsPackagesImplTest.java @@ -1,5 +1,6 @@ package org.folio.rest.impl; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -102,13 +103,6 @@ void setUp() { .build(); } - @SuppressWarnings("unchecked") - private Function> captureRequestAction() { - var captor = ArgumentCaptor.forClass(Function.class); - verify(template).requestAction(captor.capture()); - return captor.getValue(); - } - @Test void shouldFetchPackagesByTagFilterWhenTagsFilterIsGiven() { var filter = PackageRecordFilter.builder().filterTags(List.of("tag1")).sort("relevance") @@ -158,21 +152,13 @@ void shouldRetrieveAllPackagesWhenFilterCustomIsNotSet() { verify(packageService).retrievePackages(null, filter, context); } - private void invokeGetEholdingsPackages(PackageRecordFilter filter) { - impl.getEholdingsPackages(filter.getFilterCustom(), filter.getQuery(), filter.getQueryField(), - filter.getQueryType(), filter.isHighlight(), filter.getFilterSelected(), filter.getFilterType(), - filter.getFilterVisibility(), filter.getFilterFreeAccess(), filter.getFilterTags(), - filter.getFilterAccessType(), filter.getSort(), filter.getPage(), filter.getCount(), OKAPI_HEADERS, - asyncResultHandler, null); - } - @Test void shouldCreateCustomPackage() { var entity = new PackagePostRequest() .withData(new PackagePostData().withAttributes(new PackagePostDataAttributes())); when(packageService.createCustomPackage(entity, context)).thenReturn(completedFuture(null)); - impl.postEholdingsPackages("application/json", entity, OKAPI_HEADERS, asyncResultHandler, null); + impl.postEholdingsPackages(APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, asyncResultHandler, null); captureRequestAction().apply(context); verify(packageService).createCustomPackage(entity, context); @@ -180,6 +166,21 @@ void shouldCreateCustomPackage() { verify(template).executeWithResult(org.folio.rest.jaxrs.model.Package.class); } + @Test + @SuppressWarnings("unchecked") + void shouldMapInputValidationExceptionTo422OnCreateCustomPackage() { + var entity = new PackagePostRequest() + .withData(new PackagePostData().withAttributes(new PackagePostDataAttributes())); + + impl.postEholdingsPackages(APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, asyncResultHandler, null); + + var mapperCaptor = ArgumentCaptor.forClass(Function.class); + verify(template).addErrorMapper(eq(InputValidationException.class), mapperCaptor.capture()); + var response = (Response) mapperCaptor.getValue() + .apply(new InputValidationException("Invalid name", "name must not be empty")); + assertEquals(422, response.getStatus()); + } + @Test void shouldRetrievePackageWithRelatedData() { when(packageService.retrievePackageWithRelatedData(any(), any(), any())).thenReturn(completedFuture(null)); @@ -198,7 +199,8 @@ void shouldUpdatePackage() { .withData(new PackagePutData().withAttributes(new PackagePutDataAttributes())); when(packageService.updatePackage(any(), any(), any())).thenReturn(completedFuture(null)); - impl.putEholdingsPackagesByPackageId("19-3964", "application/json", entity, OKAPI_HEADERS, asyncResultHandler, + impl.putEholdingsPackagesByPackageId("19-3964", APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, + asyncResultHandler, null); captureRequestAction().apply(context); @@ -258,13 +260,6 @@ void shouldRetrievePackageTitlesWhenNoTagOrAccessTypeFilterIsGiven() { verify(template).requestAction(same(stubFunction)); } - private void invokeGetResources(ResourceFilter filter) { - impl.getEholdingsPackagesResourcesByPackageId(filter.getPackageId(), filter.getFilterTags(), - filter.getFilterAccessType(), filter.getFilterSelected(), filter.getFilterType(), filter.getFilterName(), - filter.getFilterIsxn(), filter.getFilterSubject(), filter.getFilterPublisher(), filter.getSort(), - filter.getPage(), filter.getCount(), OKAPI_HEADERS, asyncResultHandler, null); - } - @Test void shouldUpdateTagsForPackage() { var credentialsId = UUID.randomUUID(); @@ -275,7 +270,7 @@ void shouldUpdateTagsForPackage() { when(packageService.updateTagsForPackage(entity, credentialsId, "19-3964", "fs")) .thenReturn(completedFuture(attributes)); - impl.putEholdingsPackagesTagsByPackageId("19-3964", "application/json", entity, OKAPI_HEADERS, + impl.putEholdingsPackagesTagsByPackageId("19-3964", APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, asyncResultHandler, null); verify(asyncResultHandler).handle(responseCaptor.capture()); @@ -294,7 +289,7 @@ void shouldReturn422WhenTagsUpdateFails() { when(packageService.updateTagsForPackage(any(), any(), any(), any())) .thenReturn(CompletableFuture.failedFuture(new InputValidationException("invalid", "invalid tags"))); - impl.putEholdingsPackagesTagsByPackageId("19-3964", "application/json", entity, OKAPI_HEADERS, + impl.putEholdingsPackagesTagsByPackageId("19-3964", APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, asyncResultHandler, null); verify(asyncResultHandler).handle(responseCaptor.capture()); @@ -307,10 +302,32 @@ void shouldRetrievePackagesBulk() { var entity = new PackagePostBulkFetchRequest().withPackages(java.util.Set.of("19-3964")); when(packagesRmApiService.retrievePackagesBulk(entity.getPackages())).thenReturn(completedFuture(null)); - impl.postEholdingsPackagesBulkFetch("application/json", entity, OKAPI_HEADERS, asyncResultHandler, null); + impl.postEholdingsPackagesBulkFetch(APPLICATION_JSON.toString(), entity, OKAPI_HEADERS, asyncResultHandler, null); captureRequestAction().apply(context); verify(packagesRmApiService).retrievePackagesBulk(entity.getPackages()); verify(template).executeWithResult(org.folio.rest.jaxrs.model.PackageBulkFetchCollection.class); } + + @SuppressWarnings("unchecked") + private Function> captureRequestAction() { + var captor = ArgumentCaptor.forClass(Function.class); + verify(template).requestAction(captor.capture()); + return captor.getValue(); + } + + private void invokeGetEholdingsPackages(PackageRecordFilter filter) { + impl.getEholdingsPackages(filter.getFilterCustom(), filter.getQuery(), filter.getQueryField(), + filter.getQueryType(), filter.isHighlight(), filter.getFilterSelected(), filter.getFilterType(), + filter.getFilterVisibility(), filter.getFilterFreeAccess(), filter.getFilterTags(), + filter.getFilterAccessType(), filter.getSort(), filter.getPage(), filter.getCount(), OKAPI_HEADERS, + asyncResultHandler, null); + } + + private void invokeGetResources(ResourceFilter filter) { + impl.getEholdingsPackagesResourcesByPackageId(filter.getPackageId(), filter.getFilterTags(), + filter.getFilterAccessType(), filter.getFilterSelected(), filter.getFilterType(), filter.getFilterName(), + filter.getFilterIsxn(), filter.getFilterSubject(), filter.getFilterPublisher(), filter.getSort(), + filter.getPage(), filter.getCount(), OKAPI_HEADERS, asyncResultHandler, null); + } } diff --git a/src/test/java/org/folio/rest/impl/EholdingsPackagesIntegrationTest.java b/src/test/java/org/folio/rest/impl/EholdingsPackagesIntegrationTest.java index a384f385..e7331a4c 100644 --- a/src/test/java/org/folio/rest/impl/EholdingsPackagesIntegrationTest.java +++ b/src/test/java/org/folio/rest/impl/EholdingsPackagesIntegrationTest.java @@ -901,6 +901,18 @@ void shouldReturn422OnPostPackageWithInvalidAccessTypeId() { error.getErrors().getFirst().getMessage()); } + @Test + void shouldReturn422OnPostPackageWithEmptyName() { + var requestBody = readFile(POST_PACKAGE_REQUEST).replace("\"name\": \"TEST_NAME\"", "\"name\": \"\""); + + var error = postWithStatus(packagesPath(), requestBody, SC_UNPROCESSABLE_CONTENT).as(JsonapiError.class); + + assertEquals(1, error.getErrors().size()); + assertErrorContainsTitle(error, "Invalid name"); + assertEquals("name must not be empty", error.getErrors().getFirst().getDetail()); + wm.verify(0, postRequestedFor(urlPathEqualTo(packageRmApiV1(STUB_VENDOR_ID)))); + } + @Test void shouldReturn400WhenPackagePostDataIsInvalid() { mockGet(WireMock.equalTo(rootProxyCustomLabelsRmApi()), readFile(GET_PACKAGE_PROVIDER_RESPONSE));