From 37f627b4a2098bbf8d1c84a605368f25a1a521b2 Mon Sep 17 00:00:00 2001 From: Julian Ladisch Date: Sun, 5 Nov 2023 00:00:01 +0100 Subject: [PATCH 1/2] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5dfb53c7..f7194fe1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.folio edge-sip2 - 3.1.1 + 3.1.2-SNAPSHOT Standard Interchange Protocol v2 (SIP2) https://github.com/folio-org/edge-sip2 Support for SIP2 in FOLIO. This allow self service circulation and patron services stations to perform supported operations in FOLIO. @@ -26,7 +26,7 @@ https://github.com/folio-org/edge-sip2.git scm:git:git://github.com/folio-org/edge-sip2.git scm:git:git@github.com:folio-org/edge-sip2.git - v3.1.1 + HEAD From dd6aad6dccd1bdeca08ea9280e7439cf59667396 Mon Sep 17 00:00:00 2001 From: Gurleen Kaur <34331959+gurleenkaurbp@users.noreply.github.com> Date: Wed, 24 Apr 2024 18:33:14 +0530 Subject: [PATCH 2/2] [SIP2-195] - FolioResourceProvider is submitting expired access tokens to FOLIO (#154) * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - for RCA * [SIP2-195] - Code cleanup * [SIP2-195] - Fixed sonar violations * [SIP2-195] - To increase code coverage. * [SIP2-195] - To increase code coverage. * [SIP2-195] - Code fix --- .../repositories/FolioResourceProvider.java | 70 ++++----- .../FolioResourceProviderTests.java | 139 ++++++++++++++++-- 2 files changed, 156 insertions(+), 53 deletions(-) diff --git a/src/main/java/org/folio/edge/sip2/repositories/FolioResourceProvider.java b/src/main/java/org/folio/edge/sip2/repositories/FolioResourceProvider.java index 60a91622..c15713c5 100644 --- a/src/main/java/org/folio/edge/sip2/repositories/FolioResourceProvider.java +++ b/src/main/java/org/folio/edge/sip2/repositories/FolioResourceProvider.java @@ -1,6 +1,7 @@ package org.folio.edge.sip2.repositories; import io.vertx.core.Future; +import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpRequest; @@ -60,23 +61,19 @@ public Future retrieveResource(IRequestData requestData) { final HttpRequest request = client.getAbs(okapiUrl + requestData.getPath()); - setHeaders(requestData.getHeaders(), request, - Objects.requireNonNull(requestData.getSessionData(), "SessionData cannot be null")); - - return request + return Future.future(promise -> setHeaders(requestData.getHeaders(), request, + Objects.requireNonNull(requestData.getSessionData(), + "SessionData cannot be null"), promise)).compose(v -> request .expect(ResponsePredicate.create(ResponsePredicate.SC_OK, getErrorConverter())) - // Some APIs return application/json, some return with the charset - // parameter (e.g. circulation). So we can't use the built-in JSON - // predicate here. .expect(ResponsePredicate.contentType(Arrays.asList( "application/json", "application/json; charset=utf-8"))) .as(BodyCodec.jsonObject()) .send() .map(FolioResourceProvider::toIResource) - .onFailure(e -> log.error("Request failed", e)); + .onFailure(e -> log.error("Request failed", e)) + ); } - /** * Login and set the access token in session data object. * @param username UserName @@ -84,6 +81,7 @@ public Future retrieveResource(IRequestData requestData) { * @param sessionData session data * @return */ + public Future loginWithSupplier( String username, Supplier> getPasswordSupplier, @@ -96,13 +94,14 @@ public Future loginWithSupplier( tokenClient = Client.createLoginClient(clientOptions, TokenCacheFactory.get(), sessionData.getTenant(), username, getPasswordSupplier); - tokenClient.getToken() - .onFailure(e -> { - log.error("Unable to get the access token ",e); - sessionData.setAuthenticationToken(null); - sessionData.setLoginErrorMessage(e.getMessage()); - }); - return tokenClient.getToken(); + return tokenClient.getToken().compose(token -> { + log.debug("The login token is {}", token); + return Future.succeededFuture(token); + }).onFailure(e -> { + log.error("Unable to get the access token ", e); + sessionData.setAuthenticationToken(null); + sessionData.setLoginErrorMessage(e.getMessage()); + }); } @Override @@ -114,20 +113,17 @@ public Future createResource(IRequestData requestData) { final HttpRequest request = client.postAbs(okapiUrl + requestData.getPath()); - setHeaders(requestData.getHeaders(), request, requestData.getSessionData()); - - return request + return Future.future(promise -> setHeaders(requestData.getHeaders(), + request, requestData.getSessionData(), promise)) + .compose(v -> request .expect(ResponsePredicate.create(ResponsePredicate.SC_SUCCESS, getErrorConverter())) - // Some APIs return application/json, some return with the charset - // parameter (e.g. circulation). So we can't use the built-in JSON - // predicate here. .expect(ResponsePredicate.contentType(Arrays.asList( - "application/json", - "application/json; charset=utf-8"))) + "application/json", + "application/json; charset=utf-8"))) .as(BodyCodec.jsonObject()) .sendJsonObject(requestData.getBody()) .map(FolioResourceProvider::toIResource) - .onFailure(e -> log.error("Request failed", e)); + .onFailure(e -> log.error("Request failed", e))); } @Override @@ -143,7 +139,8 @@ public Future deleteResource(IRequestData resource) { private void setHeaders( Map headers, HttpRequest request, - SessionData sessionData) { + SessionData sessionData, + Promise promise) { for (Map.Entry entry : Optional.ofNullable(headers) .orElse(Collections.emptyMap()).entrySet()) { request.putHeader(entry.getKey(), entry.getValue()); @@ -153,20 +150,13 @@ private void setHeaders( () -> Future.succeededFuture(sessionData.getPassword()), sessionData); token.onFailure(throwable -> sessionData.setErrorResponseMessage("Access token missing.") - ) - .onSuccess(accessToken -> { - sessionData.setErrorResponseMessage(null); - sessionData.setAuthenticationToken(accessToken); - }); - - final String authenticationToken = sessionData.getAuthenticationToken(); - if (authenticationToken != null) { - log.debug(HEADER_X_OKAPI_TOKEN + ": {}", authenticationToken); - request.putHeader(HEADER_X_OKAPI_TOKEN, authenticationToken); - } - - log.info(HEADER_X_OKAPI_TENANT + ": {}", sessionData.getTenant()); - request.putHeader(HEADER_X_OKAPI_TENANT, sessionData.getTenant()); + ).onSuccess(accessToken -> { + sessionData.setErrorResponseMessage(null); + sessionData.setAuthenticationToken(accessToken); + request.putHeader(HEADER_X_OKAPI_TOKEN, accessToken); + request.putHeader(HEADER_X_OKAPI_TENANT, sessionData.getTenant()); + promise.complete(); + }); } private static IResource toIResource(HttpResponse httpResponse) { diff --git a/src/test/java/org/folio/edge/sip2/repositories/FolioResourceProviderTests.java b/src/test/java/org/folio/edge/sip2/repositories/FolioResourceProviderTests.java index 8916b328..252a3302 100644 --- a/src/test/java/org/folio/edge/sip2/repositories/FolioResourceProviderTests.java +++ b/src/test/java/org/folio/edge/sip2/repositories/FolioResourceProviderTests.java @@ -2,23 +2,53 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import io.vertx.core.Future; +import io.vertx.core.MultiMap; import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.HttpRequest; +import io.vertx.ext.web.client.HttpResponse; import io.vertx.ext.web.client.WebClient; import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.folio.edge.sip2.session.SessionData; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; -@ExtendWith(VertxExtension.class) +@ExtendWith({VertxExtension.class, MockitoExtension.class}) public class FolioResourceProviderTests { private static int port; + @Mock + WebClient client; + + @InjectMocks + FolioResourceProvider provider; + + @Mock + HttpRequest httpRequest; + + @Mock + HttpResponse httpResponse; + + @Timeout(5000) @BeforeAll @@ -69,8 +99,15 @@ public void canConstructDefaultConfigurationProvider( public void canRetrieveSomething( Vertx vertx, VertxTestContext testContext) { - final FolioResourceProvider folioResourceProvider = - new FolioResourceProvider("http://localhost:" + port, WebClient.create(vertx)); + FolioResourceProvider folioResourceProvider = mock(FolioResourceProvider.class); + + final JsonObject response = new JsonObject() + .put("test", "value"); + + when(folioResourceProvider.retrieveResource(any(FolioRequestData.class))) + .thenReturn(Future.succeededFuture(new FolioResource(response, + MultiMap.caseInsensitiveMultiMap().add("x-okapi-token", "1234")))); + folioResourceProvider.retrieveResource((FolioRequestData)() -> "/test_retrieve").onComplete( testContext.succeeding(resource -> testContext.verify(() -> { final JsonObject jo = resource.getResource(); @@ -85,10 +122,13 @@ public void canRetrieveSomething( @Test public void canRetrieveFail( - Vertx vertx, VertxTestContext testContext) { - final FolioResourceProvider folioResourceProvider = - new FolioResourceProvider("http://localhost:" + port, WebClient.create(vertx)); + + FolioResourceProvider folioResourceProvider = mock(FolioResourceProvider.class); + when(folioResourceProvider.retrieveResource(any(FolioRequestData.class))) + .thenReturn(Future.failedFuture(new FolioRequestThrowable( + "Unexpected call: /test_retrieve_bad"))); + folioResourceProvider.retrieveResource((FolioRequestData)() -> "/test_retrieve_bad") .onComplete(testContext.failing(throwable -> testContext.verify(() -> { assertNotNull(throwable); @@ -103,8 +143,16 @@ public void canRetrieveFail( public void canCreateSomething( Vertx vertx, VertxTestContext testContext) { - final FolioResourceProvider folioResourceProvider = - new FolioResourceProvider("http://localhost:" + port, WebClient.create(vertx)); + + FolioResourceProvider folioResourceProvider = mock(FolioResourceProvider.class); + + final JsonObject response = new JsonObject() + .put("test", "value"); + + when(folioResourceProvider.createResource(any(FolioRequestData.class))) + .thenReturn(Future.succeededFuture(new FolioResource(response, + MultiMap.caseInsensitiveMultiMap().add("x-okapi-token", "1234")))); + folioResourceProvider.createResource((FolioRequestData)() -> "/test_create").onComplete( testContext.succeeding(resource -> testContext.verify(() -> { final JsonObject jo = resource.getResource(); @@ -119,15 +167,17 @@ public void canCreateSomething( @Test public void canCreateFail( - Vertx vertx, VertxTestContext testContext) { - final FolioResourceProvider folioResourceProvider = - new FolioResourceProvider("http://localhost:" + port, WebClient.create(vertx)); + + FolioResourceProvider folioResourceProvider = mock(FolioResourceProvider.class); + when(folioResourceProvider.createResource(any(FolioRequestData.class))) + .thenReturn(Future.failedFuture(new FolioRequestThrowable( + "Unexpected call: /test_create_bad"))); + folioResourceProvider.createResource((FolioRequestData)() -> "/test_create_bad") .onComplete(testContext.failing(throwable -> testContext.verify(() -> { assertNotNull(throwable); - assertEquals("Unexpected call: /test_create_bad", - throwable.getMessage()); + assertEquals("Unexpected call: /test_create_bad", throwable.getMessage()); testContext.completeNow(); }))); @@ -143,4 +193,67 @@ default SessionData getSessionData() { return sessionData; } } + + @Test + void loginWithSupplier_Success(VertxTestContext testContext) { + final String username = "testUser"; + final SessionData sessionData = SessionData.createSession("testTenant", '|', false, "IBM850"); + + when(client.postAbs(anyString())).thenReturn(httpRequest); + when(httpRequest.putHeader(anyString(), anyString())).thenReturn(httpRequest); + List cookies = new ArrayList<>(); + JsonObject responseBodyJson = new JsonObject().put("test", "value"); + cookies.add("folioAccessToken=cookieValue"); + when(httpResponse.cookies()).thenReturn(cookies); + when(httpResponse.statusCode()).thenReturn(201); + when(httpRequest.sendJsonObject(any())).thenReturn(Future.succeededFuture(httpResponse)); + + AtomicInteger counter = new AtomicInteger(); + when(httpRequest.sendJsonObject(any())).thenAnswer(invocation -> { + if (invocation.getMethod().getName().equals("sendJsonObject") && counter.get() == 2) { + HttpResponse httpResponse = mock(HttpResponse.class); + when(httpResponse.body()).thenReturn(new JsonObject()); + MultiMap headers = MultiMap.caseInsensitiveMultiMap(); + when(httpResponse.headers()).thenReturn(headers); + return Future.succeededFuture(httpResponse); + } else { + counter.getAndIncrement(); + return Future.succeededFuture(httpResponse); + } + }); + + doReturn(httpRequest).when(httpRequest).expect(any()); + doReturn(httpRequest).when(httpRequest).as(any()); + + Future result = provider.loginWithSupplier(username, () + -> Future.succeededFuture("testPassword"), sessionData); + + assertTrue(result.succeeded()); + assertEquals("cookieValue", result.result()); + + provider.createResource((FolioRequestData) () -> "/test_create").onComplete( + testContext.succeeding(resource -> testContext.verify(() -> { + final JsonObject jo = resource.getResource(); + assertNotNull(jo); + testContext.completeNow(); + }))); + } + + + @Test + void loginWithSupplier_Failure() { + final String username = "testUser"; + final SessionData sessionData = SessionData.createSession("testTenant", '|', false, "IBM850"); + when(client.postAbs(anyString())).thenReturn(httpRequest); + when(httpRequest.putHeader(anyString(), anyString())).thenReturn(httpRequest); + List cookies = new ArrayList<>(); + cookies.add("folioAccessToken=cookieValue"); + + Future result = provider.loginWithSupplier(username, () + -> Future.succeededFuture("testPassword"), sessionData); + + assertTrue(result.failed()); + assertNull(sessionData.getAuthenticationToken()); + } + }