package org.testfun.jee;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* A JUnit rule that is used for declaring on expected REST/HTTP-client response returned from the server - should be
* used in conjunction with the {@link JaxRsServer} rule.
* <p>
* To use this rule, add the following member variable to your test class:
* <pre>{@code @Rule public ExpectedClientResponseFailure thrownClientResponseFailure = ExpectedClientResponseFailure.none();}</pre>
* <p>
* Setting failure expectation can be done as follows:
* <pre>{@code thrownClientResponseFailure.expectFailureResponse(Response.Status.BAD_REQUEST, "Password was not provided");}</pre>
* Note that it is recommended to use {@link JaxRsServer#expectFailureResponse(javax.ws.rs.core.Response.Status, java.lang.String)} instead:
*/
public class ExpectedClientResponseFailure implements MethodRule {
private String expectedMessageSubstring;
private Response.Status expectedResponseStatus;
public static ExpectedClientResponseFailure none() {
return new ExpectedClientResponseFailure();
}
private ExpectedClientResponseFailure() {
}
@Override
public Statement apply(Statement base, FrameworkMethod method, Object target) {
return new ExpectedClientResponseFailureStatement(base);
}
/**
* Set expectation for REST failure with a particular status code and a substring that should appear in the failure message.
*/
public void expectFailureResponse(Response.Status expectedResponseStatus, String expectedMessageSubstring) {
assertNotNull(expectedResponseStatus);
this.expectedResponseStatus = expectedResponseStatus;
assertNotNull(expectedMessageSubstring);
this.expectedMessageSubstring = expectedMessageSubstring;
}
@SuppressWarnings("unchecked")
@SafeVarargs
public static <T extends Throwable> T getEncapsulatedException(Throwable currentException,
Class<? extends Throwable>... exceptionClasses) {
List<Throwable> visitedExceptions = new ArrayList<>();
while (currentException != null) {
if (visitedExceptions.contains(currentException)) {
// to avoid infinite loops
break;
} else {
visitedExceptions.add(currentException);
}
for (Class<? extends Throwable> exceptionClass : exceptionClasses) {
if (exceptionClass.isInstance(currentException))
return (T) currentException;
}
currentException = currentException.getCause();
}
return null;
}
private class ExpectedClientResponseFailureStatement extends Statement {
private final Statement next;
private ExpectedClientResponseFailureStatement(Statement next) {
this.next = next;
}
@Override
public void evaluate() throws Throwable {
try {
next.evaluate();
} catch (Throwable e) {
if (expectedMessageSubstring == null) {
throw e; // unexpected exception
}
Throwable causedByClientResponseFailure = getEncapsulatedException(e, ClientErrorException.class);
if (causedByClientResponseFailure == null) {
throw e; // the caught exception isn't caused by the expected one
}
// if there's a failure was expected and the caught, make sure the expected message matches the caught one
ClientErrorException failure = (ClientErrorException) causedByClientResponseFailure;
Response response = failure.getResponse();
String actualResponseMessage = response.readEntity(String.class);
Response.Status responseStatus = Response.Status.fromStatusCode(response.getStatus());
boolean actualFailureMatchesExpectedOne = actualResponseMessage.contains(expectedMessageSubstring) && responseStatus == expectedResponseStatus;
if (!actualFailureMatchesExpectedOne) {
StringBuilder sb = getExpectedFailureMessage("Unexpected failure:").
append("\n\tFound response with status ").append(response.getStatus()).
append(" (").append(responseStatus).append(") and body: ").append(actualResponseMessage);
fail(sb.toString());
}
// must return now so to skip the code outside of the try-catch block which is responsible for making sure that
// the test will fail if an exception was expected but wasn't thrown
return;
}
// Fail if an exception was expected but wasn't thrown
if (expectedMessageSubstring != null) {
fail(getExpectedFailureMessage("Expected test to fail:").toString());
}
}
private StringBuilder getExpectedFailureMessage(String prefix) {
return new StringBuilder(prefix).
append("\n\tExpected response with status ").append(expectedResponseStatus.getStatusCode()).
append(" (").append(expectedResponseStatus).append(") ").
append("and body containing: ").append(expectedMessageSubstring);
}
}
}