/*
* Copyright 2015 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.apiman.manager.test.junit;
import io.apiman.manager.api.core.util.PolicyTemplateUtil;
import io.apiman.manager.test.junit.ManagerRestTester.TestInfo;
import io.apiman.manager.test.server.ManagerApiTestServer;
import io.apiman.manager.test.server.MockGatewayServlet;
import io.apiman.test.common.json.JsonCompare;
import io.apiman.test.common.plan.TestGroupType;
import io.apiman.test.common.plan.TestPlan;
import io.apiman.test.common.plan.TestType;
import io.apiman.test.common.resttest.RestTest;
import io.apiman.test.common.util.TestPlanRunner;
import io.apiman.test.common.util.TestUtil;
import java.io.File;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.ParentRunner;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A junit test runner that fires up apiman and makes it ready for
* use in the tests. This runner also loads up the test plan from
* the required {@link ManagerRestTestPlan} annotation.
*
* @author eric.wittmann@redhat.com
*/
@SuppressWarnings("nls")
public class ManagerRestTester extends ParentRunner<TestInfo> {
private static Logger logger = LoggerFactory.getLogger(TestPlanRunner.class);
private static ManagerApiTestServer testServer = new ManagerApiTestServer();
private static final boolean USE_PROXY = false;
private static final int PROXY_PORT = 7071;
private List<TestPlanInfo> testPlans = new ArrayList<>();
private Set<String> resetSysProps = new HashSet<>();
/**
* Constructor.
*/
public ManagerRestTester(Class<?> testClass) throws InitializationError {
super(testClass);
configureSystemProperties();
loadTestPlans(testClass);
}
/**
* Loads the test plans.
* @param testClass
* @throws InitializationError
*/
private void loadTestPlans(Class<?> testClass) throws InitializationError {
try {
ManagerRestTestPlan annotation = testClass.getAnnotation(ManagerRestTestPlan.class);
if (annotation == null) {
Method[] methods = testClass.getMethods();
TreeSet<ManagerRestTestPlan> annotations = new TreeSet<>(new Comparator<ManagerRestTestPlan>() {
@Override
public int compare(ManagerRestTestPlan o1, ManagerRestTestPlan o2) {
Integer i1 = o1.order();
Integer i2 = o2.order();
return i1.compareTo(i2);
}
});
for (Method method : methods) {
annotation = method.getAnnotation(ManagerRestTestPlan.class);
if (annotation != null) {
annotations.add(annotation);
}
}
for (ManagerRestTestPlan anno : annotations) {
TestPlanInfo planInfo = new TestPlanInfo();
planInfo.planPath = anno.value();
planInfo.name = new File(planInfo.planPath).getName();
planInfo.endpoint = TestUtil.doPropertyReplacement(anno.endpoint());
planInfo.plan = TestUtil.loadTestPlan(planInfo.planPath, testClass.getClassLoader());
testPlans.add(planInfo);
}
} else {
TestPlanInfo planInfo = new TestPlanInfo();
planInfo.planPath = annotation.value();
planInfo.name = new File(planInfo.planPath).getName();
planInfo.plan = TestUtil.loadTestPlan(planInfo.planPath, testClass.getClassLoader());
planInfo.endpoint = TestUtil.doPropertyReplacement(annotation.endpoint());
testPlans.add(planInfo);
}
} catch (Throwable e) {
throw new InitializationError(e);
}
if (testPlans.isEmpty()) {
throw new InitializationError("No @ManagerRestTestPlan annotations found on test class: " + testClass);
}
}
/**
* Called to setup the test.
* @throws InitializationError
*/
public static void setup() {
if (!"true".equals(System.getProperty("apiman.junit.no-server", "false"))) {
startServer();
} else {
System.out.println("**** APIMan Server suppressed - assuming running tests against a live server. ****");
}
}
/**
* Called at the end of the test.
*/
public static void shutdown() {
if (!"true".equals(System.getProperty("apiman.junit.no-server", "false"))) {
stopServer();
}
}
/**
* @throws Exception
*/
protected static void startServer() {
try {
testServer.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @throws Exception
*/
protected static void stopServer() {
try {
testServer.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @see org.junit.runners.ParentRunner#getChildren()
*/
@Override
protected List<TestInfo> getChildren() {
List<TestInfo> children = new ArrayList<>();
TestPlanInfo lastPlan = null;
for (TestPlanInfo planInfo : testPlans) {
lastPlan = planInfo;
planInfo.runner = new TestPlanRunner();
List<TestGroupType> groups = planInfo.plan.getTestGroup();
for (TestGroupType group : groups) {
for (TestType test : group.getTest()) {
TestInfo testInfo = new TestInfo();
if (testPlans.size() > 1) {
testInfo.name = planInfo.name + " / " + test.getName();
} else {
testInfo.name = test.getName();
}
testInfo.plan = planInfo;
testInfo.group = group;
testInfo.test = test;
children.add(testInfo);
}
}
}
ManagerRestTestGatewayLog annotation = getTestClass().getJavaClass().getAnnotation(ManagerRestTestGatewayLog.class);
if (annotation != null) {
GatewayAssertionTestInfo gatewayTest = new GatewayAssertionTestInfo();
gatewayTest.name = "Assert Gateway Log";
gatewayTest.plan = lastPlan;
gatewayTest.expectedLog = annotation.value();
children.add(gatewayTest);
}
ManagerRestTestPublishPayload annotation2 = getTestClass().getJavaClass().getAnnotation(ManagerRestTestPublishPayload.class);
if (annotation2 != null) {
PublishPayloadTestInfo pubTest = new PublishPayloadTestInfo();
pubTest.name = "Assert Publishing Payloads";
pubTest.plan = lastPlan;
pubTest.expectedPayloads = annotation2.value();
children.add(pubTest);
}
return children;
}
/**
* @see org.junit.runners.ParentRunner#run(org.junit.runner.notification.RunNotifier)
*/
@Override
public void run(RunNotifier notifier) {
setup();
PolicyTemplateUtil.clearCache();
MockGatewayServlet.reset();
log("");
log("-------------------------------------------------------------------------------");
log("Executing REST Test");
log("-------------------------------------------------------------------------------");
log("");
try {
super.run(notifier);
} finally {
try { shutdown(); } catch (Throwable e) { e.printStackTrace(); }
resetSystemProperties();
}
log("");
log("-------------------------------------------------------------------------------");
log("REST Test complete");
log("-------------------------------------------------------------------------------");
log("");
}
/**
* @see org.junit.runners.ParentRunner#runChild(java.lang.Object, org.junit.runner.notification.RunNotifier)
*/
@Override
protected void runChild(final TestInfo testInfo, RunNotifier notifier) {
log("-----------------------------------------------------------");
log("Starting Test [{0} / {1}]", testInfo.plan.name, testInfo.name);
log("-----------------------------------------------------------");
Description description = describeChild(testInfo);
if (testInfo instanceof GatewayAssertionTestInfo) {
runLeaf(new Statement() {
@Override
public void evaluate() throws Throwable {
String actualGatewayLog = MockGatewayServlet.getRequestLog();
Assert.assertEquals(((GatewayAssertionTestInfo) testInfo).expectedLog, actualGatewayLog);
}
}, description, notifier);
} else if (testInfo instanceof PublishPayloadTestInfo) {
runLeaf(new Statement() {
@Override
public void evaluate() throws Throwable {
String[] expectedPayloads = ((PublishPayloadTestInfo) testInfo).expectedPayloads;
int index = 0;
for (String expectedPayload : expectedPayloads) {
if (MockGatewayServlet.getPayloads().isEmpty()) {
Assert.fail("Expected a payload but did not find one.");
}
String actualPayload = MockGatewayServlet.getPayloads().get(index);
if (expectedPayload == null || "".equals(expectedPayload)) {
Assert.assertNull(actualPayload);
} else {
ObjectMapper mapper = new ObjectMapper();
JsonNode expected = mapper.readTree(expectedPayload);
JsonNode actual = mapper.readTree(actualPayload.trim());
JsonCompare jsonCompare = new JsonCompare();
jsonCompare.assertJson(expected, actual);
}
index++;
}
}
}, description, notifier);
} else {
runLeaf(new Statement() {
@Override
public void evaluate() throws Throwable {
String rtPath = testInfo.test.getValue();
Integer delay = testInfo.test.getDelay();
if (delay != null) {
try { Thread.sleep(delay); } catch (InterruptedException e) { }
}
if (rtPath != null && !rtPath.trim().isEmpty()) {
RestTest restTest = TestUtil.loadRestTest(rtPath, getTestClass().getJavaClass().getClassLoader());
String endpoint = testInfo.plan.endpoint;
if (StringUtils.isEmpty(endpoint)) {
endpoint = TestUtil.doPropertyReplacement(testInfo.test.getEndpoint());
}
if (StringUtils.isEmpty(endpoint)) {
endpoint = TestUtil.doPropertyReplacement(testInfo.group.getEndpoint());
}
if (StringUtils.isEmpty(endpoint)) {
endpoint = TestUtil.doPropertyReplacement(testInfo.plan.plan.getEndpoint());
}
if (StringUtils.isEmpty(endpoint)) {
endpoint = "http://localhost:" + getTestServerPort() + getBaseApiContext();
}
testInfo.plan.runner.runTest(restTest, endpoint);
}
}
}, description, notifier);
}
}
/**
* @see org.junit.runners.ParentRunner#describeChild(java.lang.Object)
*/
@Override
protected Description describeChild(TestInfo child) {
return Description.createTestDescription(getTestClass().getJavaClass(), child.name);
}
/**
* @return the base context of the DT API
*/
protected String getBaseApiContext() {
return System.getProperty("apiman.junit.server-api-context", "/apiman");
}
/**
* @return the port to use when sending requests
*/
protected int getTestServerPort() {
String spPort = System.getProperty("apiman.junit.server-port");
if (spPort != null) {
return Integer.parseInt(spPort);
}
if (USE_PROXY) {
return PROXY_PORT;
} else {
return testServer.serverPort();
}
}
/**
* Configure some proeprties.
*/
private void configureSystemProperties() {
TestUtil.setProperty("apiman.test.gateway.endpoint", "http://localhost:" + getTestServerPort() + "/mock-gateway");
TestUtil.setProperty("apiman.test.gateway.username", "admin");
TestUtil.setProperty("apiman.test.gateway.password", "admin");
TestUtil.setProperty("apiman.manager.require-auto-granted-org", "false");
RestTestSystemProperties annotation = getTestClass().getJavaClass().getAnnotation(RestTestSystemProperties.class);
if (annotation != null) {
String[] strings = annotation.value();
for (int idx = 0; idx < strings.length; idx += 2) {
String pname = strings[idx];
String pval = strings[idx+1];
log("Setting system property \"{0}\" to \"{1}\".", pname, pval);
if (System.getProperty(pname) == null) {
resetSysProps.add(pname);
}
TestUtil.setProperty(pname, pval);
}
}
}
/**
* Resets the system properties that were set at the start of the test.
*/
private void resetSystemProperties() {
for (String propName : resetSysProps) {
System.clearProperty(propName);
}
resetSysProps.clear();
}
/**
* Logs a message.
*
* @param message
* @param params
*/
public void log(String message, Object... params) {
String outmsg = MessageFormat.format(message, params);
logger.info(" >> " + outmsg);
}
public static class TestPlanInfo {
TestPlan plan;
String name;
String planPath;
String endpoint;
TestPlanRunner runner;
}
public static class TestInfo {
TestGroupType group;
TestType test;
String name;
TestPlanInfo plan;
}
public static class GatewayAssertionTestInfo extends TestInfo {
String expectedLog;
}
public static class PublishPayloadTestInfo extends TestInfo {
String[] expectedPayloads;
}
}