/* * Copyright 2014, The Sporting Exchange Limited * Copyright 2015, Simon Matić Langford * * 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 com.betfair.cougar.transport.jetty; import com.betfair.cougar.api.export.Protocol; import com.betfair.cougar.api.security.IdentityTokenResolver; import com.betfair.cougar.core.api.ServiceVersion; import com.betfair.cougar.core.api.exception.CougarValidationException; import com.betfair.cougar.core.api.exception.ServerFaultCode; import com.betfair.cougar.transport.api.TransportCommandProcessor; import com.betfair.cougar.transport.api.protocol.ProtocolBinding; import com.betfair.cougar.transport.api.protocol.http.HttpCommand; import org.eclipse.jetty.continuation.Continuation; import org.eclipse.jetty.continuation.ContinuationListener; import org.eclipse.jetty.continuation.ContinuationThrowable; import org.eclipse.jetty.server.Request; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import javax.servlet.*; import javax.servlet.http.*; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Collection; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Unit test for @see JettyHandler */ public class JettyHandlerTest { private JettyHandler jettyHandler; private TransportCommandProcessor<HttpCommand> commandProcessor; private HttpServletRequest request; private HttpServletResponse response; private Request jettyRequest; public static final String PROTOCOL_BINDING_CONTEXT = "/wibble"; private JettyHandlerSpecification jettyHandlerSpec; @Before public void init() { commandProcessor = Mockito.mock(TransportCommandProcessor.class); jettyHandlerSpec = new JettyHandlerSpecification(PROTOCOL_BINDING_CONTEXT, Protocol.RESCRIPT, "serviceContext"); jettyHandler = new JettyHandler(commandProcessor, jettyHandlerSpec, null, true); jettyRequest = Mockito.mock(Request.class); request = Mockito.mock(HttpServletRequest.class); Continuation c = Mockito.mock(Continuation.class); when(request.getAttribute(eq(Continuation.ATTRIBUTE))).thenReturn(c); response = Mockito.mock(HttpServletResponse.class); } @Test public void testHandle() throws Exception { when(request.getContextPath()).thenReturn(PROTOCOL_BINDING_CONTEXT + "/someotherurl"); jettyHandler.handle(null, jettyRequest, request, response); verify(commandProcessor).process(any(HttpCommand.class)); } @Test public void testHandle2() throws Exception { when(request.getContextPath()).thenReturn(PROTOCOL_BINDING_CONTEXT + "/someotherurl"); jettyHandler.handle(null, jettyRequest, request, response); ArgumentCaptor<HttpCommand> commandArgumentCaptor = ArgumentCaptor.forClass(HttpCommand.class); verify(commandProcessor).process(commandArgumentCaptor.capture()); //The default implementation of the jettyhandler has no identityTokenLookup thingo specified, so verify //that the command has no identityTokenResolver set on it HttpCommand command = commandArgumentCaptor.getValue(); assertNull("Should not have an IdentityTokenResolver set", command.getIdentityTokenResolver()); } @Test public void testHandle3() throws Exception { IdentityTokenResolver resolver = Mockito.mock(IdentityTokenResolver.class); JettyHandler.IdentityTokenResolverLookup identityTokenResolverLookup = Mockito.mock(JettyHandler.IdentityTokenResolverLookup.class); when(identityTokenResolverLookup.lookupIdentityTokenResolver(anyString())).thenReturn(resolver); JettyHandler jh = new JettyHandler(commandProcessor, null, identityTokenResolverLookup, true); jh.handle(null, jettyRequest, request, response); ArgumentCaptor<HttpCommand> commandArgumentCaptor = ArgumentCaptor.forClass(HttpCommand.class); verify(commandProcessor).process(commandArgumentCaptor.capture()); //The default implementation of the jettyhandler has no identityTokenLookup thingo specified, so verify //that the command has no identityTokenResolver set on it HttpCommand command = commandArgumentCaptor.getValue(); assertEquals("This command should have an identityTokenResolver set", resolver, command.getIdentityTokenResolver()); } @Test public void testHandleGenericException() throws Exception { JettyHandler.IdentityTokenResolverLookup identityTokenResolverLookup = Mockito.mock(JettyHandler.IdentityTokenResolverLookup.class); when(identityTokenResolverLookup.lookupIdentityTokenResolver(anyString())).thenThrow(new IllegalArgumentException()); JettyHandler jh = new JettyHandler(commandProcessor, null, identityTokenResolverLookup, true); jh.handle(null, jettyRequest, request, response); verify(response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } @Test public void testHandleCougarException() throws Exception { JettyHandler.IdentityTokenResolverLookup identityTokenResolverLookup = Mockito.mock(JettyHandler.IdentityTokenResolverLookup.class); when(identityTokenResolverLookup.lookupIdentityTokenResolver(anyString())).thenThrow(new CougarValidationException(ServerFaultCode.NoSuchService)); JettyHandler jh = new JettyHandler(commandProcessor, null, identityTokenResolverLookup, true); jh.handle(null, jettyRequest, request, response); verify(response).sendError(HttpServletResponse.SC_NOT_FOUND); } @Test public void testCommandInstantiationWithBindingPath() { String operationPath = "/serviceContext/v2/operationUri"; String fullpath = PROTOCOL_BINDING_CONTEXT + operationPath; when(request.getContextPath()).thenReturn(fullpath); HttpCommand cmd = jettyHandler.new JettyTransportCommand(request, response); assertEquals("Full path was not equal", cmd.getFullPath(), fullpath); assertEquals("Operation Path was not equal", cmd.getOperationPath(), operationPath); } @Test public void testCommandInstantiationWithoutBindingPath() { JettyHandlerSpecification spec = new JettyHandlerSpecification(null, Protocol.RESCRIPT, "serviceContext"); JettyHandler jh = new JettyHandler(commandProcessor, spec, null, true); String operationPath = "/serviceContext/v2/operationUri"; String fullpath = operationPath; when(request.getContextPath()).thenReturn(fullpath); HttpCommand cmd = jh.new JettyTransportCommand(request, response); assertEquals("Full path was not equal", cmd.getFullPath(), fullpath); assertEquals("Operation Path was not equal", cmd.getOperationPath(), operationPath); } @Test public void perf() { JettyHandlerSpecification spec = new JettyHandlerSpecification("/q", Protocol.RESCRIPT, "serviceContext"); ProtocolBinding pb = new ProtocolBinding("/q", null, Protocol.RESCRIPT); JettyHandler jh = new JettyHandler(null, spec, null, true); final String cp = "/asdfasldkfjasdf/"; final String pathInfo = "/qqqqq"; final Continuation c = new Continuation() { @Override public void setTimeout(long timeoutMs) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void suspend() { //To change body of implemented methods use File | Settings | File Templates. } @Override public void suspend(ServletResponse response) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void resume() { //To change body of implemented methods use File | Settings | File Templates. } @Override public void complete() { //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isSuspended() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isResumed() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isExpired() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isInitial() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isResponseWrapped() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public ServletResponse getServletResponse() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void addContinuationListener(ContinuationListener listener) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setAttribute(String name, Object attribute) { //To change body of implemented methods use File | Settings | File Templates. } @Override public Object getAttribute(String name) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void removeAttribute(String name) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void undispatch() throws ContinuationThrowable { //To change body of implemented methods use File | Settings | File Templates. } }; HttpServletRequest req = new HttpServletRequest() { @Override public String changeSessionId() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public long getContentLengthLong() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public void login(String s, String s2) throws ServletException { //To change body of implemented methods use File | Settings | File Templates. } @Override public void logout() throws ServletException { //To change body of implemented methods use File | Settings | File Templates. } @Override public Collection<Part> getParts() throws IOException, ServletException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Part getPart(String s) throws IOException, ServletException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public ServletContext getServletContext() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public AsyncContext startAsync() throws IllegalStateException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isAsyncStarted() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isAsyncSupported() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public AsyncContext getAsyncContext() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public DispatcherType getDispatcherType() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getAuthType() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Cookie[] getCookies() { return new Cookie[0]; //To change body of implemented methods use File | Settings | File Templates. } @Override public long getDateHeader(String name) { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getHeader(String name) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Enumeration getHeaders(String name) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Enumeration getHeaderNames() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public int getIntHeader(String name) { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getMethod() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getPathInfo() { return pathInfo; } @Override public String getPathTranslated() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getContextPath() { return cp; } @Override public String getQueryString() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRemoteUser() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isUserInRole(String role) { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public Principal getUserPrincipal() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRequestedSessionId() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRequestURI() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public StringBuffer getRequestURL() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getServletPath() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public HttpSession getSession(boolean create) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public HttpSession getSession() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isRequestedSessionIdValid() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isRequestedSessionIdFromCookie() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isRequestedSessionIdFromURL() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isRequestedSessionIdFromUrl() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public Object getAttribute(String name) { return c; } @Override public Enumeration getAttributeNames() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getCharacterEncoding() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void setCharacterEncoding(String env) throws UnsupportedEncodingException { //To change body of implemented methods use File | Settings | File Templates. } @Override public int getContentLength() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getContentType() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public ServletInputStream getInputStream() throws IOException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getParameter(String name) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Enumeration getParameterNames() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String[] getParameterValues(String name) { return new String[0]; //To change body of implemented methods use File | Settings | File Templates. } @Override public Map getParameterMap() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getProtocol() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getScheme() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getServerName() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public int getServerPort() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public BufferedReader getReader() throws IOException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRemoteAddr() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRemoteHost() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void setAttribute(String name, Object o) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void removeAttribute(String name) { //To change body of implemented methods use File | Settings | File Templates. } @Override public Locale getLocale() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Enumeration getLocales() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isSecure() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public RequestDispatcher getRequestDispatcher(String path) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getRealPath(String path) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public int getRemotePort() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getLocalName() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getLocalAddr() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public int getLocalPort() { return 0; //To change body of implemented methods use File | Settings | File Templates. } }; HttpServletResponse resp = new HttpServletResponse() { @Override public int getStatus() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getHeader(String s) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Collection<String> getHeaders(String s) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public Collection<String> getHeaderNames() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void addCookie(Cookie cookie) { //To change body of implemented methods use File | Settings | File Templates.; } @Override public boolean containsHeader(String name) { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public String encodeURL(String url) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String encodeRedirectURL(String url) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String encodeUrl(String url) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String encodeRedirectUrl(String url) { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void sendError(int sc, String msg) throws IOException { //To change body of implemented methods use File | Settings | File Templates. } @Override public void sendError(int sc) throws IOException { //To change body of implemented methods use File | Settings | File Templates. } @Override public void sendRedirect(String location) throws IOException { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setDateHeader(String name, long date) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void addDateHeader(String name, long date) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setHeader(String name, String value) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void addHeader(String name, String value) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setIntHeader(String name, int value) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void addIntHeader(String name, int value) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setStatus(int sc) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setStatus(int sc, String sm) { //To change body of implemented methods use File | Settings | File Templates. } @Override public String getCharacterEncoding() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public String getContentType() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public ServletOutputStream getOutputStream() throws IOException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public PrintWriter getWriter() throws IOException { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void setCharacterEncoding(String charset) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setContentLength(int len) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setContentType(String type) { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setBufferSize(int size) { //To change body of implemented methods use File | Settings | File Templates. } @Override public int getBufferSize() { return 0; //To change body of implemented methods use File | Settings | File Templates. } @Override public void flushBuffer() throws IOException { //To change body of implemented methods use File | Settings | File Templates. } @Override public void resetBuffer() { //To change body of implemented methods use File | Settings | File Templates. } @Override public boolean isCommitted() { return false; //To change body of implemented methods use File | Settings | File Templates. } @Override public void reset() { //To change body of implemented methods use File | Settings | File Templates. } @Override public void setLocale(Locale loc) { //To change body of implemented methods use File | Settings | File Templates. } @Override public Locale getLocale() { return null; //To change body of implemented methods use File | Settings | File Templates. } @Override public void setContentLengthLong(long len) { //To change body of implemented methods use File | Settings | File Templates. } }; for (int i=0; i<100000; i++) { HttpCommand cmd = jh.new JettyTransportCommand(req, resp); } long start = System.currentTimeMillis(); for (int i=0; i<10000000; i++) { HttpCommand cmd = jh.new JettyTransportCommand(req, resp); } System.out.println("Execution time: " + (System.currentTimeMillis()-start)); } @Test public void testSingletonIdentityTokenResolution() { IdentityTokenResolver identityTokenResolver = Mockito.mock(IdentityTokenResolver.class); JettyHandler.SingletonIdentityTokenResolverLookup lookup = new JettyHandler.SingletonIdentityTokenResolverLookup(identityTokenResolver); assertEquals("Incorrect identityTokenResolver returned", identityTokenResolver, lookup.lookupIdentityTokenResolver("xxx")); } @Test public void testGeneralIdentityTokenResolutionVersionScrubbing() { final String contextRoot = "/serviceUri"; JettyHandler.GeneralHttpIdentityTokenResolverLookup lookup = new JettyHandler.GeneralHttpIdentityTokenResolverLookup(contextRoot, Mockito.mock(JettyHandlerSpecification.class)); assertEquals("failed to extract version correctly", "v2", lookup.extractVersion("/serviceUri/v2.0/foo?arg=bar")); assertEquals("failed to extract version correctly", "v2", lookup.extractVersion("/serviceUri/V2.0/foo?arg=bar")); assertEquals("failed to extract version correctly", "v3", lookup.extractVersion("/serviceUri/v3.600/foo?arg=bar")); assertEquals("failed to extract version correctly", "v3", lookup.extractVersion("/serviceUri/V3.600/foo?arg=bar")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/v4/foo?arg=bar")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/v4/")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/v4.5/")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/V4.5/")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/v4")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/v4.5")); assertEquals("failed to extract version correctly", "v4", lookup.extractVersion("/serviceUri/V4.5")); try { lookup.extractVersion("/thereIsNoVersionHere"); fail("This should have thrown an exception as the uri did not contain a version"); } catch (CougarValidationException expected) {} } @Test public void testGeneralIdentityTokenResolutionLookup() { final String contextRoot = "/serviceUri"; IdentityTokenResolver identityTokenResolver1 = Mockito.mock(IdentityTokenResolver.class); IdentityTokenResolver identityTokenResolver2 = Mockito.mock(IdentityTokenResolver.class); JettyHandlerSpecification spec = new JettyHandlerSpecification("", Protocol.RESCRIPT, contextRoot); spec.addServiceVersionToTokenResolverEntry(new ServiceVersion("v1.2"), identityTokenResolver1); spec.addServiceVersionToTokenResolverEntry(new ServiceVersion("v2.4"), identityTokenResolver2); JettyHandler.GeneralHttpIdentityTokenResolverLookup lookup = new JettyHandler.GeneralHttpIdentityTokenResolverLookup(contextRoot, spec); assertEquals("We didn't get the IdentityTokenResolver we anticipated", identityTokenResolver1, lookup.lookupIdentityTokenResolver(contextRoot + "/v1.1/myInterestingOperation?arg=foo")); assertEquals("We didn't get the IdentityTokenResolver we anticipated", identityTokenResolver2, lookup.lookupIdentityTokenResolver(contextRoot + "/v2.4/myInterestingOperation?arg=foo")); assertEquals("We didn't get the IdentityTokenResolver we anticipated", identityTokenResolver2, lookup.lookupIdentityTokenResolver(contextRoot + "/v2/myInterestingOperation?arg=foo")); try { lookup.lookupIdentityTokenResolver("noVersionHere"); fail("This should have thrown an exception as the uri did not contain a version"); } catch (CougarValidationException expected) {} } }