/* * 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.gateway.test.junit; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.apiman.gateway.test.junit.GatewayRestTester.TestInfo; 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.IGatewayTestServer; import io.apiman.test.common.resttest.IGatewayTestServerFactory; 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.io.IOException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; 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; /** * A junit test runner that fires up an API Gateway and makes it ready for use * in the tests. This runner also loads up the test plan from the required * {@link GatewayRestTestPlan} annotation. * * @author eric.wittmann@redhat.com */ @SuppressWarnings("nls") public class GatewayRestTester extends ParentRunner<TestInfo> { private static Logger logger = LoggerFactory.getLogger(TestPlanRunner.class); private static IGatewayTestServer gatewayServer; static { createAndConfigureGateway(); } /** * Creates a gateway from a gateway test config file. */ protected static void createAndConfigureGateway() { String testConfig = System.getProperty("apiman.gateway-test.config", null); if (testConfig == null) { testConfig = "default"; } URL configUrl = GatewayRestTester.class.getClassLoader().getResource("test-configs/" + testConfig + ".json"); ObjectMapper mapper = new ObjectMapper(); try { JsonNode config = mapper.readTree(configUrl); String factoryFQN = config.get("factory").asText(); IGatewayTestServerFactory factory = (IGatewayTestServerFactory) Class.forName(factoryFQN).newInstance(); gatewayServer = factory.createGatewayTestServer(); gatewayServer.configure(config); } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } private List<TestPlanInfo> testPlans = new ArrayList<>(); private Set<String> resetSysProps = new HashSet<>(); /** * Constructor. * @param testClass the test class * @throws InitializationError the initialziation error */ public GatewayRestTester(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 { GatewayRestTestPlan annotation = testClass.getAnnotation(GatewayRestTestPlan.class); if (annotation == null) { throw new InitializationError("Missing @GatewayRestTestPlan annotation on test class."); } else { TestPlanInfo planInfo = new TestPlanInfo(); planInfo.planPath = annotation.value(); planInfo.name = new File(planInfo.planPath).getName(); planInfo.plan = TestUtil.loadTestPlan(planInfo.planPath, testClass.getClassLoader()); testPlans.add(planInfo); } } catch (Throwable e) { throw new InitializationError(e); } if (testPlans.isEmpty()) { throw new InitializationError("No @GatewayRestTestPlan annotations found on test class: " + testClass); } } /** * Called to setup the test. */ public void setup() { startServer(); } /** * Called at the end of the test. */ public void shutdown() { stopServer(); } /** * @throws Exception */ protected static void startServer() { try { gatewayServer.start(); } catch (Exception e) { throw new RuntimeException(e); } } /** * @throws Exception */ protected static void stopServer() { try { gatewayServer.stop(); } catch (Exception e) { throw new RuntimeException(e); } } /** * @see org.junit.runners.ParentRunner#getChildren() */ @Override protected List<TestInfo> getChildren() { List<TestInfo> children = new ArrayList<>(); for (TestPlanInfo planInfo : testPlans) { 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); } } } return children; } /** * @see org.junit.runners.ParentRunner#run(org.junit.runner.notification.RunNotifier) */ @Override public void run(RunNotifier notifier) { setup(); log(""); log("-------------------------------------------------------------------------------"); log("Executing REST Test"); log("-------------------------------------------------------------------------------"); log(""); System.setProperty("apiman-gateway-test.endpoints.echo", gatewayServer.getEchoTestEndpoint()); 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); 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(testInfo.test.getValue(), getTestClass().getJavaClass().getClassLoader()); String endpoint = null; if (endpoint == null) { endpoint = testInfo.test.getEndpoint(); } if (endpoint == null) { endpoint = testInfo.group.getEndpoint(); } if (endpoint == null) { endpoint = testInfo.plan.plan.getEndpoint(); } if (endpoint != null) { endpoint = resolveEndpoint(endpoint); } if (endpoint == null) { endpoint = gatewayServer.getGatewayEndpoint(); } testInfo.plan.runner.runTest(restTest, endpoint); gatewayServer.next(endpoint); } } }, description, notifier); } /** * Resolves the logical endpoint into a real endpoint provided by the {@link IGatewayTestServer}. * @param endpoint */ protected String resolveEndpoint(String endpoint) { if ("api".equals(endpoint)) { return gatewayServer.getApiEndpoint(); } else if ("gateway".equals(endpoint)) { return gatewayServer.getGatewayEndpoint(); } else { return TestUtil.doPropertyReplacement(endpoint); } } /** * @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.gateway-context", "/"); } /** * Configure some proeprties. */ private void configureSystemProperties() { GatewayRestTestSystemProperties annotation = getTestClass().getJavaClass().getAnnotation(GatewayRestTestSystemProperties.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 the message * @param params the 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; TestPlanRunner runner; } public static class TestInfo { TestGroupType group; TestType test; String name; TestPlanInfo plan; } }