Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpWaitStrategy response body checking #441

Merged
merged 7 commits into from
Sep 19, 2017
Merged
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ All notable changes to this project will be documented in this file.
- Stop creation of temporary directory prior to creating temporary file (#443)

### Changed
- Added `forResponsePredicate` method to HttpWaitStrategy to test response body
- Load `DockerClientProviderStrategy` via Service Loader (#434, #435)
- Make it possible to specify docker compose container in configuration (#422, #425)


## [1.4.2] - 2017-07-25
### Fixed
- Worked around incompatibility between Netty's Unix socket support and OS X 10.11. Reinstated use of TCP-Unix Socket proxy when running on OS X prior to v10.12. (Fixes #402)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
import com.google.common.base.Strings;
import com.google.common.io.BaseEncoding;
import org.rnorth.ducttape.TimeoutException;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.containers.GenericContainer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;

Expand All @@ -36,6 +38,7 @@ public class HttpWaitStrategy extends GenericContainer.AbstractWaitStrategy {
private boolean tlsEnabled;
private String username;
private String password;
private Predicate<String> responsePredicate;

/**
* Waits for the given status code.
Expand Down Expand Up @@ -82,6 +85,16 @@ public HttpWaitStrategy withBasicCredentials(String username, String password) {
return this;
}

/**
* Waits for the response to pass the given predicate
* @param responsePredicate The predicate to test the response against
* @return this
*/
public HttpWaitStrategy forResponsePredicate(Predicate<String> responsePredicate) {
this.responsePredicate = responsePredicate;
return this;
}

@Override
protected void waitUntilReady() {
final Integer livenessCheckPort = getLivenessCheckPort();
Expand Down Expand Up @@ -114,6 +127,12 @@ protected void waitUntilReady() {
connection.getResponseCode()));
}

String responseBody = getResponseBody(connection);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move the parsing of the response body into the if clause. I would prefer this, since it's only needed if a responsePredicate is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that makes sense. I've made the change now.

if(responsePredicate != null && !responsePredicate.test(responseBody)) {
throw new RuntimeException(String.format("Response: %s did not match predicate",
responseBody));
}

} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -155,4 +174,20 @@ private URI buildLivenessUri(int livenessCheckPort) {
private String buildAuthString(String username, String password) {
return AUTH_BASIC + BaseEncoding.base64().encode((username + ":" + password).getBytes());
}

private String getResponseBody(HttpURLConnection connection) throws IOException {
BufferedReader reader;
if (200 <= connection.getResponseCode() && connection.getResponseCode() <= 299) {
reader = new BufferedReader(new InputStreamReader((connection.getInputStream())));
} else {
reader = new BufferedReader(new InputStreamReader((connection.getErrorStream())));
}

StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
*/
public class HttpWaitStrategyTest extends AbstractWaitStrategyTest<HttpWaitStrategy> {
/**
* Doubly-escaped newline sequence indicating end of the HTTP header.
* newline sequence indicating end of the HTTP header.
*/
private static final String DOUBLE_NEWLINE = "\\\r\\\n\\\r\\\n";
private static final String NEWLINE = "\r\n";

private static final String GOOD_RESPONSE_BODY = "Good Response Body";

/**
* Expects that the WaitStrategy returns successfully after receiving an HTTP 200 response from the container.
*/
@Test
public void testWaitUntilReady_Success() {
waitUntilReadyAndSucceed("while true; do echo -e \"HTTP/1.1 200 OK" + DOUBLE_NEWLINE + "\" | nc -lp 8080; done");
waitUntilReadyAndSucceed(createShellCommand("200 OK", GOOD_RESPONSE_BODY));
}

/**
Expand All @@ -32,7 +34,16 @@ public void testWaitUntilReady_Success() {
*/
@Test
public void testWaitUntilReady_Timeout() {
waitUntilReadyAndTimeout("while true; do echo -e \"HTTP/1.1 400 Bad Request" + DOUBLE_NEWLINE + "\" | nc -lp 8080; done");
waitUntilReadyAndTimeout(createShellCommand("400 Bad Request", GOOD_RESPONSE_BODY));
}

/**
* Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not the expected response body
* from the container within the timeout period.
*/
@Test
public void testWaitUntilReady_Timeout_BadResponseBody() {
waitUntilReadyAndTimeout(createShellCommand("200 OK", "Bad Response"));
}

/**
Expand All @@ -48,6 +59,14 @@ protected void waitUntilReady() {
super.waitUntilReady();
ready.set(true);
}
};
}.forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY));
}

private String createShellCommand(String header, String responseBody) {
int length = responseBody.getBytes().length;
return "while true; do { echo -e \"HTTP/1.1 "+header+NEWLINE+
"Content-Type: text/html"+NEWLINE+
"Content-Length: "+length +NEWLINE+ "\";"
+" echo \""+responseBody+"\";} | nc -lp 8080; done";
}
}