diff --git a/pom.xml b/pom.xml index 97d7dda..9f6a370 100644 --- a/pom.xml +++ b/pom.xml @@ -107,5 +107,17 @@ 1.16.1 test + + org.powermock + powermock-module-junit4 + 1.7.3 + test + + + org.powermock + powermock-api-mockito + 1.7.3 + test + diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java index 3babf79..4d4d944 100644 --- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java +++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java @@ -18,18 +18,33 @@ 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 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.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; import javax.net.ssl.SSLContext; import java.io.IOException; import java.lang.reflect.Field; -import java.net.ServerSocket; import java.security.NoSuchAlgorithmException; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.powermock.api.mockito.PowerMockito.*; /** * JUnit test for HTTP Vault connector. @@ -38,7 +53,78 @@ import static org.junit.Assert.assertThat; * @author Stefan Kalscheuer * @since 0.7.0 */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({HttpClientBuilder.class}) +@PowerMockIgnore({"javax.net.ssl.*"}) public class HTTPVaultConnectorOfflineTest { + private static final String INVALID_URL = "foo:/\\1nv4l1d_UrL"; + + @Mock + private HttpClientBuilder httpMockBuilder; + + @Mock + private CloseableHttpClient httpMock; + + @Mock + private CloseableHttpResponse responseMock; + + /** + * Test exceptions thrown during request. + */ + @Test + public void requestExceptionTest() throws IOException { + HTTPVaultConnector connector = new HTTPVaultConnector("http://127.0.0.1", null, 0, 250); + + // Test invalid response code. + initHttpMock(); + final int responseCode = 400; + mockResponse(responseCode, "", ContentType.APPLICATION_JSON); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Invalid response code")); + assertThat("Unexpected status code in exception", ((InvalidResponseException) e).getStatusCode(), is(responseCode)); + assertThat("Response message where none was expected", ((InvalidResponseException) e).getResponse(), is(nullValue())); + } + + // Simulate permission denied response. + mockResponse(responseCode, "{\"errors\":[\"permission denied\"]}", ContentType.APPLICATION_JSON); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(PermissionDeniedException.class)); + } + + // Test exception thrown during request. + when(httpMock.execute(any())).thenThrow(new IOException("Test Exception")); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to read response")); + 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)); + + try { + connector.getHealth(); + } catch (Exception e) { + fail("Request failed unexpectedly: " + e.getMessage()); + } + } /** * Test constductors of the {@link HTTPVaultConnector} class. @@ -87,7 +173,7 @@ public class HTTPVaultConnectorOfflineTest { */ @Test public void sealExceptionTest() throws IOException { - HTTPVaultConnector connector = new HTTPVaultConnector("foo:/\\1nv4l1d_UrL"); + HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); try { connector.sealStatus(); fail("Querying seal status succeeded on invalid URL"); @@ -96,20 +182,64 @@ public class HTTPVaultConnectorOfflineTest { assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); } - // Create socket on free port to ensure no other process is listening on the specified URL. - try (ServerSocket s = new ServerSocket(0)) { - connector = new HTTPVaultConnector("https://127.0.0.1:" + s.getLocalPort(), null, 0, 250); - try { - connector.sealStatus(); - fail("Querying seal status succeeded on invalid instance"); - } catch (Exception e) { - assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); - assertThat("Unexpected exception message", e.getMessage(), is("Unable to read response")); - } + connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + + // Simulate NULL response (mock not supplied with data). + initHttpMock(); + try { + connector.sealStatus(); + fail("Querying seal status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable")); + } + + // Simulate un-mappable response. + mockResponse(200, "", ContentType.APPLICATION_JSON); + try { + connector.sealStatus(); + fail("Querying seal status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to parse response")); } } - private void fail(String s) { + /** + * This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable. + */ + @Test + public void healthExceptionTest() throws IOException { + HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid URL"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidRequestException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); + } + + connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); + + // Simulate NULL response (mock not supplied with data). + initHttpMock(); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Response unavailable")); + } + + // Simulate un-mappable response. + mockResponse(200, "", ContentType.APPLICATION_JSON); + try { + connector.getHealth(); + fail("Querying health status succeeded on invalid instance"); + } catch (Exception e) { + assertThat("Unexpected type of exception", e, instanceOf(InvalidResponseException.class)); + assertThat("Unexpected exception message", e.getMessage(), is("Unable to parse response")); + } } private Object getPrivate(Object target, String fieldName) { @@ -125,4 +255,17 @@ public class HTTPVaultConnectorOfflineTest { return null; } } + + private void initHttpMock() { + mockStatic(HttpClientBuilder.class); + when(HttpClientBuilder.create()).thenReturn(httpMockBuilder); + when(httpMockBuilder.setSSLContext(null)).thenReturn(httpMockBuilder); + when(httpMockBuilder.build()).thenReturn(httpMock); + } + + 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)); + } }