From 72cb196094a05d7c12ea56033120cd9e6ddf88d3 Mon Sep 17 00:00:00 2001 From: Vignesh-Kalyanasundaram Date: Thu, 17 Oct 2024 16:26:19 +0530 Subject: [PATCH] EDGPATRON-149 changing emailId from path param to query param and formatting error response --- ramls/edge-patron.raml | 9 +- ramls/staging_user.json | 2 +- .../org/folio/edge/patron/MainVerticle.java | 2 +- .../org/folio/edge/patron/PatronHandler.java | 39 +++++-- .../edge/patron/utils/PatronOkapiClient.java | 11 -- .../folio/edge/patron/MainVerticleTest.java | 106 +++++++++++++++++- .../edge/patron/utils/PatronMockOkapi.java | 44 +++++++- src/test/resources/multiple_user_error.json | 9 ++ src/test/resources/user_active.json | 46 ++++++++ src/test/resources/user_not_found_error.json | 9 ++ 10 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 src/test/resources/multiple_user_error.json create mode 100644 src/test/resources/user_active.json create mode 100644 src/test/resources/user_not_found_error.json diff --git a/ramls/edge-patron.raml b/ramls/edge-patron.raml index f4fb318..0d45359 100644 --- a/ramls/edge-patron.raml +++ b/ramls/edge-patron.raml @@ -623,17 +623,16 @@ types: text/plain: example: internal server error, contact administrator /registration-status/{emailId}: - uriParameters: - emailId: - description: The email ID of the patron. - type: string - required: true get: description: Get the patron details by email ID queryParameters: apikey: description: "API Key" type: string + emailId: + description: The email ID of the patron. + type: string + required: true responses: 200: description: patron information retrieved successfully diff --git a/ramls/staging_user.json b/ramls/staging_user.json index 98bcc85..330f13d 100644 --- a/ramls/staging_user.json +++ b/ramls/staging_user.json @@ -5,7 +5,7 @@ "type": "object", "properties": { "isEmailVerified": { - "description": "A boolean flag that indicates whether the patron has completed email verification. If this value is not provided when creating a new record, it will default to false. However, for Kiosk user registrations, this value should be sent true.", + "description": "A boolean flag that indicates whether the patron has completed email verification. If this value is not provided when creating a new record, it will default to false. However, for Kiosk user registrations, this value should be sent false.", "type": "boolean" }, "status": { diff --git a/src/main/java/org/folio/edge/patron/MainVerticle.java b/src/main/java/org/folio/edge/patron/MainVerticle.java index 528c2c2..73ff66b 100644 --- a/src/main/java/org/folio/edge/patron/MainVerticle.java +++ b/src/main/java/org/folio/edge/patron/MainVerticle.java @@ -81,7 +81,7 @@ public Router defineRoutes() { router.route(HttpMethod.POST, "/patron/account/:patronId/hold/:holdId/cancel") .handler(patronHandler::handleCancelHold); - router.route(HttpMethod.GET, "/patron/registration-status/:emailId") + router.route(HttpMethod.GET, "/patron/registration-status") .handler(patronHandler::handleGetPatronRegistrationStatus); return router; diff --git a/src/main/java/org/folio/edge/patron/PatronHandler.java b/src/main/java/org/folio/edge/patron/PatronHandler.java index 89ad616..e6c85d8 100644 --- a/src/main/java/org/folio/edge/patron/PatronHandler.java +++ b/src/main/java/org/folio/edge/patron/PatronHandler.java @@ -22,6 +22,7 @@ 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; import io.vertx.core.http.HttpHeaders; @@ -253,11 +254,19 @@ public void handleGetAllowedServicePoints(RoutingContext ctx) { } public void handleGetPatronRegistrationStatus(RoutingContext ctx) { - logger.info("handleGetPatronRegistrationStatus:: EMAIL_ID {}", ctx.request().getParam(PARAM_EMAIL_ID)); - super.handleCommon(ctx, new String[]{PARAM_EMAIL_ID}, new String[]{}, (client, params) -> { + String emailId = ctx.request().getParam(PARAM_EMAIL_ID); + logger.debug("handleGetPatronRegistrationStatus:: Fetching patron details by emailId {}", emailId); + if(StringUtils.isNullOrEmpty(emailId)) { + ctx.response() + .setStatusCode(400) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(getErrorMsg("EMAIL_NOT_PROVIDED", "emailId is missing in the request")); + return; + } + 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.getPatronRegistrationStatus(params.get(PARAM_EMAIL_ID), + patronClient.getPatronRegistrationStatus(emailId, resp -> handleRegistrationStatusResponse(ctx, resp), t -> handleProxyException(ctx, t)); }); @@ -464,27 +473,33 @@ private String get422ErrorMsg(int statusCode, String respBody){ return errorMessage; } - private String getFormattedErrorMsg(int statusCode, String respBody){ + private String getFormattedErrorMsg(int statusCode, String respBody) { logger.debug("getFormattedErrorMsg:: respBody {}", respBody); String errorMessage = ""; try { var errors = Json.decodeValue(respBody, Errors.class).getErrors(); if (errors != null && !errors.isEmpty()) { var error = errors.get(0); - Map errorMap = new HashMap<>(); - errorMap.put("message", error.getMessage()); - errorMap.put("code", error.getCode()); - errorMessage = Mappers.jsonMapper.writeValueAsString(errorMap); - } else { - errorMessage = getStructuredErrorMessage(statusCode, "No error message found in response"); + return getErrorMsg(error.getCode(), error.getMessage()); } - } catch(Exception ex) { + } catch (Exception ex) { logger.warn(ex.getMessage()); - errorMessage = getStructuredErrorMessage(statusCode, "A problem encountered when extracting error message"); + errorMessage = getStructuredErrorMessage(statusCode, respBody); } return errorMessage; } + private String getErrorMsg(String code, String errorMessage) { + Map errorMap = new HashMap<>(); + errorMap.put("errorMessage", errorMessage); + errorMap.put("code", code); + try { + return Mappers.jsonMapper.writeValueAsString(errorMap); + } catch (JsonProcessingException e) { + return getStructuredErrorMessage(500, "A problem encountered when extracting error message"); + } + } + private String getErrorMessage(int statusCode, String respBody){ if (statusCode == 422) 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 8ade70b..10164be 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 getExtPatronAccountByEmail(String email, Handler> responseHandler, - Handler exceptionHandler) { - String url = String.format("%s/patron/registration-status/%s", okapiURL, email); - get( - url, - tenant, - null, - responseHandler, - exceptionHandler); - } - public void getExtPatronAccounts(boolean expired, Handler> responseHandler, Handler exceptionHandler) { String url = String.format("%s/patron/account?expired=%s", okapiURL, expired); diff --git a/src/test/java/org/folio/edge/patron/MainVerticleTest.java b/src/test/java/org/folio/edge/patron/MainVerticleTest.java index aa94411..9ec84e4 100644 --- a/src/test/java/org/folio/edge/patron/MainVerticleTest.java +++ b/src/test/java/org/folio/edge/patron/MainVerticleTest.java @@ -273,14 +273,110 @@ public void testGetAccountPatronNotFound(TestContext context) throws Exception { } @Test - public void testGetAccountByEmail(TestContext context) { - logger.info("=== Test request for getting external_patron by email ==="); + public void testGetPatronRegistrationStatusWithoutEmail(TestContext context) { - RestAssured - .get(String.format("/patron/account/%s/by-email/%s?apikey=%s", extPatronId, "fgh@mail", apiKey)) + var response = RestAssured + .get(String.format("/patron/registration-status?apikey=%s", apiKey)) + .then() + .statusCode(400) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + var jsonResponse = new JsonObject(response.body().asString()); + assertEquals("EMAIL_NOT_PROVIDED", jsonResponse.getString("code")); + assertEquals("emailId is missing in the request", jsonResponse.getString("errorMessage")); + + response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "", apiKey)) + .then() + .statusCode(400) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + jsonResponse = new JsonObject(response.body().asString()); + assertEquals("EMAIL_NOT_PROVIDED", jsonResponse.getString("code")); + assertEquals("emailId is missing in the request", jsonResponse.getString("errorMessage")); + } + + @Test + public void testGetPatronRegistrationStatusWithActiveEmail(TestContext context) { + + final var response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "active@folio.com", apiKey)) .then() .statusCode(200) - .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON); + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + var expected = new JsonObject(readMockFile( + "/user_active.json")); + var actual = new JsonObject(response.body().asString()); + assertEquals(expected, actual); + } + + @Test + public void testGetPatronRegistrationStatusWithInvalidEmail() { + + final var response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "usernotfound@folio.com", apiKey)) + .then() + .statusCode(404) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + var jsonResponse = new JsonObject(response.body().asString()); + assertEquals("USER_NOT_FOUND", jsonResponse.getString("code")); + assertEquals("User does not exist", jsonResponse.getString("errorMessage")); + } + + @Test + public void testGetPatronRegistrationStatusWithMultipleUserEmail() { + + final var response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "multipleuser@folio.com", apiKey)) + .then() + .statusCode(400) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + var jsonResponse = new JsonObject(response.body().asString()); + assertEquals("MULTIPLE_USER_WITH_EMAIL", jsonResponse.getString("code")); + assertEquals("Multiple users found with the same email", jsonResponse.getString("errorMessage")); + } + + @Test + public void testGetPatronRegistrationStatusWithInvalidScenarios() { + + // when we are getting 404, we converted it to Errors.class. But there are cases where we get text/plain errors. + // In that case, code will return the error as it is. + var response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "invalid@folio.com", apiKey)) + .then() + .statusCode(404) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + var jsonResponse = new JsonObject(response.body().asString()); + assertEquals("404", jsonResponse.getString("code")); + assertEquals("Resource not found", jsonResponse.getString("errorMessage")); + + response = RestAssured + .get(String.format("/patron/registration-status?emailId=%s&apikey=%s", "empty@folio.com", apiKey)) + .then() + .statusCode(500) + .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .extract() + .response(); + + jsonResponse = new JsonObject(response.body().asString()); + assertEquals("500", jsonResponse.getString("code")); + assertEquals("unable to retrieve user details", jsonResponse.getString("errorMessage")); } @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 f6a660d..1a67012 100644 --- a/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java +++ b/src/test/java/org/folio/edge/patron/utils/PatronMockOkapi.java @@ -5,6 +5,7 @@ import static org.folio.edge.core.Constants.DAY_IN_MILLIS; import static org.folio.edge.core.Constants.TEXT_PLAIN; import static org.folio.edge.core.Constants.X_OKAPI_TOKEN; +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; @@ -22,10 +23,13 @@ import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Currency; import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -166,6 +170,9 @@ public Router defineRoutes() { router.route(HttpMethod.GET, "/circulation/requests/:requestId") .handler(this::getRequestHandler); + router.route(HttpMethod.GET, "/patron/registration-status/:emailId") + .handler(this::getRegistrationStatusHandler); + return router; } @@ -255,6 +262,42 @@ public void getExtPatronAccountHandler(RoutingContext ctx) { } } + public void getRegistrationStatusHandler(RoutingContext ctx) { + String token = ctx.request().getHeader(X_OKAPI_TOKEN); + String emailId = ctx.request().getParam(PARAM_EMAIL_ID); + 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 if(emailId.equals("active@folio.com")) { + ctx.response() + .setStatusCode(200) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(readMockFile("/user_active.json")); + } else if(emailId.equals("multipleuser@folio.com")) { + ctx.response() + .setStatusCode(400) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(readMockFile("/multiple_user_error.json")); + } else if(emailId.equals("usernotfound@folio.com")) { + ctx.response() + .setStatusCode(404) + .putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON) + .end(readMockFile("/user_not_found_error.json")); + } else if(emailId.equals("invalid@folio.com")) { + ctx.response() + .setStatusCode(404) + .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) + .end("Resource not found"); + } else { + ctx.response() + .setStatusCode(500) + .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) + .end("unable to retrieve user details"); + } + } + public void putExtPatronAccountHandler(RoutingContext ctx) { String token = ctx.request().getHeader(X_OKAPI_TOKEN); if (token == null || !token.equals(MOCK_TOKEN)) { @@ -667,7 +710,6 @@ public static Patron getPatron() { .preferredEmailCommunication(new ArrayList<>()) .build(); } - public static Charge getCharge(String itemId) { return Charge.builder() .item(getItem(itemId_overdue)) diff --git a/src/test/resources/multiple_user_error.json b/src/test/resources/multiple_user_error.json new file mode 100644 index 0000000..4f1468b --- /dev/null +++ b/src/test/resources/multiple_user_error.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "message": "Multiple users found with the same email", + "code": "MULTIPLE_USER_WITH_EMAIL", + "parameters": [] + } + ] +} diff --git a/src/test/resources/user_active.json b/src/test/resources/user_active.json new file mode 100644 index 0000000..cc3e1ae --- /dev/null +++ b/src/test/resources/user_active.json @@ -0,0 +1,46 @@ +{ + "id": "cacc29d8-cade-4312-a5f2-4eeac55d8697", + "externalSystemId": "active@folio.com", + "active": true, + "type": "patron", + "patronGroup": "63f8065f-df84-4e76-a36b-3ba32dbdc9e5", + "departments": [], + "proxyFor": [], + "personal": { + "lastName": "active", + "firstName": "folio", + "middleName": "", + "preferredFirstName": "new", + "email": "active@folio.com", + "phone": "555-123456", + "mobilePhone": "555-5678", + "addresses": [ + { + "id": "ec8c23d5-c301-4bba-8ade-39cc409e5d7e", + "countryId": "US", + "addressLine1": "123 Main St", + "addressLine2": "Apt 8", + "city": "Metropolis", + "region": "NY", + "postalCode": "12345", + "addressTypeId": "93d3d88d-499b-45d0-9bc7-ac73c3a19880", + "primaryAddress": true + } + ], + "preferredContactTypeId": "002" + }, + "enrollmentDate": "2024-08-29T13:29:39.248+00:00", + "expirationDate": "2026-08-29T00:00:00.000+00:00", + "createdDate": "2024-08-29T13:29:39.256+00:00", + "updatedDate": "2024-08-29T13:29:39.256+00:00", + "metadata": { + "createdDate": "2024-08-29T13:29:39.250+00:00", + "createdByUserId": "21457ab5-4635-4e56-906a-908f05e9233b", + "updatedDate": "2024-08-29T13:29:39.250+00:00", + "updatedByUserId": "21457ab5-4635-4e56-906a-908f05e9233b" + }, + "preferredEmailCommunication": [ + "Support", + "Programs" + ] +} diff --git a/src/test/resources/user_not_found_error.json b/src/test/resources/user_not_found_error.json new file mode 100644 index 0000000..22f4a31 --- /dev/null +++ b/src/test/resources/user_not_found_error.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "message": "User does not exist", + "code": "USER_NOT_FOUND", + "parameters": [] + } + ] +}