Skip to content

Commit

Permalink
[EDGPATRON-160] - Add put API for /patron/{externalSystemId} (#138)
Browse files Browse the repository at this point in the history
* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}

* [EDGPATRON-160] - Add put API for /patron/{externalSystemId}
  • Loading branch information
gurleenkaurbp authored Dec 19, 2024
1 parent 5042bdf commit 1299b03
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 33 deletions.
67 changes: 67 additions & 0 deletions ramls/edge-patron.raml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,73 @@ types:
body:
text/plain:
example: internal server error, contact administrator

/{externalSystemId}:
uriParameters:
externalSystemId:
description: The UUID of a FOLIO user
type: string
pattern: ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
put:
description: |
Update a staging user based on external system ID.
queryParameters:
apikey:
description: "API Key"
type: string
body:
application/json:
type: staging_user
example: !include examples/staging_user.json
responses:
200:
description: |
staging user updated successfully
body:
application/json:
type: staging_user
example: !include examples/staging_user.json
201:
description: |
staging user created successfully
body:
application/json:
type: staging_user
example: !include examples/staging_user.json
400:
description: Bad request
body:
text/plain:
example: unable to process request
401:
description: Not authorized to perform requested action
body:
text/plain:
example: unable to create request
403:
description: Access Denied
body:
text/plain:
example: Access Denied
422:
description: Validation error
body:
text/plain:
example: Validation error
500:
description: |
Internal server error, e.g. due to misconfiguration
body:
text/plain:
example: internal server error, contact administrator
404:
description: Item with a given ID not found
body:
application/json:
type: external_patron_error_404
example: !include examples/external_patron_error.json


/account:
post:
description: |
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/folio/edge/patron/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class Constants {
public static final String PARAM_INSTANCE_ID = "instanceId";
public static final String PARAM_HOLD_ID = "holdId";
public static final String PARAM_EMAIL_ID = "emailId";
public static final String PARAM_EXTERNAL_SYSTEM_ID = "externalSystemId";
public static final String PARAM_REQUEST_ID = "requestId";

public static final String MSG_ACCESS_DENIED = "Access Denied";
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/folio/edge/patron/MainVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public Router defineRoutes() {
router.route(HttpMethod.POST, "/patron")
.handler(patronHandler::handlePostPatronRequest);

router.route(HttpMethod.PUT, "/patron/:externalSystemId")
.handler(patronHandler::handlePutPatronRequest);

router.route(HttpMethod.POST, "/patron/account/:patronId/instance/:instanceId/hold")
.handler(patronHandler::handlePlaceInstanceHold);

Expand Down
71 changes: 39 additions & 32 deletions src/main/java/org/folio/edge/patron/PatronHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,7 @@
import static org.folio.edge.core.Constants.APPLICATION_JSON;
import static org.folio.edge.core.Constants.X_OKAPI_TENANT;
import static org.folio.edge.core.Constants.X_OKAPI_TOKEN;
import static org.folio.edge.patron.Constants.EXTERNAL_SYSTEM_ID_CLAIM;
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.Constants.VIP_CLAIM;
import static org.folio.edge.patron.Constants.*;
import static org.folio.edge.patron.model.HoldCancellationValidator.validateCancelHoldRequest;

import com.amazonaws.util.StringUtils;
Expand All @@ -41,7 +23,10 @@
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.edge.core.Handler;
Expand Down Expand Up @@ -207,26 +192,39 @@ public void handleSecurePlaceItemHold(RoutingContext ctx) {
handleSecureCommon(ctx, this::handlePlaceItemHold);
}

public void handlePostPatronRequest(RoutingContext ctx) {
public void handlePatronRequest(RoutingContext ctx, BiConsumer<PatronOkapiClient, String> patronAction) {
if (ctx.body().asJsonObject() == null) {
logger.warn("handlePostPatronRequest:: missing body found");
logger.warn("handlePatronRequest:: missing body found");
ctx.response()
.setStatusCode(400)
.putHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.end(getErrorMsg("MISSING_BODY", "Request body must not null"));
.end(getErrorMsg("MISSING_BODY", "Request body must not be null"));
return;
}

final String body = String.valueOf(ctx.body().asJsonObject());
super.handleCommon(ctx, new String[]{}, new String[]{}, (client, params) -> {
String alternateTenantId = ctx.request().getParam("alternateTenantId", client.tenant);
final PatronOkapiClient patronClient = new PatronOkapiClient(client, alternateTenantId);
patronAction.accept(patronClient, body);
});
}

public void handlePostPatronRequest(RoutingContext ctx) {
handlePatronRequest(ctx, (patronClient, body) ->
patronClient.postPatron(body,
resp -> handleProxyResponse(ctx, resp),
t -> handleProxyException(ctx, t));
});
t -> handleProxyException(ctx, t)));
}

public void handlePutPatronRequest(RoutingContext ctx) {
handlePatronRequest(ctx, (patronClient, body) ->
patronClient.putPatron(ctx.request().getParam(PARAM_EXTERNAL_SYSTEM_ID), body,
resp -> handlePutPatronResponse(ctx, resp),
t -> handleProxyException(ctx, t)));
}


public void handleCancelHold(RoutingContext ctx) {
String validationResult = validateCancelHoldRequest(ctx.body().asJsonObject());
if ( validationResult != null) {
Expand Down Expand Up @@ -400,32 +398,41 @@ protected void handleProxyResponse(RoutingContext ctx, HttpResponse<Buffer> resp
}
}

protected void handleRegistrationStatusResponse(RoutingContext ctx, HttpResponse<Buffer> resp) {
protected void handleResponse(RoutingContext ctx, HttpResponse<Buffer> resp, String logPrefix,
UnaryOperator<String> errorMessageFunction) {
HttpServerResponse serverResponse = ctx.response();

int statusCode = resp.statusCode();
serverResponse.setStatusCode(statusCode);

String respBody = resp.bodyAsString();
if (logger.isDebugEnabled() ) {
logger.debug("handleRegistrationStatusResponse:: response {} ", respBody);
if (logger.isDebugEnabled()) {
logger.debug("{}:: response {}", logPrefix, respBody);
}

String contentType = resp.getHeader(HttpHeaders.CONTENT_TYPE.toString());

if (resp.statusCode() < 400 && Objects.nonNull(respBody)){
if (statusCode < 400 && Objects.nonNull(respBody)) {
setContentType(serverResponse, contentType);
serverResponse.end(respBody); //not an error case, pass on the response body as received
}
else {
serverResponse.end(respBody); // Not an error case, pass on the response body as received
} else {
String errorMsg = (statusCode == 404 || statusCode == 400)
? getFormattedErrorMsg(statusCode, respBody)
: getStructuredErrorMessage(statusCode, respBody);
: errorMessageFunction.apply(respBody);
setContentType(serverResponse, APPLICATION_JSON);
serverResponse.end(errorMsg);
}
}

protected void handleRegistrationStatusResponse(RoutingContext ctx, HttpResponse<Buffer> resp) {
handleResponse(ctx, resp, "handleRegistrationStatusResponse", body -> getStructuredErrorMessage(resp.statusCode(), body));
}

protected void handlePutPatronResponse(RoutingContext ctx, HttpResponse<Buffer> resp) {
handleResponse(ctx, resp, "handlePutPatronResponse", body -> getErrorMessage(resp.statusCode(), body));
}


@Override
protected void handleProxyException(RoutingContext ctx, Throwable t) {
logger.error("Exception retrieving data from mod-patron:", t);
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,17 @@ public void postPatron(String requestBody,
exceptionHandler);
}

public void putPatron(String externalSystemId, String requestBody,
Handler<HttpResponse<Buffer>> responseHandler, Handler<Throwable> exceptionHandler) {
put(
format("%s/patron/%s", okapiURL, externalSystemId),
tenant,
requestBody,
null,
responseHandler,
exceptionHandler);
}

public void cancelHold(String patronId, String holdId, JsonObject holdCancellationRequest,
Handler<HttpResponse<Buffer>> responseHandler, Handler<Throwable> exceptionHandler) {
getRequest(holdId,
Expand Down
105 changes: 104 additions & 1 deletion src/test/java/org/folio/edge/patron/MainVerticleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public class MainVerticleTest {
private static final String itemId = UUID.randomUUID().toString();
private static final String instanceId = UUID.randomUUID().toString();
private static final String holdId = UUID.randomUUID().toString();
private static final String EXTERNAL_SYSTEM_ID = UUID.randomUUID().toString();
private static final String apiKey = ApiKeyUtils.generateApiKey(10, "diku", "diku");
private static final String badApiKey = apiKey + "0000";
private static final String unknownTenantApiKey = ApiKeyUtils.generateApiKey(10, "bogus", "diku");
Expand Down Expand Up @@ -970,6 +971,23 @@ public void testPostPatron_201(TestContext context) {
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
}

@Test
public void testPutPatron_200(TestContext context) {
logger.info("=== testPutPatron_200 ===");
JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-put-request.json"));
jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_200");
RestAssured
.with()
.body(jsonObject.encode())
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", EXTERNAL_SYSTEM_ID, apiKey))
.then()
.statusCode(200)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON);
}


@Test
public void testPostPatron_200(TestContext context) {
logger.info("=== testPostPatron_200 ===");
Expand Down Expand Up @@ -1004,6 +1022,25 @@ public void testPostPatron_400(TestContext context) {
.body("code", is(400));
}

@Test
public void testPutPatron_400(TestContext context) {
logger.info("=== testPutPatron_400 ===");
JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-put-request.json"));
jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_400");
RestAssured
.with()
.body(jsonObject.encode())
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", EXTERNAL_SYSTEM_ID, apiKey))
.then()
.statusCode(400)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.body("errorMessage", is("A bad exception occurred"))
.body("code", is(400));
}


@Test
public void testPostPatron_422(TestContext context) {
logger.info("=== testPostPatron_422 ===");
Expand All @@ -1022,6 +1059,24 @@ public void testPostPatron_422(TestContext context) {
.body("code", is(422));
}

@Test
public void testPutPatron_422(TestContext context) {
logger.info("=== testPutPatron_422 ===");
JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-put-request.json"));
jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_422");
RestAssured
.with()
.body(jsonObject.encode())
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", EXTERNAL_SYSTEM_ID, apiKey))
.then()
.statusCode(422)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.body("errorMessage", is("ABC is required"))
.body("code", is(422));
}

@Test
public void testPostPatron_500(TestContext context) {
logger.info("=== testPostPatron_500 ===");
Expand Down Expand Up @@ -1051,10 +1106,58 @@ public void testPostPatron_NoRequestBody(TestContext context) {
.then()
.statusCode(400)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.body("errorMessage", is("Request body must not null"))
.body("errorMessage", is("Request body must not be null"))
.body("code", is("MISSING_BODY"));
}

@Test
public void testPutPatron_500(TestContext context) {
logger.info("=== testPutPatron_500 ===");
JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-put-request.json"));
jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_500");
RestAssured
.with()
.body(jsonObject.encode())
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", EXTERNAL_SYSTEM_ID, apiKey))
.then()
.statusCode(500)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.body("errorMessage", is("Server exception occurred"))
.body("code", is(500));
}

@Test
public void testPutPatron_NoRequestBody(TestContext context) {
logger.info("=== testPutPatron_NoRequestBody ===");
RestAssured
.with()
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", EXTERNAL_SYSTEM_ID, apiKey))
.then()
.statusCode(400)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON)
.body("errorMessage", is("Request body must not be null"))
.body("code", is("MISSING_BODY"));
}

@Test
public void testPutPatron_NoParam(TestContext context) {
logger.info("=== testPutPatron_NoParam ===");
JsonObject jsonObject = new JsonObject(readMockFile("/staging-users-put-request.json"));
jsonObject.getJsonObject("generalInfo").put("firstName", "TEST_STATUS_CODE_405");
RestAssured
.with()
.body(jsonObject.encode())
.contentType(APPLICATION_JSON)
.put(
String.format("/patron/%s?apikey=%s", "", apiKey))
.then()
.statusCode(405);
}

@Test
public void testPlaceInstanceHoldInstanceNotFound(TestContext context) throws Exception {
logger.info("=== Test place instance hold w/ instance not found ===");
Expand Down
Loading

0 comments on commit 1299b03

Please sign in to comment.