Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 12 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.Function;

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,
Function<String, 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
Loading