/******************************************************************************* * Copyright 2012 Geoscience Australia * * 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 au.gov.ga.earthsci.core.retrieve.retriever; import static org.junit.Assert.*; import gov.nasa.worldwind.util.WWIO; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.URL; import org.jmock.Expectations; import org.jmock.Mockery; import org.junit.AfterClass; import org.junit.Assume; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import au.gov.ga.earthsci.core.retrieve.IRetrievalProperties; import au.gov.ga.earthsci.core.retrieve.IRetrievalResult; import au.gov.ga.earthsci.core.retrieve.IRetrieverMonitor; import au.gov.ga.earthsci.core.retrieve.RetrievalProperties; import au.gov.ga.earthsci.core.retrieve.RetrievalStatus; /** * Unit tests for the {@link HttpRetriever} class * * @author James Navin (james.navin@ga.gov.au) */ public class HTTPRetrieverTest { private final HttpRetriever classUnderTest = new HttpRetriever(); private IRetrieverMonitor monitor; private Mockery mockContext; @BeforeClass public static void initialise() { createHttpServer(); startServer(); } @AfterClass public static void destroy() { stopServer(); } @Before public void setup() { mockContext = new Mockery(); monitor = mockContext.mock(IRetrieverMonitor.class); mockContext.checking(new Expectations() { { { // Monitor allowing(monitor); } } }); } @Test public void testSupportsFileURL() throws Exception { URL url = new URL("file://somefile.txt"); assertFalse(classUnderTest.supports(url)); } @Test public void testSupportsHttpURL() throws Exception { URL url = new URL("http://somewhere.com/somefile.txt"); assertTrue(classUnderTest.supports(url)); } @Test public void testSupportsHttpsURL() throws Exception { URL url = new URL("https://somewhere.com/somefile.txt"); assertTrue(classUnderTest.supports(url)); } @Test(expected = NullPointerException.class) public void testRetrieveNullURL() throws Exception { URL url = null; mockContext.checking(new Expectations() { { { oneOf(monitor).updateStatus(RetrievalStatus.STARTED); } } }); classUnderTest.retrieve(url, monitor, createRetrievalProperties(), null); } @Test(expected = NullPointerException.class) public void testRetrieveFileURL() throws Exception { URL url = new URL("file://somefile.txt"); mockContext.checking(new Expectations() { { { oneOf(monitor).updateStatus(RetrievalStatus.STARTED); } } }); classUnderTest.retrieve(url, monitor, createRetrievalProperties(), null); } @Test public void testRetrieveHttpURLWithSuccessKnownLength() throws Exception { Assume.assumeTrue(httpServerIsAvailable()); final String expectedResult = "success!"; setServerResponse("/success", 200, expectedResult, false); URL url = createHttpURL("/success"); IRetrievalResult result = classUnderTest.retrieve(url, monitor, createRetrievalProperties(), null).result; assertNotNull(result); assertNull(result.getError()); String string = WWIO.readStreamToString(result.getData().getInputStream(), "UTF-8"); assertEquals(expectedResult, string); } @Test(expected = IOException.class) public void testRetrieveHttpURLWithFail404() throws Exception { Assume.assumeTrue(httpServerIsAvailable()); URL url = createHttpURL("/404"); classUnderTest.retrieve(url, monitor, createRetrievalProperties(), null); } @Test(expected = IOException.class) public void testRetrieveHttpURLWithFail403() throws Exception { Assume.assumeTrue(httpServerIsAvailable()); String expectedResult = "success!"; setServerResponse("/fail", 403, expectedResult, true); URL url = createHttpURL("/fail"); classUnderTest.retrieve(url, monitor, createRetrievalProperties(), null); } private static IRetrievalProperties createRetrievalProperties() { RetrievalProperties retrievalProperties = new RetrievalProperties(); retrievalProperties.setUseCache(false); return retrievalProperties; } // TODO: Move this code somewhere more reusable private static InetSocketAddress serverAddress; private static Class<?> serverClass; private static Object serverInstance; private static URL createHttpURL(String relativePath) throws Exception { URL url = new URL("http://" + serverAddress.getHostName() + ":" + serverAddress.getPort() + relativePath); return url; } /** * Check that the Sun JVM HttpServer class is present. Used to filter tests * that depend on this class. */ private static boolean httpServerIsAvailable() { try { ClassLoader classLoader = HttpRetriever.class.getClassLoader(); Class.forName("com.sun.net.httpserver.HttpServer", false, classLoader); Class.forName("com.sun.net.httpserver.HttpHandler", false, classLoader); Class.forName("com.sun.net.httpserver.HttpExchange", false, classLoader); return true; } catch (Exception e) { return false; } } private static void createHttpServer() { if (!httpServerIsAvailable()) { return; } try { serverAddress = new InetSocketAddress("localhost", chooseAvailablePort()); serverClass = Class.forName("com.sun.net.httpserver.HttpServer"); serverInstance = serverClass.getMethod("create", InetSocketAddress.class, int.class).invoke(serverClass, serverAddress, 0); } catch (Exception e) { e.printStackTrace(); } } private static int chooseAvailablePort() { for (int port = 8796; port < 9000; port++) { try { ServerSocket ss = new ServerSocket(port); ss.setReuseAddress(true); ss.close(); return port; } catch (Exception e) { } } return -1; } private static void startServer() { try { serverClass.getMethod("start").invoke(serverInstance); } catch (Exception e) { e.printStackTrace(); } } private static void stopServer() { try { serverClass.getMethod("stop", int.class).invoke(serverInstance, 0); } catch (Exception e) { e.printStackTrace(); } } private static void setServerResponse(final String path, final int responseCode, final String response, final boolean unknownContentLength) { if (!httpServerIsAvailable()) { return; } try { final Class<?> handlerClass = Class.forName("com.sun.net.httpserver.HttpHandler"); final Class<?> exchangeClass = Class.forName("com.sun.net.httpserver.HttpExchange"); Object handler = Proxy.newProxyInstance(HttpRetriever.class.getClassLoader(), new Class[] { handlerClass }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (!method.getName().equalsIgnoreCase("handle")) { return null; } Object httpExchange = args[0]; // Send headers long responseLength = unknownContentLength ? 0 : response == null ? -1 : response.getBytes().length; exchangeClass.getMethod("sendResponseHeaders", int.class, long.class).invoke( httpExchange, responseCode, responseLength); // Write response OutputStream responseBody = (OutputStream) exchangeClass.getMethod("getResponseBody").invoke( httpExchange); responseBody.write(response.getBytes()); exchangeClass.getMethod("close").invoke(httpExchange); } catch (Exception e) { // Should never get here e.printStackTrace(); } return null; } }); serverClass.getMethod("createContext", String.class, handlerClass).invoke(serverInstance, path, handler); } catch (Exception e) { e.printStackTrace(); } } }