diff options
author | Paul Hawke <paul.hawke@gmail.com> | 2014-04-24 23:21:55 -0500 |
---|---|---|
committer | Paul Hawke <paul.hawke@gmail.com> | 2014-04-24 23:21:55 -0500 |
commit | 84a1ab453aafe41149fab0e663b49d85cf0896cb (patch) | |
tree | 0e858c7fa82ea5b6971781e73329945018a15de7 | |
parent | 44f52c7b241f235d887ab57bf0ffd97a8ce0f5c4 (diff) | |
download | nanohttpd-84a1ab453aafe41149fab0e663b49d85cf0896cb.tar.gz |
Simplified the test using Mockito, and expanded what was being tested along with some other minor cleanup
11 files changed, 212 insertions, 190 deletions
diff --git a/websocket/pom.xml b/websocket/pom.xml index 7e8960f..f9d9703 100644 --- a/websocket/pom.xml +++ b/websocket/pom.xml @@ -18,6 +18,11 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <version>1.9.5</version> + </dependency> + <dependency> <groupId>fi.iki.elonen</groupId> <artifactId>nanohttpd</artifactId> <version>2.1.0</version> diff --git a/websocket/src/main/java/fi/iki/elonen/WebSocketFactory.java b/websocket/src/main/java/fi/iki/elonen/IWebSocketFactory.java index 4a4be16..3e38cd2 100644 --- a/websocket/src/main/java/fi/iki/elonen/WebSocketFactory.java +++ b/websocket/src/main/java/fi/iki/elonen/IWebSocketFactory.java @@ -2,6 +2,6 @@ package fi.iki.elonen; import fi.iki.elonen.NanoHTTPD.IHTTPSession; -public interface WebSocketFactory { +public interface IWebSocketFactory { WebSocket openWebSocket(IHTTPSession handshake); } diff --git a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java index 44a5e9c..0712371 100644 --- a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java +++ b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java @@ -1,10 +1,11 @@ package fi.iki.elonen; -public class NanoWebSocketServer extends NanoHTTPD implements WebSocketFactory { - +public class NanoWebSocketServer extends NanoHTTPD implements IWebSocketFactory { + public static final String MISSING_FACTORY_MESSAGE = "You must either override this method or supply a WebSocketFactory in the constructor"; + private final WebSocketResponseHandler responseHandler; - + public NanoWebSocketServer(int port) { super(port); responseHandler = new WebSocketResponseHandler(this); @@ -14,17 +15,17 @@ public class NanoWebSocketServer extends NanoHTTPD implements WebSocketFactory { super(hostname, port); responseHandler = new WebSocketResponseHandler(this); } - - public NanoWebSocketServer(int port, WebSocketFactory webSocketFactory) { + + public NanoWebSocketServer(int port, IWebSocketFactory webSocketFactory) { super(port); responseHandler = new WebSocketResponseHandler(webSocketFactory); } - public NanoWebSocketServer(String hostname, int port,WebSocketFactory webSocketFactory) { + public NanoWebSocketServer(String hostname, int port, IWebSocketFactory webSocketFactory) { super(hostname, port); responseHandler = new WebSocketResponseHandler(webSocketFactory); } - + @Override public Response serve(IHTTPSession session) { Response candidate = responseHandler.serve(session); @@ -32,7 +33,7 @@ public class NanoWebSocketServer extends NanoHTTPD implements WebSocketFactory { } public WebSocket openWebSocket(IHTTPSession handshake) { - throw new Error("You must either override this method or supply a WebSocketFactory in the cosntructor"); + throw new Error(MISSING_FACTORY_MESSAGE); } } diff --git a/websocket/src/main/java/fi/iki/elonen/WebSocket.java b/websocket/src/main/java/fi/iki/elonen/WebSocket.java index ba1a446..22b07fb 100644 --- a/websocket/src/main/java/fi/iki/elonen/WebSocket.java +++ b/websocket/src/main/java/fi/iki/elonen/WebSocket.java @@ -12,20 +12,24 @@ import java.util.LinkedList; import java.util.List; public abstract class WebSocket { - protected final InputStream in; - protected /*final*/ OutputStream out; + public static enum State { + UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED + } + + protected InputStream in; + + protected OutputStream out; protected WebSocketFrame.OpCode continuousOpCode = null; + protected List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>(); protected State state = State.UNCONNECTED; - public static enum State { - UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED - } - protected final NanoHTTPD.IHTTPSession handshakeRequest; - protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) { + + protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response( + NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) { @Override protected void send(OutputStream out) { WebSocket.this.out = out; @@ -40,8 +44,18 @@ public abstract class WebSocket { this.handshakeRequest = handshakeRequest; this.in = handshakeRequest.getInputStream(); - handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_UPGRADE, WebSocketResponseHandler.HEADER_UPGRADE_VALUE); - handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_CONNECTION, WebSocketResponseHandler.HEADER_CONNECTION_VALUE); + handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_UPGRADE, + WebSocketResponseHandler.HEADER_UPGRADE_VALUE); + handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_CONNECTION, + WebSocketResponseHandler.HEADER_CONNECTION_VALUE); + } + + public NanoHTTPD.IHTTPSession getHandshakeRequest() { + return handshakeRequest; + } + + public NanoHTTPD.Response getHandshakeResponse() { + return handshakeResponse; } // --------------------------------IO-------------------------------------- @@ -190,14 +204,4 @@ public abstract class WebSocket { doClose(code, reason, false); } } - - // --------------------------------Getters--------------------------------- - - public NanoHTTPD.IHTTPSession getHandshakeRequest() { - return handshakeRequest; - } - - public NanoHTTPD.Response getHandshakeResponse() { - return handshakeResponse; - } } diff --git a/websocket/src/main/java/fi/iki/elonen/WebSocketResponseHandler.java b/websocket/src/main/java/fi/iki/elonen/WebSocketResponseHandler.java index 2805889..5c042af 100644 --- a/websocket/src/main/java/fi/iki/elonen/WebSocketResponseHandler.java +++ b/websocket/src/main/java/fi/iki/elonen/WebSocketResponseHandler.java @@ -19,38 +19,40 @@ public class WebSocketResponseHandler { public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; public final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - private final WebSocketFactory webSocketFactory; - public WebSocketResponseHandler(WebSocketFactory webSocketFactory) { - super(); + private final IWebSocketFactory webSocketFactory; + + public WebSocketResponseHandler(IWebSocketFactory webSocketFactory) { this.webSocketFactory = webSocketFactory; } - + public Response serve(final IHTTPSession session) { Map<String, String> headers = session.getHeaders(); if (isWebsocketRequested(session)) { - if (!HEADER_UPGRADE_VALUE.equalsIgnoreCase(headers.get(HEADER_UPGRADE)) - || !isWebSocketConnectionHeader(session.getHeaders())) { - return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket handshake"); - } if (!HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(HEADER_WEBSOCKET_VERSION))) { - return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); + return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, + "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); } + if (!headers.containsKey(HEADER_WEBSOCKET_KEY)) { - return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); + return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, + "Missing Websocket-Key"); } WebSocket webSocket = webSocketFactory.openWebSocket(session); + Response handshakeResponse = webSocket.getHandshakeResponse(); try { - webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); + handshakeResponse.addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); } catch (NoSuchAlgorithmException e) { - return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); + return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, + "The SHA-1 Algorithm required for websockets is not available on the server."); } + if (headers.containsKey(HEADER_WEBSOCKET_PROTOCOL)) { - webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_PROTOCOL, headers.get(HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); + handshakeResponse.addHeader(HEADER_WEBSOCKET_PROTOCOL, headers.get(HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); } - return webSocket.getHandshakeResponse(); + + return handshakeResponse; } else { return null; } @@ -77,7 +79,6 @@ public class WebSocketResponseHandler { return encodeBase64(sha1hash); } - private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); /** diff --git a/websocket/src/test/java/fi/iki/elonen/DebugWebSocket.java b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocket.java index e6c66ad..badd7ee 100644 --- a/websocket/src/test/java/fi/iki/elonen/DebugWebSocket.java +++ b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocket.java @@ -1,4 +1,8 @@ -package fi.iki.elonen; +package fi.iki.elonen.samples.echo; + +import fi.iki.elonen.NanoHTTPD; +import fi.iki.elonen.WebSocket; +import fi.iki.elonen.WebSocketFrame; import java.io.IOException; @@ -7,16 +11,16 @@ import java.io.IOException; * On: 4/23/14 at 10:34 PM */ class DebugWebSocket extends WebSocket { - private final boolean DEBUG; + private final boolean debug; public DebugWebSocket(NanoHTTPD.IHTTPSession handshake, boolean debug) { super(handshake); - DEBUG = debug; + this.debug = debug; } @Override protected void onPong(WebSocketFrame pongFrame) { - if (DEBUG) { + if (debug) { System.out.println("P " + pongFrame); } } @@ -33,8 +37,10 @@ class DebugWebSocket extends WebSocket { @Override protected void onClose(WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { - if (DEBUG) { - System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + (code != null ? code : "UnknownCloseCode[" + code + "]") + (reason != null && !reason.isEmpty() ? ": " + reason : "")); + if (debug) { + System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + + (code != null ? code : "UnknownCloseCode[" + code + "]") + + (reason != null && !reason.isEmpty() ? ": " + reason : "")); } } @@ -45,7 +51,7 @@ class DebugWebSocket extends WebSocket { @Override protected void handleWebsocketFrame(WebSocketFrame frame) throws IOException { - if (DEBUG) { + if (debug) { System.out.println("R " + frame); } super.handleWebsocketFrame(frame); @@ -53,7 +59,7 @@ class DebugWebSocket extends WebSocket { @Override public synchronized void sendFrame(WebSocketFrame frame) throws IOException { - if (DEBUG) { + if (debug) { System.out.println("S " + frame); } super.sendFrame(frame); diff --git a/websocket/src/test/java/fi/iki/elonen/DebugWebSocketServer.java b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java index 1f39be1..5faf72a 100644 --- a/websocket/src/test/java/fi/iki/elonen/DebugWebSocketServer.java +++ b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java @@ -1,4 +1,7 @@ -package fi.iki.elonen; +package fi.iki.elonen.samples.echo; + +import fi.iki.elonen.NanoWebSocketServer; +import fi.iki.elonen.WebSocket; /** * @author Paul S. Hawke (paul.hawke@gmail.com) diff --git a/websocket/src/test/java/fi/iki/elonen/EchoSocketSample.java b/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java index 091dfc0..b28a42c 100644 --- a/websocket/src/test/java/fi/iki/elonen/EchoSocketSample.java +++ b/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java @@ -1,4 +1,6 @@ -package fi.iki.elonen; +package fi.iki.elonen.samples.echo; + +import fi.iki.elonen.NanoWebSocketServer; import java.io.IOException; diff --git a/websocket/src/test/java/fi/iki/elonen/NanoWebSocketServerTest.java b/websocket/src/test/java/fi/iki/elonen/NanoWebSocketServerTest.java new file mode 100644 index 0000000..7cd06b7 --- /dev/null +++ b/websocket/src/test/java/fi/iki/elonen/NanoWebSocketServerTest.java @@ -0,0 +1,41 @@ +package fi.iki.elonen; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class NanoWebSocketServerTest { + @Mock + private NanoHTTPD.IHTTPSession session; + + private NanoWebSocketServer server; + + @Before + public void setUp() { + server = new NanoWebSocketServer(9090); + } + + @Test(expected = Error.class) + public void testMissingResponseFactoryThrowsErrorOnServe() { + server.openWebSocket(session); + } + + @Test + public void testMissingResponseFactoryThrowsErrorWithCorrectMessageOnServe() { + NanoWebSocketServer server = new NanoWebSocketServer(9090); + try { + server.openWebSocket(session); + } catch (Error e) { + assertEquals(NanoWebSocketServer.MISSING_FACTORY_MESSAGE, e.getMessage()); + } + } +}
\ No newline at end of file diff --git a/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java b/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java index c007fde..04a497b 100644 --- a/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java +++ b/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java @@ -1,97 +1,120 @@ package fi.iki.elonen; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; import org.junit.Test; import fi.iki.elonen.NanoHTTPD.IHTTPSession; import fi.iki.elonen.NanoHTTPD.Response; -import fi.iki.elonen.WebSocketFrame.CloseCode; -import fi.iki.elonen.testutil.MockHttpSession; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + import static junit.framework.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class WebSocketResponseHandlerTest { - private WebSocketResponseHandler responseHandler = new WebSocketResponseHandler(new DummyWebSocketFactory( - new WebSocketAdapter(new MockHttpSession()))); - + + @Mock + private IHTTPSession session; + @Mock + private WebSocket webSocket; + @Mock + private IWebSocketFactory webSocketFactory; + @Mock + private Response response; + @Captor + private ArgumentCaptor<String> headerNameCaptor; + @Captor + private ArgumentCaptor<String> headerCaptor; + + private Map<String, String> headers; + + private WebSocketResponseHandler responseHandler; + + @Before + public void setUp() { + headers = new HashMap<String, String>(); + headers.put("upgrade", "websocket"); + headers.put("connection", "Upgrade"); + headers.put("sec-websocket-key", "x3JJHMbDL1EzLkh9GBhXDw=="); + headers.put("sec-websocket-protocol", "chat, superchat"); + headers.put("sec-websocket-version", "13"); + + when(session.getHeaders()).thenReturn(headers); + when(webSocketFactory.openWebSocket(any(IHTTPSession.class))).thenReturn(webSocket); + when(webSocket.getHandshakeResponse()).thenReturn(response); + + responseHandler = new WebSocketResponseHandler(webSocketFactory); + } + + @Test + public void testHandshakeReturnsResponseWithExpectedHeaders() { + Response handshakeResponse = responseHandler.serve(session); + + verify(webSocket).getHandshakeResponse(); + assertNotNull(handshakeResponse); + assertSame(response, handshakeResponse); + + verify(response, atLeast(1)).addHeader(headerNameCaptor.capture(), headerCaptor.capture()); + assertHeader(0, "sec-websocket-accept", "HSmrc0sMlYUkAGmm5OPpG2HaGWk="); + assertHeader(1, "sec-websocket-protocol", "chat"); + } + @Test - public void testHandshake_returnsExpectedHeaders() { - MockHttpSession session = createWebSocketHandshakeRequest(); + public void testWrongWebsocketVersionReturnsErrorResponse() { + headers.put("sec-websocket-version", "12"); Response handshakeResponse = responseHandler.serve(session); - + assertNotNull(handshakeResponse); - assertEquals(101, handshakeResponse.getStatus().getRequestStatus()); - assertEquals("101 Switching Protocols", handshakeResponse.getStatus().getDescription()); - assertEquals("websocket", handshakeResponse.getHeader("upgrade")); - assertEquals("Upgrade", handshakeResponse.getHeader("connection")); - assertEquals("HSmrc0sMlYUkAGmm5OPpG2HaGWk=", handshakeResponse.getHeader("sec-websocket-accept")); - assertEquals("chat", handshakeResponse.getHeader("sec-websocket-protocol")); + assertEquals(Response.Status.BAD_REQUEST, handshakeResponse.getStatus()); } - + @Test - public void testWrongWebsocketVersion_returnsErrorResponse() { - MockHttpSession session = createWebSocketHandshakeRequest(); - session.getHeaders().put("sec-websocket-version", "12"); + public void testMissingKeyReturnsErrorResponse() { + headers.remove("sec-websocket-key"); Response handshakeResponse = responseHandler.serve(session); - + assertNotNull(handshakeResponse); - assertEquals(400, handshakeResponse.getStatus().getRequestStatus()); - assertEquals("400 Bad Request", handshakeResponse.getStatus().getDescription()); + assertEquals(Response.Status.BAD_REQUEST, handshakeResponse.getStatus()); } - private MockHttpSession createWebSocketHandshakeRequest() { - // Example headers copied from Wikipedia - MockHttpSession session = new MockHttpSession(); - session.getHeaders().put("upgrade", "websocket"); - session.getHeaders().put("connection", "Upgrade"); - session.getHeaders().put("sec-websocket-key", "x3JJHMbDL1EzLkh9GBhXDw=="); - session.getHeaders().put("sec-websocket-protocol", "chat, superchat"); - session.getHeaders().put("sec-websocket-version", "13"); - return session; + @Test + public void testWrongUpgradeHeaderReturnsNullResponse() { + headers.put("upgrade", "not a websocket"); + Response handshakeResponse = responseHandler.serve(session); + assertNull(handshakeResponse); } - - private static class DummyWebSocketFactory implements WebSocketFactory { - private final WebSocket webSocket; - - private DummyWebSocketFactory(WebSocket webSocket) { - super(); - this.webSocket = webSocket; - } - - @Override - public WebSocket openWebSocket(IHTTPSession handshake) { - return webSocket; - } + + @Test + public void testWrongConnectionHeaderReturnsNullResponse() { + headers.put("connection", "Junk"); + Response handshakeResponse = responseHandler.serve(session); + assertNull(handshakeResponse); } - - private static class WebSocketAdapter extends WebSocket { - - public WebSocketAdapter(IHTTPSession handshakeRequest) { - super(handshakeRequest); - } - - @Override - protected void onPong(WebSocketFrame pongFrame) { - throw new Error("this method should not have been called"); - } - - @Override - protected void onMessage(WebSocketFrame messageFrame) { - throw new Error("this method should not have been called"); - } - - @Override - protected void onClose(CloseCode code, String reason, - boolean initiatedByRemote) { - throw new Error("this method should not have been called"); - } - - @Override - protected void onException(IOException e) { - throw new Error("this method should not have been called"); - } - + + @Test + public void testConnectionHeaderHandlesKeepAlive_FixingFirefoxConnectIssue() { + headers.put("connection", "keep-alive, Upgrade"); + Response handshakeResponse = responseHandler.serve(session); + + verify(webSocket).getHandshakeResponse(); + assertNotNull(handshakeResponse); + assertSame(response, handshakeResponse); + } + + private void assertHeader(int index, String name, String value) { + assertEquals(name, headerNameCaptor.getAllValues().get(index)); + assertEquals(value, headerCaptor.getAllValues().get(index)); } } diff --git a/websocket/src/test/java/fi/iki/elonen/testutil/MockHttpSession.java b/websocket/src/test/java/fi/iki/elonen/testutil/MockHttpSession.java deleted file mode 100644 index 8d82218..0000000 --- a/websocket/src/test/java/fi/iki/elonen/testutil/MockHttpSession.java +++ /dev/null @@ -1,64 +0,0 @@ -package fi.iki.elonen.testutil; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import fi.iki.elonen.NanoHTTPD.CookieHandler; -import fi.iki.elonen.NanoHTTPD.IHTTPSession; -import fi.iki.elonen.NanoHTTPD.Method; -import fi.iki.elonen.NanoHTTPD.ResponseException; - -public class MockHttpSession implements IHTTPSession { - - private Map<String, String> params = new HashMap<String, String>(); - private Map<String, String> headers = new HashMap<String, String>(); - - @Override - public void execute() throws IOException { - - } - - @Override - public Map<String, String> getParms() { - return params; - } - - @Override - public Map<String, String> getHeaders() { - return headers; - } - - @Override - public String getUri() { - return null; - } - - @Override - public String getQueryParameterString() { - return null; - } - - @Override - public Method getMethod() { - return null; - } - - @Override - public InputStream getInputStream() { - return null; - } - - @Override - public CookieHandler getCookies() { - return null; - } - - @Override - public void parseBody(Map<String, String> files) throws IOException, - ResponseException { - - } - -} |