/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community 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://opensource.org/licenses/ecl2.txt * * 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 org.opencastproject.rest; import static org.opencastproject.util.data.Collections.toArray; import static org.opencastproject.util.data.Monadics.mlist; import static org.opencastproject.util.data.Option.some; import static org.opencastproject.util.data.functions.Misc.chuck; import org.opencastproject.util.IoSupport; import org.opencastproject.util.UrlSupport; import org.opencastproject.util.data.Function; import org.opencastproject.util.data.Option; import com.sun.jersey.api.core.ClassNamesResourceConfig; import com.sun.jersey.api.core.PackagesResourceConfig; import com.sun.jersey.api.core.ResourceConfig; import com.sun.jersey.spi.container.servlet.ServletContainer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.Random; import java.util.regex.Pattern; /** * Helper environment for creating REST service unit tests. * <p> * The REST endpoint to test needs a no-arg constructor in order to be created by the framework. * <p> * Write REST unit tests using <a href="http://code.google.com/p/rest-assured/">rest assured</a>. * <h3>Example Usage</h3> * * <pre> * import static com.jayway.restassured.RestAssured.*; * import static com.jayway.restassured.matcher.RestAssuredMatchers.*; * import static org.hamcrest.Matchers.*; * import static org.opencastproject.rest.RestServiceTestEnv.* * * public class RestEndpointTest { * // create a local environment running on some random port * // use rt.host("/path/to/service") to wrap all URL creations for HTTP request methods * private static final RestServiceTestEnv rt = testEnvScanAllPackages(localhostRandomPort()); * * \@BeforeClass public static void oneTimeSetUp() { * env.setUpServer(); * } * * \@AfterClass public static void oneTimeTearDown() { * env.tearDownServer(); * } * } * </pre> * * Add the following dependencies to your pom * * <pre> * <dependency> * <groupId>com.jayway.restassured</groupId> * <artifactId>rest-assured</artifactId> * <version>1.7.2</version> * <scope>test</scope> * </dependency> * <dependency> * <groupId>org.apache.httpcomponents</groupId> * <artifactId>httpcore</artifactId> * <version>4.2.4</version> * <scope>test</scope> * </dependency> * </pre> */ public final class RestServiceTestEnv { private Server hs; private final URL baseUrl; private final Option<? extends ResourceConfig> cfg; private static final Logger logger = LoggerFactory.getLogger(RestServiceTestEnv.class); /** * Create an environment for <code>baseUrl</code>. The base URL should be the URL where the service to test is * mounted, e.g. http://localhost:8090/test */ private RestServiceTestEnv(URL baseUrl, Option<? extends ResourceConfig> cfg) { this.baseUrl = baseUrl; this.cfg = cfg; } public static RestServiceTestEnv testEnvScanAllPackages(URL baseUrl) { return new RestServiceTestEnv(baseUrl, Option.<ResourceConfig> none()); } public static RestServiceTestEnv testEnvScanPackages(URL baseUrl, Package... servicePkgs) { return new RestServiceTestEnv(baseUrl, some(new PackagesResourceConfig(toArray(String.class, mlist(servicePkgs).map(pkgName).value())))); } public static RestServiceTestEnv testEnvForClasses(URL baseUrl, Class... restServices) { return new RestServiceTestEnv(baseUrl, some(new ClassNamesResourceConfig(restServices))); } public static RestServiceTestEnv testEnvForCustomConfig(String baseUrl, ResourceConfig cfg) { return new RestServiceTestEnv(UrlSupport.url(baseUrl), some(cfg)); } public static RestServiceTestEnv testEnvForCustomConfig(URL baseUrl, ResourceConfig cfg) { return new RestServiceTestEnv(baseUrl, some(cfg)); } /** * Return a localhost base URL with a random port between 8081 and 9000. The method features a port usage detection to * ensure it returns a free port. */ public static URL localhostRandomPort() { for (int tries = 100; tries > 0; tries--) { final URL url = UrlSupport.url("http", "localhost", 8081 + new Random(System.currentTimeMillis()).nextInt(919)); InputStream in = null; try { final URLConnection con = url.openConnection(); con.setConnectTimeout(1000); con.setReadTimeout(1000); con.getInputStream(); } catch (IOException e) { IoSupport.closeQuietly(in); return url; } } throw new RuntimeException("Cannot find free port. Giving up."); } /** Create a URL suitable for rest-assured's post(), get() e.al. methods. */ public String host(String path) { return UrlSupport.url(baseUrl, path).toString(); } /** Return the port the configured server is running on. */ public int getPort() { return baseUrl.getPort(); } /** * Return the base URL of the HTTP server. <code>http://host:port</code> public URL getBaseUrl() { return baseUrl; } * * Call in @BeforeClass annotated method. */ public void setUpServer() { try { // cut of any base pathbasestUrl might have int port = baseUrl.getPort(); logger.info("Start http server at port " + port); hs = new Server(port); ServletContainer servletContainer = cfg.isSome() ? new ServletContainer(cfg.get()) : new ServletContainer(); ServletHolder jerseyServlet = new ServletHolder(servletContainer); ServletContextHandler context = new ServletContextHandler(hs, "/"); context.addServlet(jerseyServlet, "/*"); hs.start(); } catch (Exception e) { chuck(e); } } /** Call in @AfterClass annotated method. */ public void tearDownServer() { if (hs != null) { logger.info("Stop http server"); try { hs.stop(); } catch (Exception e) { logger.warn("Stop http server - failed {}", e.getMessage()); } } } private static final Function<Package, String> pkgName = new Function<Package, String>() { @Override public String apply(Package pkg) { return pkg.getName(); } }; // Hamcrest matcher public static class RegexMatcher extends BaseMatcher<String> { private final Pattern p; public RegexMatcher(String pattern) { p = Pattern.compile(pattern); } public static RegexMatcher regex(String pattern) { return new RegexMatcher(pattern); } @Override public boolean matches(Object item) { return item != null && p.matcher(item.toString()).matches(); } @Override public void describeTo(Description description) { description.appendText("regex [" + p.pattern() + "]"); } } }