diff --git a/pom.xml b/pom.xml index 631d512..5c7927e 100644 --- a/pom.xml +++ b/pom.xml @@ -129,18 +129,18 @@ 3.8.0 test - - org.mockito - mockito-inline - 3.8.0 - test - com.github.stefanbirkner system-lambda 1.2.0 test + + com.github.tomakehurst + wiremock + 2.27.2 + test + commons-io commons-io diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java index f599f72..ee949be 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java @@ -16,37 +16,32 @@ package de.stklcode.jvault.connector; -import de.stklcode.jvault.connector.exception.InvalidRequestException; -import de.stklcode.jvault.connector.exception.InvalidResponseException; -import de.stklcode.jvault.connector.exception.PermissionDeniedException; -import de.stklcode.jvault.connector.exception.VaultConnectorException; -import org.apache.http.ProtocolVersion; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicStatusLine; -import org.junit.jupiter.api.*; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import de.stklcode.jvault.connector.exception.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.mockito.MockedStatic; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.net.ServerSocket; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Collections; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; /** * JUnit test for HTTP Vault connector. @@ -58,40 +53,33 @@ import static org.mockito.Mockito.*; class HTTPVaultConnectorOfflineTest { private static final String INVALID_URL = "foo:/\\1nv4l1d_UrL"; - private static MockedStatic hcbMock; - private static CloseableHttpClient httpMock; - private final CloseableHttpResponse responseMock = mock(CloseableHttpResponse.class); + private static WireMockServer wireMock; @BeforeAll static void prepare() { - // Mock the static HTTPClient creation. - hcbMock = mockStatic(HttpClientBuilder.class); - hcbMock.when(HttpClientBuilder::create).thenReturn(new MockedHttpClientBuilder()); + // Initialize HTTP mock. + wireMock = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + wireMock.start(); + WireMock.configureFor("localhost", wireMock.port()); } - @AfterAll - static void tearDown() { - hcbMock.close(); - } - - @BeforeEach - void init() { - // Re-initialize HTTP mock to ensure fresh (empty) results. - httpMock = mock(CloseableHttpClient.class); + @AfterAll + static void tearDown() { + wireMock.stop(); + wireMock = null; } - /** * Test exceptions thrown during request. */ @Test void requestExceptionTest() throws IOException { - HTTPVaultConnector connector = new HTTPVaultConnector("http://127.0.0.1", null, 0, 250); + HTTPVaultConnector connector = new HTTPVaultConnector(wireMock.url("/"), null, 0, 250); // Test invalid response code. final int responseCode = 400; - mockResponse(responseCode, "", ContentType.APPLICATION_JSON); - InvalidResponseException e = assertThrows( + mockHttpResponse(responseCode, "", "application/json"); + VaultConnectorException e = assertThrows( InvalidResponseException.class, connector::getHealth, "Querying health status succeeded on invalid instance" @@ -101,7 +89,7 @@ class HTTPVaultConnectorOfflineTest { assertThat("Response message where none was expected", ((InvalidResponseException) e).getResponse(), is(nullValue())); // Simulate permission denied response. - mockResponse(responseCode, "{\"errors\":[\"permission denied\"]}", ContentType.APPLICATION_JSON); + mockHttpResponse(responseCode, "{\"errors\":[\"permission denied\"]}", "application/json"); assertThrows( PermissionDeniedException.class, connector::getHealth, @@ -109,25 +97,27 @@ class HTTPVaultConnectorOfflineTest { ); // Test exception thrown during request. - when(httpMock.execute(any())).thenThrow(new IOException("Test Exception")); + try (ServerSocket s = new ServerSocket(0)) { + connector = new HTTPVaultConnector("http://localst:" + s.getLocalPort() + "/", null, 0, 250); + } e = assertThrows( - InvalidResponseException.class, + ConnectionException.class, connector::getHealth, "Querying health status succeeded on invalid instance" ); - assertThat("Unexpected exception message", e.getMessage(), is("Unable to read response")); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to connect to Vault server")); assertThat("Unexpected cause", e.getCause(), instanceOf(IOException.class)); // Now simulate a failing request that succeeds on second try. - connector = new HTTPVaultConnector("https://127.0.0.1", null, 1, 250); - doReturn(responseMock).doReturn(responseMock).when(httpMock).execute(any()); - doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "")) - .doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "")) - .doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "")) - .doReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "")) - .when(responseMock).getStatusLine(); - when(responseMock.getEntity()).thenReturn(new StringEntity("{}", ContentType.APPLICATION_JSON)); + connector = new HTTPVaultConnector(wireMock.url("/"), null, 1, 250); + WireMock.stubFor( + WireMock.any(anyUrl()) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(500)) + .willReturn(aResponse().withStatus(200).withBody("{}").withHeader("Content-Type", "application/json")) + ); assertDoesNotThrow(connector::getHealth, "Request failed unexpectedly"); } @@ -187,7 +177,7 @@ class HTTPVaultConnectorOfflineTest { * This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable. */ @Test - void sealExceptionTest() { + void sealExceptionTest() throws IOException { HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); VaultConnectorException e = assertThrows( InvalidRequestException.class, @@ -196,21 +186,23 @@ class HTTPVaultConnectorOfflineTest { ); assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); - // Simulate NULL response (mock not supplied with data). - connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + // Simulate no connection. + try (ServerSocket s = new ServerSocket(0)) { + connector = new HTTPVaultConnector("http://localst:" + s.getLocalPort() + "/", null, 0, 250); + } e = assertThrows( - InvalidResponseException.class, + ConnectionException.class, connector::sealStatus, "Querying seal status succeeded on invalid instance" ); - assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable")); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to connect to Vault server")); } /** * This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable. */ @Test - void healthExceptionTest() { + void healthExceptionTest() throws IOException { HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); VaultConnectorException e = assertThrows( InvalidRequestException.class, @@ -219,14 +211,16 @@ class HTTPVaultConnectorOfflineTest { ); assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); - // Simulate NULL response (mock not supplied with data). - connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + // Simulate no connection. + try (ServerSocket s = new ServerSocket(0)) { + connector = new HTTPVaultConnector("http://localhost:" + s.getLocalPort() + "/", null, 0, 250); + } e = assertThrows( - InvalidResponseException.class, + ConnectionException.class, connector::getHealth, "Querying health status succeeded on invalid instance" ); - assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable")); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to connect to Vault server")); } /** @@ -234,11 +228,11 @@ class HTTPVaultConnectorOfflineTest { */ @Test void parseExceptionTest() throws IOException { - HTTPVaultConnector connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + HTTPVaultConnector connector = new HTTPVaultConnector(wireMock.url("/"), null, 0, 250); // Mock authorization. setPrivate(connector, "authorized", true); // Mock response. - mockResponse(200, "invalid", ContentType.APPLICATION_JSON); + mockHttpResponse(200, "invalid", "application/json"); // Now test the methods. assertParseError(connector::sealStatus, "sealStatus() succeeded on invalid instance"); @@ -267,12 +261,12 @@ class HTTPVaultConnectorOfflineTest { * Test requests that expect an empty response with code 204, but receive a 200 body. */ @Test - void nonEmpty204ResponseTest() throws IOException { - HTTPVaultConnector connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + void nonEmpty204ResponseTest() { + HTTPVaultConnector connector = new HTTPVaultConnector(wireMock.url("/"), null, 0, 250); // Mock authorization. setPrivate(connector, "authorized", true); // Mock response. - mockResponse(200, "{}", ContentType.APPLICATION_JSON); + mockHttpResponse(200, "{}", "application/json"); // Now test the methods expecting a 204. assertThrows( @@ -361,20 +355,11 @@ class HTTPVaultConnectorOfflineTest { } } - private void mockResponse(int status, String body, ContentType type) throws IOException { - when(httpMock.execute(any())).thenReturn(responseMock); - when(responseMock.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), status, "")); - when(responseMock.getEntity()).thenReturn(new StringEntity(body, type)); + private void mockHttpResponse(int status, String body, String contentType) { + WireMock.stubFor( + WireMock.any(anyUrl()).willReturn( + aResponse().withStatus(status).withBody(body).withHeader("Content-Type", contentType) + ) + ); } - - /** - * Mocked {@link HttpClientBuilder} that always returns the mocked client. - */ - private static class MockedHttpClientBuilder extends HttpClientBuilder { - @Override - public CloseableHttpClient build() { - return httpMock; - } - } - }