use WireMock for offline tests
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Stefan Kalscheuer 2021-02-28 14:56:50 +01:00
parent 60d94fc5bb
commit 36102326db
2 changed files with 67 additions and 82 deletions

12
pom.xml
View File

@ -129,18 +129,18 @@
<version>3.8.0</version> <version>3.8.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>com.github.stefanbirkner</groupId> <groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId> <artifactId>system-lambda</artifactId>
<version>1.2.0</version> <version>1.2.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.27.2</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>

View File

@ -16,37 +16,32 @@
package de.stklcode.jvault.connector; package de.stklcode.jvault.connector;
import de.stklcode.jvault.connector.exception.InvalidRequestException; import com.github.tomakehurst.wiremock.WireMockServer;
import de.stklcode.jvault.connector.exception.InvalidResponseException; import com.github.tomakehurst.wiremock.client.WireMock;
import de.stklcode.jvault.connector.exception.PermissionDeniedException; import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import de.stklcode.jvault.connector.exception.VaultConnectorException; import de.stklcode.jvault.connector.exception.*;
import org.apache.http.ProtocolVersion; import org.junit.jupiter.api.AfterAll;
import org.apache.http.client.methods.CloseableHttpResponse; import org.junit.jupiter.api.BeforeAll;
import org.apache.http.entity.ContentType; import org.junit.jupiter.api.Test;
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 org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.Executable;
import org.mockito.MockedStatic;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collections; 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.instanceOf;
import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows; 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. * JUnit test for HTTP Vault connector.
@ -58,40 +53,33 @@ import static org.mockito.Mockito.*;
class HTTPVaultConnectorOfflineTest { class HTTPVaultConnectorOfflineTest {
private static final String INVALID_URL = "foo:/\\1nv4l1d_UrL"; private static final String INVALID_URL = "foo:/\\1nv4l1d_UrL";
private static MockedStatic<HttpClientBuilder> hcbMock; private static WireMockServer wireMock;
private static CloseableHttpClient httpMock;
private final CloseableHttpResponse responseMock = mock(CloseableHttpResponse.class);
@BeforeAll @BeforeAll
static void prepare() { static void prepare() {
// Mock the static HTTPClient creation. // Initialize HTTP mock.
hcbMock = mockStatic(HttpClientBuilder.class); wireMock = new WireMockServer(WireMockConfiguration.options().dynamicPort());
hcbMock.when(HttpClientBuilder::create).thenReturn(new MockedHttpClientBuilder()); wireMock.start();
WireMock.configureFor("localhost", wireMock.port());
} }
@AfterAll @AfterAll
static void tearDown() { static void tearDown() {
hcbMock.close(); wireMock.stop();
} wireMock = null;
@BeforeEach
void init() {
// Re-initialize HTTP mock to ensure fresh (empty) results.
httpMock = mock(CloseableHttpClient.class);
} }
/** /**
* Test exceptions thrown during request. * Test exceptions thrown during request.
*/ */
@Test @Test
void requestExceptionTest() throws IOException { 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. // Test invalid response code.
final int responseCode = 400; final int responseCode = 400;
mockResponse(responseCode, "", ContentType.APPLICATION_JSON); mockHttpResponse(responseCode, "", "application/json");
InvalidResponseException e = assertThrows( VaultConnectorException e = assertThrows(
InvalidResponseException.class, InvalidResponseException.class,
connector::getHealth, connector::getHealth,
"Querying health status succeeded on invalid instance" "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())); assertThat("Response message where none was expected", ((InvalidResponseException) e).getResponse(), is(nullValue()));
// Simulate permission denied response. // Simulate permission denied response.
mockResponse(responseCode, "{\"errors\":[\"permission denied\"]}", ContentType.APPLICATION_JSON); mockHttpResponse(responseCode, "{\"errors\":[\"permission denied\"]}", "application/json");
assertThrows( assertThrows(
PermissionDeniedException.class, PermissionDeniedException.class,
connector::getHealth, connector::getHealth,
@ -109,25 +97,27 @@ class HTTPVaultConnectorOfflineTest {
); );
// Test exception thrown during request. // 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( e = assertThrows(
InvalidResponseException.class, ConnectionException.class,
connector::getHealth, connector::getHealth,
"Querying health status succeeded on invalid instance" "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)); assertThat("Unexpected cause", e.getCause(), instanceOf(IOException.class));
// Now simulate a failing request that succeeds on second try. // Now simulate a failing request that succeeds on second try.
connector = new HTTPVaultConnector("https://127.0.0.1", null, 1, 250); connector = new HTTPVaultConnector(wireMock.url("/"), 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));
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"); 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. * This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable.
*/ */
@Test @Test
void sealExceptionTest() { void sealExceptionTest() throws IOException {
HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL);
VaultConnectorException e = assertThrows( VaultConnectorException e = assertThrows(
InvalidRequestException.class, InvalidRequestException.class,
@ -196,21 +186,23 @@ class HTTPVaultConnectorOfflineTest {
); );
assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format"));
// Simulate NULL response (mock not supplied with data). // Simulate no connection.
connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); try (ServerSocket s = new ServerSocket(0)) {
connector = new HTTPVaultConnector("http://localst:" + s.getLocalPort() + "/", null, 0, 250);
}
e = assertThrows( e = assertThrows(
InvalidResponseException.class, ConnectionException.class,
connector::sealStatus, connector::sealStatus,
"Querying seal status succeeded on invalid instance" "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. * This test is designed to test exceptions caught and thrown by seal-methods if Vault is not reachable.
*/ */
@Test @Test
void healthExceptionTest() { void healthExceptionTest() throws IOException {
HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL); HTTPVaultConnector connector = new HTTPVaultConnector(INVALID_URL);
VaultConnectorException e = assertThrows( VaultConnectorException e = assertThrows(
InvalidRequestException.class, InvalidRequestException.class,
@ -219,14 +211,16 @@ class HTTPVaultConnectorOfflineTest {
); );
assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format")); assertThat("Unexpected exception message", e.getMessage(), is("Invalid URI format"));
// Simulate NULL response (mock not supplied with data). // Simulate no connection.
connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); try (ServerSocket s = new ServerSocket(0)) {
connector = new HTTPVaultConnector("http://localhost:" + s.getLocalPort() + "/", null, 0, 250);
}
e = assertThrows( e = assertThrows(
InvalidResponseException.class, ConnectionException.class,
connector::getHealth, connector::getHealth,
"Querying health status succeeded on invalid instance" "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 @Test
void parseExceptionTest() throws IOException { 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. // Mock authorization.
setPrivate(connector, "authorized", true); setPrivate(connector, "authorized", true);
// Mock response. // Mock response.
mockResponse(200, "invalid", ContentType.APPLICATION_JSON); mockHttpResponse(200, "invalid", "application/json");
// Now test the methods. // Now test the methods.
assertParseError(connector::sealStatus, "sealStatus() succeeded on invalid instance"); 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 requests that expect an empty response with code 204, but receive a 200 body.
*/ */
@Test @Test
void nonEmpty204ResponseTest() throws IOException { void nonEmpty204ResponseTest() {
HTTPVaultConnector connector = new HTTPVaultConnector("https://127.0.0.1", null, 0, 250); HTTPVaultConnector connector = new HTTPVaultConnector(wireMock.url("/"), null, 0, 250);
// Mock authorization. // Mock authorization.
setPrivate(connector, "authorized", true); setPrivate(connector, "authorized", true);
// Mock response. // Mock response.
mockResponse(200, "{}", ContentType.APPLICATION_JSON); mockHttpResponse(200, "{}", "application/json");
// Now test the methods expecting a 204. // Now test the methods expecting a 204.
assertThrows( assertThrows(
@ -361,20 +355,11 @@ class HTTPVaultConnectorOfflineTest {
} }
} }
private void mockResponse(int status, String body, ContentType type) throws IOException { private void mockHttpResponse(int status, String body, String contentType) {
when(httpMock.execute(any())).thenReturn(responseMock); WireMock.stubFor(
when(responseMock.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), status, "")); WireMock.any(anyUrl()).willReturn(
when(responseMock.getEntity()).thenReturn(new StringEntity(body, type)); 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;
}
}
} }