diff --git a/src/main/java/org/folio/edge/patron/MainVerticle.java b/src/main/java/org/folio/edge/patron/MainVerticle.java index 73ff66b..b2c506d 100644 --- a/src/main/java/org/folio/edge/patron/MainVerticle.java +++ b/src/main/java/org/folio/edge/patron/MainVerticle.java @@ -1,12 +1,8 @@ package org.folio.edge.patron; -import static org.folio.edge.patron.Constants.DEFAULT_NULL_PATRON_ID_CACHE_TTL_MS; -import static org.folio.edge.patron.Constants.DEFAULT_PATRON_ID_CACHE_CAPACITY; -import static org.folio.edge.patron.Constants.DEFAULT_PATRON_ID_CACHE_TTL_MS; -import static org.folio.edge.patron.Constants.SYS_NULL_PATRON_ID_CACHE_TTL_MS; -import static org.folio.edge.patron.Constants.SYS_PATRON_ID_CACHE_CAPACITY; -import static org.folio.edge.patron.Constants.SYS_PATRON_ID_CACHE_TTL_MS; - +import io.vertx.core.http.HttpMethod; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.edge.core.EdgeVerticleHttp; @@ -14,9 +10,12 @@ import org.folio.edge.core.utils.OkapiClientFactoryInitializer; import org.folio.edge.patron.cache.PatronIdCache; -import io.vertx.core.http.HttpMethod; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.handler.BodyHandler; +import static org.folio.edge.patron.Constants.DEFAULT_NULL_PATRON_ID_CACHE_TTL_MS; +import static org.folio.edge.patron.Constants.DEFAULT_PATRON_ID_CACHE_CAPACITY; +import static org.folio.edge.patron.Constants.DEFAULT_PATRON_ID_CACHE_TTL_MS; +import static org.folio.edge.patron.Constants.SYS_NULL_PATRON_ID_CACHE_TTL_MS; +import static org.folio.edge.patron.Constants.SYS_PATRON_ID_CACHE_CAPACITY; +import static org.folio.edge.patron.Constants.SYS_PATRON_ID_CACHE_TTL_MS; public class MainVerticle extends EdgeVerticleHttp { @@ -57,20 +56,14 @@ public Router defineRoutes() { router.route(HttpMethod.GET, "/patron/account/:patronId") .handler(patronHandler::handleGetAccount); - router.route(HttpMethod.GET, "/patron/account/:patronId/external-patrons") - .handler(patronHandler::handleGetExtPatronsAccounts); - - router.route(HttpMethod.PUT, "/patron/account/:patronId/by-email/:emailId") - .handler(patronHandler::handlePutExtPatronAccountByEmail); - router.route(HttpMethod.POST, "/patron/account/:patronId/item/:itemId/renew") .handler(patronHandler::handleRenew); router.route(HttpMethod.POST, "/patron/account/:patronId/item/:itemId/hold") .handler(patronHandler::handlePlaceItemHold); - router.route(HttpMethod.POST, "/patron/account/:patronId") - .handler(patronHandler::handlePatronRequest); + router.route(HttpMethod.POST, "/patron") + .handler(patronHandler::handlePostPatronRequest); router.route(HttpMethod.POST, "/patron/account/:patronId/instance/:instanceId/hold") .handler(patronHandler::handlePlaceInstanceHold); diff --git a/src/main/java/org/folio/edge/patron/PatronHandler.java b/src/main/java/org/folio/edge/patron/PatronHandler.java index 6efbeae..61fa8e5 100644 --- a/src/main/java/org/folio/edge/patron/PatronHandler.java +++ b/src/main/java/org/folio/edge/patron/PatronHandler.java @@ -1,27 +1,5 @@ package org.folio.edge.patron; -import static org.folio.edge.core.Constants.APPLICATION_JSON; -import static org.folio.edge.patron.Constants.FIELD_EXPIRATION_DATE; -import static org.folio.edge.patron.Constants.FIELD_REQUEST_DATE; -import static org.folio.edge.patron.Constants.MSG_ACCESS_DENIED; -import static org.folio.edge.patron.Constants.MSG_EXTERNAL_NOBODY; -import static org.folio.edge.patron.Constants.MSG_HOLD_NOBODY; -import static org.folio.edge.patron.Constants.MSG_INTERNAL_SERVER_ERROR; -import static org.folio.edge.patron.Constants.MSG_REQUEST_TIMEOUT; -import static org.folio.edge.patron.Constants.PARAM_EMAIL_ID; -import static org.folio.edge.patron.Constants.PARAM_EXPIRED; -import static org.folio.edge.patron.Constants.PARAM_HOLD_ID; -import static org.folio.edge.patron.Constants.PARAM_INCLUDE_CHARGES; -import static org.folio.edge.patron.Constants.PARAM_INCLUDE_HOLDS; -import static org.folio.edge.patron.Constants.PARAM_INCLUDE_LOANS; -import static org.folio.edge.patron.Constants.PARAM_INSTANCE_ID; -import static org.folio.edge.patron.Constants.PARAM_ITEM_ID; -import static org.folio.edge.patron.Constants.PARAM_LIMIT; -import static org.folio.edge.patron.Constants.PARAM_OFFSET; -import static org.folio.edge.patron.Constants.PARAM_PATRON_ID; -import static org.folio.edge.patron.Constants.PARAM_SORT_BY; -import static org.folio.edge.patron.model.HoldCancellationValidator.validateCancelHoldRequest; - import com.amazonaws.util.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import io.vertx.core.buffer.Buffer; @@ -31,15 +9,6 @@ import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.client.HttpResponse; -import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TimeZone; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.edge.core.Handler; @@ -53,6 +22,35 @@ import org.folio.edge.patron.utils.PatronIdHelper; import org.folio.edge.patron.utils.PatronOkapiClient; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; + +import static org.folio.edge.core.Constants.APPLICATION_JSON; +import static org.folio.edge.patron.Constants.FIELD_EXPIRATION_DATE; +import static org.folio.edge.patron.Constants.FIELD_REQUEST_DATE; +import static org.folio.edge.patron.Constants.MSG_ACCESS_DENIED; +import static org.folio.edge.patron.Constants.MSG_HOLD_NOBODY; +import static org.folio.edge.patron.Constants.MSG_INTERNAL_SERVER_ERROR; +import static org.folio.edge.patron.Constants.MSG_REQUEST_TIMEOUT; +import static org.folio.edge.patron.Constants.PARAM_EMAIL_ID; +import static org.folio.edge.patron.Constants.PARAM_HOLD_ID; +import static org.folio.edge.patron.Constants.PARAM_INCLUDE_CHARGES; +import static org.folio.edge.patron.Constants.PARAM_INCLUDE_HOLDS; +import static org.folio.edge.patron.Constants.PARAM_INCLUDE_LOANS; +import static org.folio.edge.patron.Constants.PARAM_INSTANCE_ID; +import static org.folio.edge.patron.Constants.PARAM_ITEM_ID; +import static org.folio.edge.patron.Constants.PARAM_LIMIT; +import static org.folio.edge.patron.Constants.PARAM_OFFSET; +import static org.folio.edge.patron.Constants.PARAM_PATRON_ID; +import static org.folio.edge.patron.Constants.PARAM_SORT_BY; +import static org.folio.edge.patron.model.HoldCancellationValidator.validateCancelHoldRequest; + public class PatronHandler extends Handler { public static final String WRONG_INTEGER_PARAM_MESSAGE = "'%s' parameter is incorrect." @@ -142,22 +140,6 @@ public void handleRenew(RoutingContext ctx) { } - public void handlePutExtPatronAccountByEmail(RoutingContext ctx) { - if (ctx.body().asJsonObject() == null) { - badRequest(ctx, MSG_EXTERNAL_NOBODY); - return; - } - final String body = String.valueOf(ctx.body().asJsonObject()); - handleCommon(ctx, - new String[] {PARAM_PATRON_ID, PARAM_EMAIL_ID}, - new String[] {}, - (client, params) -> ((PatronOkapiClient) client).putPatron( - params.get(PARAM_EMAIL_ID), - body, - resp -> handleProxyResponse(ctx, resp), - t -> handleProxyException(ctx, t))); - } - public void handlePlaceItemHold(RoutingContext ctx) { if (ctx.body().asJsonObject() == null) { badRequest(ctx, MSG_HOLD_NOBODY); @@ -175,19 +157,56 @@ public void handlePlaceItemHold(RoutingContext ctx) { t -> handleProxyException(ctx, t))); } - public void handlePatronRequest(RoutingContext ctx) { + public void handlePostPatronRequest(RoutingContext ctx) { if (ctx.body().asJsonObject() == null) { - badRequest(ctx, MSG_EXTERNAL_NOBODY); + logger.warn("handlePostPatronRequest:: missing body found"); + ctx.response() + .setStatusCode(400) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(getErrorMsg("MISSING_BODY", "Request body must not null")); return; } + final String body = String.valueOf(ctx.body().asJsonObject()); - handleCommon(ctx, - new String[] {}, - new String[] {}, - (client, params) -> ((PatronOkapiClient) client).postPatron( - body, - resp -> handleProxyResponse(ctx, resp), - t -> handleProxyException(ctx, t))); + super.handleCommon(ctx, new String[]{}, new String[]{}, (client, params) -> { + String alternateTenantId = ctx.request().getParam("alternateTenantId", client.tenant); + final PatronOkapiClient patronClient = new PatronOkapiClient(client, alternateTenantId); + patronClient.postPatron(body, + resp -> handlePostPatronResponse(ctx, resp), + t -> handleProxyException(ctx, t)); + }); + } + + private void handlePostPatronResponse(RoutingContext ctx, HttpResponse resp) { + HttpServerResponse serverResponse = ctx.response(); + + int statusCode = resp.statusCode(); + serverResponse.setStatusCode(statusCode); + + String respBody = resp.bodyAsString(); + if (logger.isDebugEnabled() ) { + logger.debug("handlePostPatronResponse:: response {} ", respBody); + } + + String contentType = resp.getHeader(HttpHeaders.CONTENT_TYPE.toString()); + + if (resp.statusCode() < 400 && Objects.nonNull(respBody)){ + setContentType(serverResponse, contentType); + serverResponse.end(respBody); //not an error case, pass on the response body as received + } + else { + String errorMsg = generateErrorMessage(statusCode, respBody); + setContentType(serverResponse, APPLICATION_JSON); + serverResponse.end(errorMsg); + } + } + + private String generateErrorMessage(int statusCode, String respBody) { + return switch (statusCode) { + case 422 -> getFormattedErrorMsg(statusCode, respBody); + case 400 -> getErrorMsg("BAD_REQUEST", respBody); + default -> getStructuredErrorMessage(statusCode, respBody); + }; } public void handleCancelHold(RoutingContext ctx) { @@ -215,16 +234,6 @@ public void handleCancelHold(RoutingContext ctx) { ); } - public void handleGetExtPatronsAccounts(RoutingContext ctx) { - handleCommon(ctx, - new String[] { PARAM_PATRON_ID, PARAM_EXPIRED }, - new String[] {}, - (client, params) -> ((PatronOkapiClient) client).getExtPatronAccounts( - Boolean.parseBoolean(params.get(PARAM_EXPIRED)), - resp -> handleProxyResponse(ctx, resp), - t -> handleProxyException(ctx, t))); - } - public void handlePlaceInstanceHold(RoutingContext ctx) { if (ctx.body().asJsonObject() == null) { badRequest(ctx, MSG_HOLD_NOBODY); diff --git a/src/main/java/org/folio/edge/patron/model/Patron.java b/src/main/java/org/folio/edge/patron/model/Patron.java deleted file mode 100644 index a82ed3b..0000000 --- a/src/main/java/org/folio/edge/patron/model/Patron.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.folio.edge.patron.model; - -import java.io.IOException; -import java.util.List; - -import org.folio.edge.core.utils.Mappers; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; - -@JsonInclude(JsonInclude.Include.NON_NULL) -@JacksonXmlRootElement(localName = "patron") -@JsonDeserialize(builder = Patron.Builder.class) -@JsonPropertyOrder({ - "generalInfo", - "addressInfo", - "contactInfo", - "preferredEmailCommunication" -}) -public final class Patron { - public final GeneralInfo generalInfo; - public final AddressInfo address; - public final ContactInfo contactInfo; - public final List preferredEmailCommunication; - - private Patron(Builder builder) { - this.generalInfo = builder.generalInfo; - this.address = builder.address; - this.contactInfo = builder.contactInfo; - this.preferredEmailCommunication = builder.preferredEmailCommunication; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - @JsonProperty("generalInfo") - private GeneralInfo generalInfo; - - @JsonProperty("addressInfo") - private AddressInfo address; - - @JsonProperty("contactInfo") - private ContactInfo contactInfo; - - @JsonProperty("preferredEmailCommunication") - private List preferredEmailCommunication; - - public Builder generalInfo(GeneralInfo generalInfo) { - this.generalInfo = generalInfo; - return this; - } - - public Builder address(AddressInfo address) { - this.address = address; - return this; - } - - public Builder contactInfo(ContactInfo contactInfo) { - this.contactInfo = contactInfo; - return this; - } - - public Builder preferredEmailCommunication(List preferredEmailCommunication) { - this.preferredEmailCommunication = preferredEmailCommunication; - return this; - } - - public Patron build() { - return new Patron(this); - } - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonPropertyOrder({ - "externalSystemId", - "firstName", - "preferredFirstName", - "middleName", - "lastName" - }) - public static class GeneralInfo { - public final String externalSystemId; - public final String firstName; - public final String preferredFirstName; - public final String middleName; - public final String lastName; - - @JsonCreator - public GeneralInfo( - @JsonProperty("externalSystemId") String externalSystemId, - @JsonProperty("firstName") String firstName, - @JsonProperty("preferredFirstName") String preferredFirstName, - @JsonProperty("middleName") String middleName, - @JsonProperty("lastName") String lastName) { - this.externalSystemId = externalSystemId; - this.firstName = firstName; - this.preferredFirstName = preferredFirstName; - this.middleName = middleName; - this.lastName = lastName; - } - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonPropertyOrder({ - "addressLine0", - "addressLine1", - "city", - "province", - "zip", - "country" - }) - public static class AddressInfo { - public final String addressLine0; - public final String addressLine1; - public final String city; - public final String province; - public final String zip; - public final String country; - - @JsonCreator - public AddressInfo( - @JsonProperty("addressLine0") String addressLine0, - @JsonProperty("addressLine1") String addressLine1, - @JsonProperty("city") String city, - @JsonProperty("province") String province, - @JsonProperty("zip") String zip, - @JsonProperty("country") String country) { - this.addressLine0 = addressLine0; - this.addressLine1 = addressLine1; - this.city = city; - this.province = province; - this.zip = zip; - this.country = country; - } - } - - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonPropertyOrder({ - "phone", - "mobilePhone", - "email" - }) - public static class ContactInfo { - public final String phone; - public final String mobilePhone; - public final String email; - - @JsonCreator - public ContactInfo( - @JsonProperty("phone") String phone, - @JsonProperty("mobilePhone") String mobilePhone, - @JsonProperty("email") String email) { - this.phone = phone; - this.mobilePhone = mobilePhone; - this.email = email; - } - } - - public String toJson() throws JsonProcessingException { - return Mappers.jsonMapper.writeValueAsString(this); - } - - public static Patron fromJson(String json) throws IOException { - return Mappers.jsonMapper.readValue(json, Patron.class); - } -} diff --git a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java b/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java index 10164be..a6ef365 100644 --- a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java +++ b/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java @@ -95,17 +95,6 @@ public void getAccount(String patronId, boolean includeLoans, boolean includeCha exceptionHandler); } - public void getExtPatronAccounts(boolean expired, Handler> responseHandler, - Handler exceptionHandler) { - String url = String.format("%s/patron/account?expired=%s", okapiURL, expired); - get( - url, - tenant, - null, - responseHandler, - exceptionHandler); - } - public void renewItem(String patronId, String itemId, Handler> responseHandler, Handler exceptionHandler) { post( @@ -131,7 +120,7 @@ public void placeItemHold(String patronId, String itemId, String requestBody, public void postPatron(String requestBody, Handler> responseHandler, Handler exceptionHandler) { post( - String.format("%s/patron/account", okapiURL), + String.format("%s/patron", okapiURL), tenant, requestBody, null, @@ -139,17 +128,6 @@ public void postPatron(String requestBody, exceptionHandler); } - public void putPatron(String emailId, String requestBody, - Handler> responseHandler, Handler exceptionHandler) { - put( - String.format("%s/patron/account/by-email/%s", okapiURL, emailId), - tenant, - requestBody, - defaultHeaders, - responseHandler, - exceptionHandler); - } - public void cancelHold(String patronId, String holdId, JsonObject holdCancellationRequest, Handler> responseHandler, Handler exceptionHandler) { getRequest(holdId, diff --git a/src/test/java/org/folio/edge/patron/MainVerticleTest.java b/src/test/java/org/folio/edge/patron/MainVerticleTest.java index 9ec84e4..fc8e8c2 100644 --- a/src/test/java/org/folio/edge/patron/MainVerticleTest.java +++ b/src/test/java/org/folio/edge/patron/MainVerticleTest.java @@ -1,5 +1,41 @@ package org.folio.edge.patron; +import io.restassured.RestAssured; +import io.restassured.config.DecoderConfig; +import io.restassured.config.DecoderConfig.ContentDecoder; +import io.restassured.response.Response; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHeaders; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.edge.core.utils.ApiKeyUtils; +import org.folio.edge.core.utils.test.TestUtils; +import org.folio.edge.patron.model.Account; +import org.folio.edge.patron.model.Hold; +import org.folio.edge.patron.model.Loan; +import org.folio.edge.patron.model.error.ErrorMessage; +import org.folio.edge.patron.utils.PatronMockOkapi; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + import static org.folio.edge.core.Constants.APPLICATION_JSON; import static org.folio.edge.core.Constants.DAY_IN_MILLIS; import static org.folio.edge.core.Constants.SYS_LOG_LEVEL; @@ -10,8 +46,8 @@ import static org.folio.edge.core.Constants.SYS_SECURE_STORE_PROP_FILE; import static org.folio.edge.core.Constants.TEXT_PLAIN; import static org.folio.edge.patron.Constants.MSG_ACCESS_DENIED; -import static org.folio.edge.patron.Constants.MSG_REQUEST_TIMEOUT; import static org.folio.edge.patron.Constants.MSG_HOLD_NOBODY; +import static org.folio.edge.patron.Constants.MSG_REQUEST_TIMEOUT; import static org.folio.edge.patron.utils.PatronMockOkapi.holdCancellationHoldId; import static org.folio.edge.patron.utils.PatronMockOkapi.holdReqId_notFound; import static org.folio.edge.patron.utils.PatronMockOkapi.holdReqTs; @@ -25,52 +61,13 @@ import static org.folio.edge.patron.utils.PatronMockOkapi.wrongIntegerParamMessage; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHeaders; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.folio.edge.core.utils.ApiKeyUtils; -import org.folio.edge.core.utils.test.TestUtils; -import org.folio.edge.patron.model.Account; -import org.folio.edge.patron.model.Patron; -import org.folio.edge.patron.model.error.ErrorMessage; -import org.folio.edge.patron.model.Hold; -import org.folio.edge.patron.model.Loan; -import org.folio.edge.patron.utils.PatronMockOkapi; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import io.restassured.RestAssured; -import io.restassured.config.DecoderConfig; -import io.restassured.config.DecoderConfig.ContentDecoder; -import io.restassured.response.Response; - -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Vertx; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.unit.TestContext; -import io.vertx.ext.unit.junit.VertxUnitRunner; - @RunWith(VertxUnitRunner.class) public class MainVerticleTest { @@ -202,22 +199,6 @@ public void testGetAccountPatronFound(TestContext context) throws Exception { .body(is(expected)); } - @Test - public void testGetExternalLCPatrons(TestContext context) { - logger.info("=== Test get external patron ==="); - int expectedStatusCode = 200; - RestAssured - .with() - .contentType(APPLICATION_JSON) - .get( - String.format("/patron/account/%s/external-patrons?apikey=%s&expired=false",UUID.randomUUID(), apiKey)) - .then() - .statusCode(expectedStatusCode) - .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .extract() - .response(); - } - @Test public void testGetAccountPatronFoundGzip(TestContext context) throws Exception { logger.info("=== Patron in GZip compression ==="); @@ -825,56 +806,120 @@ public void testPlaceInstanceHoldPatronNotFound(TestContext context) throws Exce } @Test - public void testPostExternalLCPatron(TestContext context) throws Exception { - logger.info("=== Test post external patron ==="); - - Patron patron = PatronMockOkapi.getPatron(); - int expectedStatusCode = 201; + public void testPostPatron_201(TestContext context) throws Exception { + logger.info("=== testPostPatron_201 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_201"); RestAssured .with() - .body(patron.toJson()) + .body(jsonObject.encode()) .contentType(APPLICATION_JSON) .post( - String.format("/patron/account/%s?apikey=%s", UUID.randomUUID(), apiKey)) + String.format("/patron?apikey=%s", apiKey)) .then() - .statusCode(expectedStatusCode) - .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .extract() - .response(); + .statusCode(201) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); } @Test - public void testPutExternalLCPatron(TestContext context) throws Exception { - logger.info("=== Test put external patron ==="); + public void testPostPatron_200(TestContext context) throws Exception { + logger.info("=== testPostPatron_200 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_200"); + RestAssured + .with() + .body(jsonObject.encode()) + .contentType(APPLICATION_JSON) + .post( + String.format("/patron?apikey=%s", apiKey)) + .then() + .statusCode(200) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + } - Patron patron = PatronMockOkapi.getPatron(); - int expectedStatusCode = 204; + @Test + public void testPostPatronWithRandomStatusCode_250(TestContext context) throws Exception { + logger.info("=== testPostPatronWithRandomStatusCode_250 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_250"); RestAssured .with() - .body(patron.toJson()) + .body(jsonObject.encode()) .contentType(APPLICATION_JSON) - .put( - String.format("/patron/account/%s/by-email/%s?apikey=%s", UUID.randomUUID(), "TestMail", apiKey)) + .post( + String.format("/patron?apikey=%s", apiKey)) .then() - .statusCode(expectedStatusCode) + .statusCode(250) .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); } @Test - public void testPutExternalLCPatronWithEmptyBody(TestContext context) { - logger.info("=== Test put external patron ==="); + public void testPostPatron_400(TestContext context) throws Exception { + logger.info("=== testPostPatron_400 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_400"); + RestAssured + .with() + .body(jsonObject.encode()) + .contentType(APPLICATION_JSON) + .post( + String.format("/patron?apikey=%s", apiKey)) + .then() + .statusCode(400) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .body("errorMessage", is("A bad exception occurred")) + .body("code", is("BAD_REQUEST")); + } - int expectedStatusCode = 400; + @Test + public void testPostPatron_422(TestContext context) throws Exception { + logger.info("=== testPostPatron_422 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_422"); RestAssured .with() + .body(jsonObject.encode()) .contentType(APPLICATION_JSON) - .put( - String.format("/patron/account/%s/by-email/%s?apikey=%s", UUID.randomUUID(), "TestMail", apiKey)) + .post( + String.format("/patron?apikey=%s", apiKey)) .then() - .statusCode(expectedStatusCode) + .statusCode(422) .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .extract() - .response(); + .body("errorMessage", is("ABC is required")) + .body("code", is("ERROR_CODE")); + } + + @Test + public void testPostPatron_500(TestContext context) throws Exception { + logger.info("=== testPostPatron_500 ==="); + JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-post-request.json")); + jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_500"); + RestAssured + .with() + .body(jsonObject.encode()) + .contentType(APPLICATION_JSON) + .post( + String.format("/patron?apikey=%s", apiKey)) + .then() + .statusCode(500) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .body("errorMessage", is("Server exception occurred")) + .body("code", is(500)); + } + + @Test + public void testPostPatron_NoRequestBody(TestContext context) throws Exception { + logger.info("=== testPostPatron_NoRequestBody ==="); + RestAssured + .with() + .contentType(APPLICATION_JSON) + .post( + String.format("/patron?apikey=%s", apiKey)) + .then() + .statusCode(400) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .body("errorMessage", is("Request body must not null")) + .body("code", is("MISSING_BODY")); } @Test diff --git a/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java b/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java index 2eea6e5..f252373 100644 --- a/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java +++ b/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java @@ -1,5 +1,36 @@ package org.folio.edge.patron.utils; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.folio.edge.core.utils.test.MockOkapi; +import org.folio.edge.patron.model.Account; +import org.folio.edge.patron.model.Charge; +import org.folio.edge.patron.model.Hold; +import org.folio.edge.patron.model.Hold.Status; +import org.folio.edge.patron.model.HoldCancellation; +import org.folio.edge.patron.model.Item; +import org.folio.edge.patron.model.Loan; +import org.folio.edge.patron.model.Money; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Currency; +import java.util.Date; +import java.util.List; +import java.util.UUID; + import static java.util.Collections.singletonList; import static org.folio.edge.core.Constants.APPLICATION_JSON; import static org.folio.edge.core.Constants.DAY_IN_MILLIS; @@ -18,39 +49,6 @@ import static org.folio.edge.patron.Constants.PARAM_REQUEST_ID; import static org.folio.edge.patron.Constants.PARAM_SORT_BY; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Currency; -import java.util.Date; -import java.util.List; -import java.util.UUID; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.folio.edge.core.utils.test.MockOkapi; -import org.folio.edge.patron.model.Account; -import org.folio.edge.patron.model.Charge; -import org.folio.edge.patron.model.Hold; -import org.folio.edge.patron.model.Hold.Status; - -import com.fasterxml.jackson.core.JsonProcessingException; - -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.RoutingContext; -import org.folio.edge.patron.model.HoldCancellation; -import org.folio.edge.patron.model.Item; -import org.folio.edge.patron.model.Loan; -import org.folio.edge.patron.model.Money; -import org.folio.edge.patron.model.Patron; - public class PatronMockOkapi extends MockOkapi { private static final Logger logger = LogManager.getLogger(PatronMockOkapi.class); @@ -136,22 +134,13 @@ public Router defineRoutes() { router.route(HttpMethod.GET, "/patron/account/:patronId") .handler(this::getAccountHandler); - router.route(HttpMethod.GET, "/patron/account/by-email/:emailId") - .handler(this::getExtPatronAccountHandler); - - router.route(HttpMethod.GET, "/patron/account") - .handler(this::getExtPatronAccountHandler); - - router.route(HttpMethod.PUT, "/patron/account/by-email/:emailId") - .handler(this::putExtPatronAccountHandler); - router.route(HttpMethod.POST, "/patron/account/:patronId/item/:itemId/renew") .handler(this::renewItemHandler); router.route(HttpMethod.POST, "/patron/account/:patronId/item/:itemId/hold") .handler(this::placeItemHoldHandler); - router.route(HttpMethod.POST, "/patron/account") + router.route(HttpMethod.POST, "/patron") .handler(this::postPatronMock); router.route(HttpMethod.POST, "/patron/account/:patronId/instance/:instanceId/hold") @@ -243,22 +232,6 @@ public void getAccountHandler(RoutingContext ctx) { } } - public void getExtPatronAccountHandler(RoutingContext ctx) { - String token = ctx.request().getHeader(X_OKAPI_TOKEN); - - if (token == null || !token.equals(MOCK_TOKEN)) { - ctx.response() - .setStatusCode(403) - .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) - .end("Access requires permission: patron.account.get"); - } else { - ctx.response() - .setStatusCode(200) - .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .end(getPatron().toString()); - } - } - public void getRegistrationStatusHandler(RoutingContext ctx) { String token = ctx.request().getHeader(X_OKAPI_TOKEN); String emailId = ctx.request().getParam(PARAM_EMAIL_ID); @@ -295,25 +268,6 @@ public void getRegistrationStatusHandler(RoutingContext ctx) { } } - public void putExtPatronAccountHandler(RoutingContext ctx) { - String token = ctx.request().getHeader(X_OKAPI_TOKEN); - if (token == null || !token.equals(MOCK_TOKEN)) { - ctx.response() - .setStatusCode(403) - .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) - .end("Access requires permission: patron.account.put"); - } else if (ctx.body().isEmpty()) { - ctx.response() - .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) - .end("No Body"); - } else { - ctx.response() - .setStatusCode(204) - .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .end(getPatron().toString()); - } - } - public void renewItemHandler(RoutingContext ctx) { String patronId = ctx.request().getParam(PARAM_PATRON_ID); String itemId = ctx.request().getParam(PARAM_ITEM_ID); @@ -414,16 +368,46 @@ public void placeItemHoldHandler(RoutingContext ctx) { public void postPatronMock(RoutingContext ctx) { try { - ctx.response() - .setStatusCode(201) - .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) - .end(); + String firstName = ctx.body().asJsonObject().getJsonObject("generalInfo").getString("firstName"); + String mockResponseBody = readMockFile("/staging-users-post-response.json"); + String mockResponse422ErrorBody = readMockFile("/staging-users-post-error-response.json"); + if ("TEST_STATUS_CODE_200".equals(firstName)) { + ctx.response() + .setStatusCode(200) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(mockResponseBody); + } else if ("TEST_STATUS_CODE_201".equals(firstName)) { + ctx.response() + .setStatusCode(201) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(mockResponseBody); + } else if ("TEST_STATUS_CODE_250".equals(firstName)) { + ctx.response() + .setStatusCode(250) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(mockResponseBody); + } else if ("TEST_STATUS_CODE_400".equals(firstName)) { + ctx.response() + .setStatusCode(400) + .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) + .end("A bad exception occurred"); + } else if ("TEST_STATUS_CODE_422".equals(firstName)) { + ctx.response() + .setStatusCode(422) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(mockResponse422ErrorBody); + } else if ("TEST_STATUS_CODE_500".equals(firstName)) { + ctx.response() + .setStatusCode(500) + .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) + .end("Server exception occurred"); + } } catch (Exception e) { logger.error("Exception parsing request payload", e); ctx.response() .setStatusCode(400) .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) - .end("Bad Request"); + .end("Bad Request: " + e.toString()); } } @@ -699,14 +683,6 @@ public static Hold getHold(String itemId, Date holdReqDate) { .build(); } - public static Patron getPatron() { - return Patron.builder() - .address(new Patron.AddressInfo("fdsf","sds", "fsd", "dasd", "123", "sdsd")) - .contactInfo(new Patron.ContactInfo("342424","232321","fgh@mail")) - .generalInfo(new Patron.GeneralInfo("1234","sds","a","s", "45")) - .preferredEmailCommunication(new ArrayList<>()) - .build(); - } public static Charge getCharge(String itemId) { return Charge.builder() .item(getItem(itemId_overdue)) @@ -753,17 +729,6 @@ public static String getPlacedHoldJson(Hold hold) { return ret; } - public static String getPJson(Patron hold) { - - String ret = null; - try { - ret = hold.toJson(); - } catch (JsonProcessingException e) { - logger.warn("Failed to generate Hold JSON", e); - } - return ret; - } - public static String getRemovedHoldJson(String holdReqId) { String ret = null; try { diff --git a/src/test/resources/staging-users-post-error-response.json b/src/test/resources/staging-users-post-error-response.json new file mode 100644 index 0000000..dabed5f --- /dev/null +++ b/src/test/resources/staging-users-post-error-response.json @@ -0,0 +1,27 @@ +{ + "total_records": 2, + "errors": [ + { + "message": "ABC is required", + "type": "STANDARD_TYPE", + "code": "ERROR_CODE", + "parameters": [ + { + "key": "KEY-1", + "value": "VALUE-1" + } + ] + }, + { + "message": "XYZ is required", + "type": "STANDARD_TYPE", + "code": "ERROR_CODE", + "parameters": [ + { + "key": "KEY-2", + "value": "VALUE-2" + } + ] + } + ] +} diff --git a/src/test/resources/staging-users-post-request.json b/src/test/resources/staging-users-post-request.json new file mode 100644 index 0000000..2405a0b --- /dev/null +++ b/src/test/resources/staging-users-post-request.json @@ -0,0 +1,27 @@ +{ + "isEmailVerified": false, + "status": "TIER-1", + "generalInfo": { + "firstName": "TEST_STATUS_CODE_200", + "middleName": "www", + "lastName": "new-record-1" + }, + "addressInfo": { + "addressLine0": "123 Main St", + "addressLine1": "Apt 4B", + "city": "Metropolis", + "province": "NY", + "zip": "12345", + "country": "USA" + }, + "contactInfo": { + "phone": "555-123456", + "mobilePhone": "555-5678", + "email": "new-record-kapil_new3@test.com" + }, + "preferredEmailCommunication": [ + "Programs", + "Support", + "Services" + ] +} diff --git a/src/test/resources/staging-users-post-response.json b/src/test/resources/staging-users-post-response.json new file mode 100644 index 0000000..9e2bc31 --- /dev/null +++ b/src/test/resources/staging-users-post-response.json @@ -0,0 +1,34 @@ +{ + "id": "http_status_200", + "isEmailVerified": true, + "status": "TIER-1", + "generalInfo": { + "firstName": "test1", + "middleName": "www", + "lastName": "new-record-1" + }, + "addressInfo": { + "addressLine0": "123 Main St", + "addressLine1": "Apt 4B", + "city": "Metropolis", + "province": "NY", + "zip": "12345", + "country": "USA" + }, + "contactInfo": { + "phone": "555-123456", + "mobilePhone": "555-5678", + "email": "new-record-kapil_new3@test.com" + }, + "preferredEmailCommunication": [ + "Programs", + "Support", + "Services" + ], + "metadata": { + "createdDate": "2024-10-15T10:50:36.267+00:00", + "createdByUserId": "21457ab5-4635-4e56-906a-908f05e9233b", + "updatedDate": "2024-10-17T09:32:24.840+00:00", + "updatedByUserId": "21457ab5-4635-4e56-906a-908f05e9233b" + } +}