diff --git a/cloudplatform/connectivity-destination-service/pom.xml b/cloudplatform/connectivity-destination-service/pom.xml index 36fce7b95..1eff93b63 100644 --- a/cloudplatform/connectivity-destination-service/pom.xml +++ b/cloudplatform/connectivity-destination-service/pom.xml @@ -41,10 +41,10 @@ com.sap.cloud.sdk.cloudplatform cloudplatform-connectivity - - com.sap.cloud.sdk.cloudplatform - connectivity-apache-httpclient4 - + + com.sap.cloud.sdk.cloudplatform + connectivity-apache-httpclient5 + com.sap.cloud.sdk.cloudplatform @@ -97,26 +97,14 @@ commons-io commons-io - - org.apache.httpcomponents - httpcore - - - commons-logging - commons-logging - - - - - org.apache.httpcomponents - httpclient - - - commons-logging - commons-logging - - - + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.client5 + httpclient5 + io.vavr vavr diff --git a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProvider.java b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProvider.java index 504b35d83..4b1fc00ba 100644 --- a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProvider.java +++ b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProvider.java @@ -11,7 +11,7 @@ import javax.annotation.Nonnull; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import com.google.common.base.Strings; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; @@ -96,13 +96,9 @@ private static List
getDestinationHeaders( @Nonnull final List false; + default -> true; + }; } } diff --git a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapter.java b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapter.java index 51b964383..9d65fa44a 100644 --- a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapter.java +++ b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapter.java @@ -13,11 +13,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpUriRequest; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpStatus; import com.google.common.base.Strings; import com.sap.cloud.environment.servicebinding.api.DefaultServiceBindingAccessor; @@ -32,6 +32,7 @@ import io.vavr.control.Try; import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -126,53 +127,62 @@ String getConfigurationAsJson( serviceDestinationLoader.apply(strategy.behalf()), () -> "Destination for Destination Service on behalf of " + strategy.behalf() + " not found."); - final HttpUriRequest request = prepareRequest(servicePath, strategy); + final ClassicHttpRequest request = prepareRequest(servicePath, strategy); - final HttpResponse response; try { - response = HttpClientAccessor.getHttpClient(serviceDestination).execute(request); + return ApacheHttpClient5Accessor + .getHttpClient(serviceDestination) + .execute(request, new DestinationHttpClientResponseHandler(request)); } catch( final IOException e ) { throw new DestinationAccessException(e); } - return handleResponse(request, response); } - @Nonnull - private static String handleResponse( final HttpUriRequest request, final HttpResponse response ) + @RequiredArgsConstructor( access = AccessLevel.PRIVATE ) + static class DestinationHttpClientResponseHandler extends BasicHttpClientResponseHandler { - final StatusLine status = response.getStatusLine(); - final int statusCode = status.getStatusCode(); - final String reasonPhrase = status.getReasonPhrase(); + final ClassicHttpRequest request; - log.debug("Destination service returned HTTP status {} ({})", statusCode, reasonPhrase); + @Override + public String handleResponse( final ClassicHttpResponse response ) + { + final int statusCode = response.getCode(); + final String reasonPhrase = response.getReasonPhrase(); - Try maybeBody = Try.of(() -> HttpEntityUtil.getResponseBody(response)); - if( maybeBody.isFailure() ) { - final var ex = - new DestinationAccessException("Failed to read body from HTTP response", maybeBody.getCause()); - maybeBody = Try.failure(ex); - } + log.debug("Destination service returned HTTP status {} ({})", statusCode, reasonPhrase); - if( statusCode == HttpStatus.SC_OK ) { - final var ex = new DestinationAccessException("Failed to get destinations: no body returned in response."); - maybeBody = maybeBody.filter(it -> !Strings.isNullOrEmpty(it), () -> ex); - return maybeBody.get(); - } + Try maybeBody = Try.of(() -> handleEntity(response.getEntity())); + if( maybeBody.isFailure() ) { + final var ex = + new DestinationAccessException("Failed to read body from HTTP response", maybeBody.getCause()); + maybeBody = Try.failure(ex); + } - final String requestUri = request.getURI().getPath(); - if( statusCode == HttpStatus.SC_NOT_FOUND ) { - throw new DestinationNotFoundException(null, "Destination could not be found for path " + requestUri + "."); + if( statusCode == HttpStatus.SC_OK ) { + final var ex = + new DestinationAccessException("Failed to get destinations: no body returned in response."); + maybeBody = maybeBody.filter(it -> !Strings.isNullOrEmpty(it), () -> ex); + return maybeBody.get(); + } + + final String requestUri = request.getPath(); + if( statusCode == HttpStatus.SC_NOT_FOUND ) { + throw new DestinationNotFoundException( + null, + "Destination could not be found for path " + requestUri + "."); + } + final String message = + "Failed to get destinations: destination service responded with HTTP status %s (%S) at '%s'." + .formatted(statusCode, reasonPhrase, requestUri); + final String messageWithBody = + message + " Body: %s".formatted(maybeBody.getOrElseGet(Throwable::getMessage)); + log.error(messageWithBody); + throw new DestinationAccessException(message); } - final String message = - "Failed to get destinations: destination service responded with HTTP status %s (%S) at '%s'." - .formatted(statusCode, reasonPhrase, requestUri); - final String messageWithBody = message + " Body: %s".formatted(maybeBody.getOrElseGet(Throwable::getMessage)); - log.error(messageWithBody); - throw new DestinationAccessException(message); } - private HttpUriRequest prepareRequest( final String servicePath, final DestinationRetrievalStrategy strategy ) + private ClassicHttpRequest prepareRequest( final String servicePath, final DestinationRetrievalStrategy strategy ) { final URI requestUri; try { @@ -183,7 +193,7 @@ private HttpUriRequest prepareRequest( final String servicePath, final Destinati } log.debug("Querying Destination Service via URI {}.", requestUri); - final HttpUriRequest request = new HttpGet(requestUri); + final ClassicHttpRequest request = new HttpGet(requestUri); if( !servicePath.startsWith(DestinationService.PATH_DEFAULT) && !servicePath.startsWith(DestinationService.PATH_V2) ) { diff --git a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxy.java b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxy.java index 89433808b..0a0b715cf 100644 --- a/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxy.java +++ b/cloudplatform/connectivity-destination-service/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxy.java @@ -11,11 +11,14 @@ import javax.annotation.Nonnull; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.http.HttpMessage; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpHead; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpHead; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.HttpMessage; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.EntityUtils; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException; @@ -76,7 +79,7 @@ public class TransparentProxy implements DestinationLoader private static final String SET_COOKIE_HEADER = "Set-Cookie"; private static final Integer DEFAULT_PORT = 80; private static final String SCHEME_SEPARATOR = "://"; - private static final String HTTP_SCHEME = org.apache.http.HttpHost.DEFAULT_SCHEME_NAME + SCHEME_SEPARATOR; + private static final String HTTP_SCHEME = HttpHost.DEFAULT_SCHEME.getId() + SCHEME_SEPARATOR; private static final String PORT_SEPARATOR = ":"; private static final String HOST_CONTAINS_PATH_ERROR_MESSAGE_TEMPLATE = "Host '%s' contains a path '%s'. Paths are not allowed in host registration."; @@ -279,7 +282,7 @@ private static String normalizeHostWithScheme( @Nonnull final String host ) private static TransparentProxyDestination verifyDestination( @Nonnull final TransparentProxyDestination destination ) { - final HttpClient httpClient = HttpClientAccessor.getHttpClient(destination); + final HttpClient httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); final URI destinationUri = URI.create(uri); final HttpHead headRequest = new HttpHead(destinationUri); final String destinationName = getDestinationName(destination, destinationUri); @@ -288,8 +291,8 @@ private static TransparentProxyDestination verifyDestination( .debug( "Performing HEAD request to destination with name {} to verify the destination exists", destinationName); - final Supplier tpDestinationVerifierSupplier = prepareSupplier(httpClient, headRequest); - HttpResponse response = null; + final Supplier tpDestinationVerifierSupplier = prepareSupplier(httpClient, headRequest); + ClassicHttpResponse response = null; try { response = ResilienceDecorator.executeSupplier(tpDestinationVerifierSupplier, resilienceConfiguration); verifyTransparentProxyResponse(response, destinationName); @@ -303,7 +306,7 @@ private static TransparentProxyDestination verifyDestination( finally { if( response != null ) { try { - org.apache.http.util.EntityUtils.consume(response.getEntity()); + EntityUtils.consume(response.getEntity()); } catch( IOException e ) { log.warn("Failed to close HTTP response", e); @@ -340,7 +343,7 @@ private static void verifyTransparentProxyResponse( final HttpResponse response, throw new DestinationAccessException(FAILED_TO_VERIFY_DESTINATION + "Response is null."); } if( response.containsHeader(SET_COOKIE_HEADER) ) { - final org.apache.http.Header[] header = response.getHeaders(SET_COOKIE_HEADER); + final org.apache.hc.core5.http.Header[] header = response.getHeaders(SET_COOKIE_HEADER); final List cookieNames = Arrays.stream(header).map(h -> h.getValue().split("=", 2)[0]).toList(); log .warn( @@ -348,7 +351,7 @@ private static void verifyTransparentProxyResponse( final HttpResponse response, cookieNames); } - final int statusCode = response.getStatusLine().getStatusCode(); + final int statusCode = response.getCode(); final String errorInternalCode = getHeaderValue(response, X_ERROR_INTERNAL_CODE_HEADER); final String errorMessage = getHeaderValue(response, X_ERROR_MESSAGE_HEADER); final String errorOrigin = getHeaderValue(response, X_ERROR_ORIGIN_HEADER); @@ -378,11 +381,14 @@ private static void verifyTransparentProxyResponse( final HttpResponse response, } @Nonnull - private static Supplier prepareSupplier( final HttpClient httpClient, final HttpHead headRequest ) + private static + Supplier + prepareSupplier( final HttpClient httpClient, final HttpHead headRequest ) { return () -> { try { - return httpClient.execute(headRequest); + // migration from apache httpclient4 to httpclient5 by possibly adding a response handler to httpClient.execute(headRequest); + return httpClient.execute(headRequest, classicHttpResponse -> classicHttpResponse); } catch( final IOException e ) { throw new DestinationAccessException(FAILED_TO_VERIFY_DESTINATION, e); diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProviderTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProviderTest.java index 7251358fe..9022aab16 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProviderTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/AuthTokenHeaderProviderTest.java @@ -8,7 +8,7 @@ import java.util.Arrays; import java.util.Collection; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapterTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapterTest.java index 477659db6..4d9fbf9e4 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapterTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAdapterTest.java @@ -22,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -40,10 +40,13 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.http.HttpVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.entity.InputStreamEntity; -import org.apache.http.message.BasicHttpResponse; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -52,6 +55,7 @@ import org.mockito.Mockito; import com.auth0.jwt.JWT; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; import com.github.tomakehurst.wiremock.junit5.WireMockTest; import com.sap.cloud.environment.servicebinding.api.DefaultServiceBinding; @@ -59,6 +63,7 @@ import com.sap.cloud.environment.servicebinding.api.ServiceBinding; import com.sap.cloud.environment.servicebinding.api.ServiceBindingAccessor; import com.sap.cloud.environment.servicebinding.api.ServiceIdentifier; +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationServiceAdapter.DestinationHttpClientResponseHandler; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException; import com.sap.cloud.sdk.cloudplatform.exception.MultipleServiceBindingsException; @@ -93,6 +98,9 @@ class DestinationServiceAdapterTest private String xsuaaToken; + @RegisterExtension + static final WireMockExtension server = WireMockExtension.newInstance().build(); + @BeforeAll static void setupSession() { @@ -434,20 +442,33 @@ void getDestinationServiceProviderTenantShouldThrowForMissingId() @Test void testErrorHandling() { - final var httpClient = mock(HttpClient.class); - final var destination = DefaultHttpDestination.builder("http://foo").build(); - HttpClientAccessor.setHttpClientFactory(( dest ) -> dest == destination ? httpClient : null); + final HttpClient httpClient = mock(HttpClient.class); + final HttpDestination destination = DefaultHttpDestination.builder("http://foo").build(); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> httpClient); final var destinations = Collections.singletonMap(OnBehalfOf.TECHNICAL_USER_PROVIDER, destination); final var SUT = new DestinationServiceAdapter(destinations::get, () -> null, null); - // setup 400 response - var stream400 = spy(new ByteArrayInputStream("bad, evil request".getBytes(StandardCharsets.UTF_8))); - var response400 = new BasicHttpResponse(HttpVersion.HTTP_1_1, 400, "Bad Request"); - response400.setEntity(new InputStreamEntity(stream400)); - doReturn(response400).when(httpClient).execute(any()); - - // test + // prepare 400 response with a spied input stream so we can verify it is closed + final var stream400 = spy(new ByteArrayInputStream("bad, evil request".getBytes(StandardCharsets.UTF_8))); + final var response400 = new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST, "Bad Request"); + response400.setEntity(new InputStreamEntity(stream400, -1, ContentType.TEXT_PLAIN)); + + // prepare 404 response with a spied input stream + final var stream404 = spy(new ByteArrayInputStream("Nothing here.".getBytes(StandardCharsets.UTF_8))); + final var response404 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND, "Not Found"); + response404.setEntity(new InputStreamEntity(stream404, -1, ContentType.TEXT_PLAIN)); + + // make the mocked httpClient call the provided response handler with our prepared responses + doAnswer(invocation -> { + final HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response400); + }).doAnswer(invocation -> { + final HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response404); + }).when(httpClient).execute(any(ClassicHttpRequest.class), any(DestinationHttpClientResponseHandler.class)); + + // first invocation -> 400 -> DestinationAccessException assertThatThrownBy( () -> SUT.getConfigurationAsJson("/service-path", withoutToken(OnBehalfOf.TECHNICAL_USER_PROVIDER))) .isInstanceOf(DestinationAccessException.class) @@ -456,13 +477,7 @@ void testErrorHandling() // verify closed stream Mockito.verify(stream400, atLeastOnce()).close(); - // setup 404 response - var stream404 = spy(new ByteArrayInputStream("Nothing here.".getBytes(StandardCharsets.UTF_8))); - var response404 = new BasicHttpResponse(HttpVersion.HTTP_1_1, 404, "Not Found"); - response404.setEntity(new InputStreamEntity(stream404)); - doReturn(response404).when(httpClient).execute(any()); - - // test + // second invocation -> 404 -> DestinationNotFoundException assertThatThrownBy( () -> SUT.getConfigurationAsJson("/service-path", withoutToken(OnBehalfOf.TECHNICAL_USER_PROVIDER))) .describedAs("A 404 should produce a DestinationNotFoundException") @@ -472,7 +487,7 @@ void testErrorHandling() // verify closed stream Mockito.verify(stream404, atLeastOnce()).close(); - HttpClientAccessor.setHttpClientFactory(null); + ApacheHttpClient5Accessor.setHttpClientFactory(null); } private static DestinationServiceAdapter createSut( @Nonnull final ServiceBinding... serviceBindings ) diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAuthenticationTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAuthenticationTest.java index 62a9e7127..76e4747d0 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAuthenticationTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceAuthenticationTest.java @@ -22,7 +22,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.apache.http.HttpHeaders; +import org.apache.hc.core5.http.HttpHeaders; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceTest.java index 660149345..e8309ed69 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/DestinationServiceTest.java @@ -28,9 +28,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import java.io.IOException; import java.net.URI; import java.time.Duration; import java.time.Instant; @@ -52,9 +50,11 @@ import java.util.function.Function; import java.util.stream.Stream; -import org.apache.http.HttpVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.message.BasicHttpResponse; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.assertj.core.api.Condition; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.AfterEach; @@ -73,6 +73,7 @@ import com.google.gson.stream.MalformedJsonException; import com.sap.cloud.environment.servicebinding.api.ServiceBinding; import com.sap.cloud.sdk.cloudplatform.cache.CacheKey; +import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationServiceAdapter.DestinationHttpClientResponseHandler; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException; import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration; @@ -515,21 +516,20 @@ void testGettingDestinationPropertiesSubscriber() .containsExactly("CC8-HTTP-BASIC", "CC8-HTTP-CERT1", "CC8-HTTP-CERT", destinationName); } + @SneakyThrows @Test // slow test, run manually if needed void destinationServiceTimeOutWhileGettingDestination() - throws IOException { final HttpDestination serviceDestination = DefaultHttpDestination.builder("").build(); // prepare slow HttpClient - HttpClientFactory factory = HttpClientAccessor.getHttpClientFactory(); - HttpClient cl = mock(HttpClient.class); + final HttpClient cl = mock(HttpClient.class); doAnswer(invocation -> { Thread.sleep(TEST_TIMEOUT); return null; - }).when(cl).execute(any()); - HttpClientAccessor.setHttpClientFactory(dest -> cl); + }).when(cl).execute(any(ClassicHttpRequest.class), any(DestinationHttpClientResponseHandler.class)); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> cl); // prepare adapter final DestinationServiceAdapter adapter = @@ -558,11 +558,11 @@ void destinationServiceTimeOutWhileGettingDestination() .isExactlyInstanceOf(DestinationAccessException.class) .hasRootCauseExactlyInstanceOf(TimeoutException.class); - verify(cl, times(1)).execute(any()); + verify(cl, times(1)).execute(any(ClassicHttpRequest.class), any(DestinationHttpClientResponseHandler.class)); verify(adapter, times(1)).getConfigurationAsJson(eq("/v1/destinations/SomeDestinationName"), any()); // reset - HttpClientAccessor.setHttpClientFactory(null); + ApacheHttpClient5Accessor.setHttpClientFactory(null); } @Test @@ -910,21 +910,25 @@ void testCachingHttpDestination() assertThat(DestinationService.Cache.instanceSingle().estimatedSize()).isEqualTo(1); } + @SneakyThrows @Test void testUnknownDestinationLeadsToDestinationNotFoundException() - throws IOException { // prepare 404 HttpClient - final HttpClientFactory factory = HttpClientAccessor.getHttpClientFactory(); final HttpClient client404 = mock(HttpClient.class); - when(client404.execute(any())).thenReturn(new BasicHttpResponse(HttpVersion.HTTP_1_1, 404, "Not found")); - HttpClientAccessor.setHttpClientFactory(dest -> client404); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> client404); + + final var response404 = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND, "Not Found"); + doAnswer(invocation -> { + final HttpClientResponseHandler handler = invocation.getArgument(1); + return handler.handleResponse(response404); + }).when(client404).execute(any(ClassicHttpRequest.class), any(DestinationHttpClientResponseHandler.class)); assertThatThrownBy(() -> loader.tryGetDestination("UnknownDestination").get()) .isInstanceOf(DestinationNotFoundException.class); // reset - HttpClientAccessor.setHttpClientFactory(factory); + ApacheHttpClient5Accessor.setHttpClientFactory(null); } private void tryGetDestinationTwice( String destinationName, String responseDestination, int numberOfFetches ) @@ -1887,11 +1891,11 @@ public void performanceTest() assertThat(PrincipalAccessor.getCurrentPrincipal().getPrincipalId()).isEqualTo("principal-" + i); destination = loader.tryGetDestination(name, DestinationOptions.builder().build()).get().asHttp(); - httpClient = HttpClientAccessor.getHttpClient(destination); + httpClient = ApacheHttpClient5Accessor.getHttpClient(destination); System.out.println("[" + LocalDateTime.now() + "] Got " + name); } assertThat(DestinationService.Cache.instanceSingle().estimatedSize()).isEqualTo(1); - assertThat(httpClient).isSameAs(HttpClientAccessor.getHttpClient(destination)); + assertThat(httpClient).isSameAs(ApacheHttpClient5Accessor.getHttpClient(destination)); } @Test diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxyTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxyTest.java index b22b2c93e..715fd9926 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxyTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/TransparentProxyTest.java @@ -2,20 +2,23 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.IOException; -import org.apache.http.HttpStatus; -import org.apache.http.HttpVersion; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.message.BasicHttpResponse; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException; import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException; @@ -45,11 +48,10 @@ void resetLoader() { TransparentProxy.uri = null; TransparentProxy.providerTenantId = null; - HttpClientAccessor.setHttpClientFactory(null); + ApacheHttpClient5Accessor.setHttpClientFactory(null); } private T executeWithTenant( java.util.concurrent.Callable callable ) - throws Exception { Tenant tenant = new DefaultTenant("tenant-id", ""); return TenantAccessor.executeWithTenant(tenant, callable); @@ -64,9 +66,14 @@ void testRegisterWithLocalhostHost() // Test with localhost which should always be reachable final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("127.0.0.1", "tenant-id"); @@ -85,9 +92,14 @@ void testRegisterWithHostWithoutScheme() // Test that http:// is automatically added to host without scheme final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "tenant-id"); @@ -105,9 +117,14 @@ void testRegisterWithHostWithHttpScheme() // Test that existing http:// scheme is preserved final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("http://gateway", "tenant-id"); @@ -125,9 +142,14 @@ void testRegisterWithHostWithHttpsScheme() // Test that existing https:// scheme is preserved final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("https://gateway", "tenant-id"); @@ -173,9 +195,14 @@ void testRegisterWithHostPortAndProviderTenantId() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", 8080, "provider-tenant-123"); @@ -189,9 +216,14 @@ void testTenantIdFromOptionsOverridesProviderTenantId() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); // Register with provider tenant ID TransparentProxy.register("gateway", 8080, "provider-tenant-fallback"); @@ -223,9 +255,14 @@ void testContextTenantPreventsFallbackToProviderTenantId() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); // Register with provider tenant ID TransparentProxy.register("gateway", 8080, "provider-tenant-fallback"); @@ -236,6 +273,7 @@ void testContextTenantPreventsFallbackToProviderTenantId() Try result = TenantAccessor.executeWithTenant(contextTenant, () -> loader.tryGetDestination("test-destination")); + assertThat(result).isNotNull(); assertThat(result.isSuccess()).isTrue(); HttpDestination destination = result.get().asHttp(); @@ -253,9 +291,14 @@ void testRegisterWithHostAndProviderTenantId() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "provider-tenant-456"); @@ -272,9 +315,14 @@ void testRegisterWithHostPortProviderTenantIdAndScheme() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("https://gateway", 443, "provider-tenant-789"); @@ -291,9 +339,14 @@ void testRegisterWithProviderTenantIdStoresCorrectly() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); // Test with 3-parameter version TransparentProxy.register("localhost", 8080, "tenant-abc"); @@ -324,9 +377,14 @@ void testRegisterWithProviderTenantIdFailsOnSecondRegistration() throws IOException { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "provider-tenant-1"); @@ -344,9 +402,14 @@ void testTryGetDestinationWithoutOptions() throws Exception { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "tenant-id"); @@ -364,9 +427,14 @@ void testTryGetDestinationWithOptions() throws Exception { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "tenant-id"); @@ -386,9 +454,14 @@ void testTryGetDestinationReturnsSuccessfulTry() throws Exception { final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("https://gateway", "tenant-id"); @@ -434,9 +507,14 @@ void testMultipleRegistrationsThrowsException() // Test that first registration succeeds final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); TransparentProxy.register("gateway", "tenant-id"); @@ -462,9 +540,14 @@ void testDestinationLoaderInterface() // Test that TransparentProxyLoader properly implements DestinationLoader final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); assertThat(loader).isInstanceOf(DestinationLoader.class); @@ -489,9 +572,14 @@ void testRegisterWithDifferentSchemes() // Test various schemes to ensure they are preserved final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); String[] schemes = { "http://", "https://", "ftp://", "custom://" }; String hostname = "gateway"; @@ -518,9 +606,14 @@ void testRegisterWithPortInUriButNoPath() TransparentProxy.register("https://gateway:9443", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("test-destination")); assertThat(result.isSuccess()).isTrue(); @@ -547,9 +640,14 @@ void testRegisterWithHostAndPort() TransparentProxy.register("gateway", 8080, "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("test-destination")); assertThat(result.isSuccess()).isTrue(); @@ -565,9 +663,14 @@ void testRegisterWithHostAndPortWithScheme() TransparentProxy.register("https://gateway", 443, "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("test-destination")); assertThat(result.isSuccess()).isTrue(); @@ -634,13 +737,17 @@ void testDestinationNotFoundWhenStatus502WithErrorCode404() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = - new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); response.setHeader("x-error-internal-code", "404"); response.setHeader("x-error-message", expectedErrorMessage); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("non-existent-destination")); @@ -656,13 +763,17 @@ void testDestinationNotFoundWhenStatus502WithErrorCode404WithOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = - new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); response.setHeader("x-error-internal-code", "404"); response.setHeader("x-error-message", expectedErrorMessage); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant( @@ -680,10 +791,15 @@ void testDestinationFoundWhenStatus200() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("existing-destination")); @@ -698,11 +814,15 @@ void testDestinationFoundWhenStatus502WithoutErrorCode() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = - new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("destination-with-502")); @@ -717,12 +837,16 @@ void testDestinationFoundWhenStatus502WithDifferentErrorCode() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = - new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_BAD_GATEWAY, "Bad Gateway"); response.setHeader("x-error-internal-code", "500"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); Try result = executeWithTenant(() -> loader.tryGetDestination("destination-with-different-error")); @@ -758,9 +882,14 @@ void testRegisterWithValidHostsWithoutPathsSucceeds() // Set up mock after registration final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); // Verify the registration worked Try result = executeWithTenant(() -> loader.tryGetDestination("test-destination")); @@ -775,9 +904,14 @@ void testTryGetDestinationWithLevelOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -809,9 +943,14 @@ void testTryGetDestinationWithAllSupportedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -854,9 +993,14 @@ void testFragmentNameWithAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -881,9 +1025,14 @@ void testCrossLevelConsumptionSubaccount() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -915,9 +1064,14 @@ void testCrossLevelConsumptionProviderSubaccount() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -950,9 +1104,14 @@ void testCrossLevelConsumptionInstance() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -984,9 +1143,14 @@ void testCrossLevelConsumptionProviderInstance() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1018,9 +1182,14 @@ void testCustomHeadersWithAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1051,9 +1220,14 @@ void testCombinedFragmentAndCrossLevelOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1090,9 +1264,14 @@ void testFragmentWithCustomHeadersAndCrossLevel() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1138,9 +1317,14 @@ void testRetrievalStrategyWithAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1167,9 +1351,14 @@ void testTokenExchangeStrategyWithAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1197,9 +1386,14 @@ void testRefreshTokenWithAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions @@ -1223,9 +1417,14 @@ void testEmptyAugmentedOptions() TransparentProxy.register("gateway", "tenant-id"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); DestinationOptions options = DestinationOptions.builder().augmentBuilder(DestinationServiceOptionsAugmenter.augmenter()).build(); @@ -1243,9 +1442,14 @@ void testTenantAccessExceptionWhenTenantMissingInContextAndProviderTenantId() TransparentProxy.register("gateway"); final HttpClient mockHttpClient = mock(HttpClient.class); - final BasicHttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); - when(mockHttpClient.execute(org.mockito.ArgumentMatchers.any(HttpHead.class))).thenReturn(response); - HttpClientAccessor.setHttpClientFactory(dest -> mockHttpClient); + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK"); + when( + mockHttpClient + .execute( + any(ClassicHttpRequest.class), + ArgumentMatchers.> any())) + .thenReturn(response); + ApacheHttpClient5Accessor.setHttpClientFactory(dest -> mockHttpClient); com.sap.cloud.sdk.cloudplatform.tenant.exception.TenantAccessException exception = assertThrows( diff --git a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/XsuaaSecurityTest.java b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/XsuaaSecurityTest.java index ce4d2738b..b939a66dc 100644 --- a/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/XsuaaSecurityTest.java +++ b/cloudplatform/connectivity-destination-service/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/XsuaaSecurityTest.java @@ -5,17 +5,15 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Collections; import javax.annotation.Nonnull; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.HttpClients; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpHeaders; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -98,10 +96,9 @@ void requestWithValidTokenRequest() final HttpGet request = new HttpGet(RULE.getApplicationServerUri() + "/app"); request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token.getTokenValue()); - try( CloseableHttpResponse response = HttpClients.createDefault().execute(request) ) { - final String responseBody = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - assertThat(responseBody).isEmpty(); - assertThat(response.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK); + try( CloseableHttpClient client = HttpClients.createDefault() ) { + String response = client.execute(request, new BasicHttpClientResponseHandler()); + assertThat(response).isEmpty(); } }