org.restexpress.pipeline.DefaultRequestHandlerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.restexpress.pipeline.DefaultRequestHandlerTest.java

Source

/*
Copyright 2010, Strategic Gains, 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 org.restexpress.pipeline;

import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import org.junit.Before;
import org.junit.Test;
import org.restexpress.ContentType;
import org.restexpress.Format;
import org.restexpress.Request;
import org.restexpress.Response;
import org.restexpress.exception.BadRequestException;
import org.restexpress.response.DefaultHttpResponseWriter;
import org.restexpress.response.JsendResponseWrapper;
import org.restexpress.response.RawResponseWrapper;
import org.restexpress.response.StringBufferHttpResponseWriter;
import org.restexpress.route.RouteDeclaration;
import org.restexpress.route.RouteResolver;
import org.restexpress.serialization.NullSerializationProvider;
import org.restexpress.serialization.SerializationProvider;
import org.restexpress.serialization.json.JacksonJsonProcessor;
import org.restexpress.serialization.xml.XstreamXmlProcessor;
import org.restexpress.settings.RouteDefaults;

import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
 * @author toddf
 * @since Dec 15, 2010
 */
public class DefaultRequestHandlerTest {
    private DefaultRequestHandler messageHandler;
    private DummyObserver observer;
    private Channel channel;
    private ChannelPipeline pl;
    private StringBuffer responseBody;
    private Map<String, List<String>> responseHeaders;

    @Before
    public void initialize() throws Exception {
        SerializationProvider provider = new NullSerializationProvider();
        provider.add(new JacksonJsonProcessor(Format.JSON), new RawResponseWrapper());
        provider.add(new JacksonJsonProcessor(Format.WRAPPED_JSON), new JsendResponseWrapper());
        provider.add(new XstreamXmlProcessor(Format.XML), new JsendResponseWrapper());
        provider.alias("dated", Dated.class);
        provider.setDefaultFormat(Format.WRAPPED_JSON);

        DummyRoutes routes = new DummyRoutes();
        routes.defineRoutes();
        messageHandler = new DefaultRequestHandler(
                new RouteResolver(routes.createRouteMapping(new RouteDefaults())), provider,
                new DefaultHttpResponseWriter(), false);
        observer = new DummyObserver();
        messageHandler.addMessageObserver(observer);
        responseBody = new StringBuffer();
        responseHeaders = new HashMap<String, List<String>>();
        messageHandler.setResponseWriter(new StringBufferHttpResponseWriter(responseHeaders, responseBody));
        channel = new EmbeddedChannel(messageHandler);
        pl = channel.pipeline();
    }

    @Test
    public void shouldReturnTextPlainContentTypeByDefault() throws Exception {
        sendGetEvent("/unserializedDefault");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        //      System.out.println(responseBody.toString());
        assertEquals("should be text plain, here", responseBody.toString());
        assertTrue(responseHeaders.containsKey("Content-Type"));
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals("text/plain; charset=UTF-8", contentTypes.get(0));
    }

    @Test
    public void shouldAllowSettingOfContentType() throws Exception {
        sendGetEvent("/unserialized");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        //      System.out.println(responseBody.toString());
        assertEquals("<html><body>Some kinda wonderful!</body></html>", responseBody.toString());
        assertTrue(responseHeaders.containsKey("Content-Type"));
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals("text/html", contentTypes.get(0));
    }

    @Test
    public void shouldAllowSettingOfContentTypeViaHeader() throws Exception {
        sendGetEvent("/unserializedToo");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        //      System.out.println(responseBody.toString());
        assertEquals("<html><body>Wow! What a fabulous HTML body...</body></html>", responseBody.toString());
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals("text/html", contentTypes.get(0));
    }

    @Test
    public void shouldAllowSettingOfArbitraryBody() throws Exception {
        sendGetEvent("/setBodyAction.html");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        //      System.out.println(responseBody.toString());
        assertEquals("<html><body>Arbitrarily set HTML body...</body></html>", responseBody.toString());
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals(ContentType.HTML, contentTypes.get(0));
    }

    @Test
    public void shouldNotifyObserverOnSuccess() throws Exception {
        sendGetEvent("/foo");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertEquals("{\"code\":200,\"status\":\"success\"}", responseBody.toString());
    }

    @Test
    public void shouldUrlDecodeUrlParameters() throws Exception {
        sendGetEvent("/foo/Todd%7CFredrich%2Bwas%20here.json");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertEquals("\"Todd|Fredrich+was here\"", responseBody.toString());
    }

    @Test
    public void shouldNotifyObserverOnError() throws Exception {
        sendGetEvent("/bar");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getExceptionCount());
        assertEquals(0, observer.getSuccessCount());
        //      System.out.println(httpResponse.toString());
        assertEquals(
                "{\"code\":400,\"status\":\"error\",\"message\":\"foobar'd\",\"data\":\"BadRequestException\"}",
                responseBody.toString());
    }

    @Test
    public void shouldHandleNonDecodableValueInQueryString() throws Exception {
        sendGetEvent("/bar?value=%target");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getExceptionCount());
        assertEquals(0, observer.getSuccessCount());
        //      System.out.println(responseBody.toString());
        assertEquals(
                "{\"code\":400,\"status\":\"error\",\"message\":\"foobar'd\",\"data\":\"BadRequestException\"}",
                responseBody.toString());
    }

    @Test
    public void shouldHandleUrlDecodeErrorInFormat() throws Exception {
        sendGetEvent("/foo.%target");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getExceptionCount());
        assertEquals(0, observer.getSuccessCount());
        //      System.out.println(httpResponse.toString());
        String json = responseBody.toString();
        assertTrue(json.startsWith(
                "{\"code\":400,\"status\":\"error\",\"message\":\"Requested representation format not supported: %target. Supported formats: "));
        assertTrue(json.contains("json"));
        assertTrue(json.contains("wjson"));
        assertTrue(json.contains("xml"));
        assertTrue(json.endsWith("\",\"data\":\"BadRequestException\"}"));
    }

    @Test
    public void shouldThrowExceptionForErrorInFormat() throws Exception {
        sendGetEvent("/foo.unsupported");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getExceptionCount());
        assertEquals(0, observer.getSuccessCount());
        //      System.out.println(httpResponse.toString());
        String json = responseBody.toString();
        assertTrue(json.startsWith(
                "{\"code\":400,\"status\":\"error\",\"message\":\"Requested representation format not supported: unsupported. Supported formats: "));
        assertTrue(json.contains("json"));
        assertTrue(json.contains("wjson"));
        assertTrue(json.contains("xml"));
        assertTrue(json.endsWith("\",\"data\":\"BadRequestException\"}"));
    }

    @Test
    public void shouldHandleInvalidDecodeInQueryString() throws Exception {
        sendGetEvent("/foo?value=%target");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertEquals("{\"code\":200,\"status\":\"success\"}", responseBody.toString());
    }

    @Test
    public void shouldHandleUrlDecodeErrorInUrl() throws Exception {
        sendGetEvent("/%bar");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getExceptionCount());
        assertEquals(0, observer.getSuccessCount());
        //      System.out.println(httpResponse.toString());
        assertEquals(
                "{\"code\":404,\"status\":\"error\",\"message\":\"Unresolvable URL: http://null/%bar\",\"data\":\"NotFoundException\"}",
                responseBody.toString());
    }

    @Test
    public void shouldParseTimepointJson() {
        sendGetEvent("/date.wjson", "{\"at\":\"2010-12-17T120000Z\"}");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertEquals("{\"code\":200,\"status\":\"success\",\"data\":{\"at\":\"2010-12-17T12:00:00.000Z\"}}",
                responseBody.toString());
    }

    @Test
    public void shouldParseTimepointJsonUsingQueryString() {
        sendGetEvent("/date?format=wjson", "{\"at\":\"2010-12-17T120000Z\"}");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertEquals("{\"code\":200,\"status\":\"success\",\"data\":{\"at\":\"2010-12-17T12:00:00.000Z\"}}",
                responseBody.toString());
    }

    @Test
    public void shouldParseTimepointXml() {
        sendGetEvent("/date.xml",
                "<org.restexpress.pipeline.Dated><at>2010-12-17T120000Z</at></org.restexpress.pipeline.Dated>");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertTrue(responseBody.toString().startsWith("<response>"));
        assertTrue(responseBody.toString().contains("<code>200</code>"));
        assertTrue(responseBody.toString().contains("<data class=\"dated\">"));
        assertTrue(responseBody.toString().contains("<at>2010-12-17T12:00:00.000Z</at>"));
        assertTrue(responseBody.toString().contains("</data>"));
        assertTrue(responseBody.toString().endsWith("</response>"));
    }

    @Test
    public void shouldParseTimepointXmlUsingQueryString() {
        sendGetEvent("/date?format=xml", "<dated><at>2010-12-17T120000Z</at></dated>");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals(0, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertTrue(responseBody.toString().startsWith("<response>"));
        assertTrue(responseBody.toString().contains("<code>200</code>"));
        assertTrue(responseBody.toString().contains("<data class=\"dated\">"));
        assertTrue(responseBody.toString().contains("<at>2010-12-17T12:00:00.000Z</at>"));
        assertTrue(responseBody.toString().contains("</data>"));
        assertTrue(responseBody.toString().endsWith("</response>"));
    }

    @Test
    public void shouldThrowExceptionOnInvalidUrl() {
        sendGetEvent("/xyzt.xml");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(0, observer.getSuccessCount());
        assertEquals(1, observer.getExceptionCount());
        //      System.out.println(responseBody.toString());
        assertTrue(responseBody.toString().startsWith("<response>"));
        assertTrue(responseBody.toString().contains("<code>404</code>"));
        assertTrue(responseBody.toString().contains("<status>error</status>"));
        assertTrue(responseBody.toString().contains("<message>Unresolvable URL: http://null/xyzt.xml</message>"));
        assertTrue(responseBody.toString().endsWith("</response>"));
    }

    @Test
    public void shouldThrowExceptionOnInvalidUrlWithXmlFormatQueryString() {
        sendGetEvent("/xyzt?format=xml");
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(0, observer.getSuccessCount());
        assertEquals(1, observer.getExceptionCount());
        //      System.out.println(httpResponse.toString());
        assertTrue(responseBody.toString().startsWith("<response>"));
        assertTrue(responseBody.toString().contains("<code>404</code>"));
        assertTrue(responseBody.toString().contains("<status>error</status>"));
        assertTrue(responseBody.toString()
                .contains("<message>Unresolvable URL: http://null/xyzt?format=xml</message>"));
        assertTrue(responseBody.toString().endsWith("</response>"));
    }

    @Test
    public void shouldCallAllFinallyProcessors() {
        NoopPostprocessor p1 = new NoopPostprocessor();
        NoopPostprocessor p2 = new NoopPostprocessor();
        NoopPostprocessor p3 = new NoopPostprocessor();
        messageHandler.addPostprocessor(p1);
        messageHandler.addPostprocessor(p2);
        messageHandler.addPostprocessor(p3);
        messageHandler.addFinallyProcessor(p1);
        messageHandler.addFinallyProcessor(p2);
        messageHandler.addFinallyProcessor(p3);
        sendGetEvent("/foo");
        assertEquals(2, p1.getCallCount());
        assertEquals(2, p2.getCallCount());
        assertEquals(2, p3.getCallCount());
    }

    @Test
    public void shouldCallAllFinallyProcessorsOnRouteException() {
        NoopPostprocessor p1 = new NoopPostprocessor();
        NoopPostprocessor p2 = new NoopPostprocessor();
        NoopPostprocessor p3 = new NoopPostprocessor();
        messageHandler.addPostprocessor(p1);
        messageHandler.addPostprocessor(p2);
        messageHandler.addPostprocessor(p3);
        messageHandler.addFinallyProcessor(p1);
        messageHandler.addFinallyProcessor(p2);
        messageHandler.addFinallyProcessor(p3);
        sendGetEvent("/xyzt.html");
        assertEquals(1, p1.getCallCount());
        assertEquals(1, p2.getCallCount());
        assertEquals(1, p3.getCallCount());
    }

    @Test
    public void shouldCallAllFinallyProcessorsOnProcessorException() {
        NoopPostprocessor p1 = new ExceptionPostprocessor();
        NoopPostprocessor p2 = new ExceptionPostprocessor();
        NoopPostprocessor p3 = new ExceptionPostprocessor();
        messageHandler.addPostprocessor(p1); // this one throws the exception in Postprocessors
        messageHandler.addPostprocessor(p2); // not called
        messageHandler.addPostprocessor(p3); // not called
        messageHandler.addFinallyProcessor(p1);
        messageHandler.addFinallyProcessor(p2);
        messageHandler.addFinallyProcessor(p3);
        sendGetEvent("/foo");
        assertEquals(2, p1.getCallCount());
        assertEquals(1, p2.getCallCount());
        assertEquals(1, p3.getCallCount());
    }

    @Test
    public void shouldSetJSONContentType() throws Exception {
        sendGetEvent("/serializedString.json?returnValue=raw string");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertEquals("\"raw string\"", responseBody.toString());
        assertTrue(responseHeaders.containsKey("Content-Type"));
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals("application/json; charset=UTF-8", contentTypes.get(0));
    }

    @Test
    public void shouldSetJSONContentTypeOnNullReturn() throws Exception {
        sendGetEvent("/serializedString.json");
        assertEquals(0, observer.getExceptionCount());
        assertEquals(1, observer.getReceivedCount());
        assertEquals(1, observer.getCompleteCount());
        assertEquals(1, observer.getSuccessCount());
        assertTrue(responseHeaders.containsKey("Content-Type"));
        List<String> contentTypes = responseHeaders.get(HttpHeaders.Names.CONTENT_TYPE);
        assertEquals(1, contentTypes.size());
        assertEquals("application/json; charset=UTF-8", contentTypes.get(0));
        assertEquals("null", responseBody.toString());
    }

    private void sendGetEvent(String path) {
        try {
            pl.fireChannelRead(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path));
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }
    }

    private void sendGetEvent(String path, String body) {
        try {
            pl.fireChannelRead(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path,
                    Unpooled.copiedBuffer(body, Charset.defaultCharset())));
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }
    }

    public class DummyRoutes extends RouteDeclaration {
        private Object controller = new FooBarController();
        private RouteDefaults defaults = new RouteDefaults();

        public void defineRoutes() {
            uri("/foo.{format}", controller, defaults).action("fooAction", HttpMethod.GET);

            uri("/foo/{userPhrase}.{format}", controller, defaults).action("verifyUrlDecodedParameters",
                    HttpMethod.GET);

            uri("/bar.{format}", controller, defaults).action("barAction", HttpMethod.GET);

            uri("/date.{format}", controller, defaults).action("dateAction", HttpMethod.GET);

            uri("/unserializedDefault", controller, defaults).action("unserializedDefault", HttpMethod.GET);

            uri("/unserialized", controller, defaults).action("unserializedAction", HttpMethod.GET);

            uri("/unserializedToo", controller, defaults).action("contentHeaderAction", HttpMethod.GET);

            uri("/serializedString.{format}", controller, defaults).action("serializedStringAction",
                    HttpMethod.GET);

            uri("/setBodyAction.html", controller, defaults).action("setBodyAction", HttpMethod.GET)
                    .format(Format.HTML);
        }
    }

    public class FooBarController {
        public void fooAction(Request request, Response response) {
            // do nothing.
        }

        public String verifyUrlDecodedParameters(Request request, Response response) {
            return request.getHeader("userPhrase");
        }

        public void barAction(Request request, Response response) {
            throw new BadRequestException("foobar'd");
        }

        public Object dateAction(Request request, Response response) {
            return request.getBodyAs(Dated.class);
        }

        public String unserializedDefault(Request request, Response response) {
            response.noSerialization();
            return "should be text plain, here";
        }

        public String unserializedAction(Request request, Response response) {
            response.setContentType("text/html");
            response.noSerialization();
            return "<html><body>Some kinda wonderful!</body></html>";
        }

        public String serializedStringAction(Request request, Response response) {
            return request.getHeader("returnValue");
        }

        public String contentHeaderAction(Request request, Response response) {
            response.addHeader("Content-Type", "text/html");
            response.noSerialization();
            return "<html><body>Wow! What a fabulous HTML body...</body></html>";
        }

        public void setBodyAction(Request request, Response response) {
            response.setContentType(ContentType.HTML);
            response.noSerialization();
            response.setBody("<html><body>Arbitrarily set HTML body...</body></html>");
        }
    }

    public class DummyObserver extends MessageObserver {
        private int receivedCount = 0;
        private int exceptionCount = 0;
        private int successCount = 0;
        private int completeCount = 0;

        @Override
        protected void onReceived(Request request, Response response) {
            ++receivedCount;
        }

        @Override
        protected void onException(Throwable exception, Request request, Response response) {
            ++exceptionCount;
        }

        @Override
        protected void onSuccess(Request request, Response response) {
            ++successCount;
        }

        @Override
        protected void onComplete(Request request, Response response) {
            ++completeCount;
        }

        public int getReceivedCount() {
            return receivedCount;
        }

        public int getExceptionCount() {
            return exceptionCount;
        }

        public int getSuccessCount() {
            return successCount;
        }

        public int getCompleteCount() {
            return completeCount;
        }
    }

    private class NoopPostprocessor implements Postprocessor {
        private int callCount = 0;

        @Override
        public void process(Request request, Response response) {
            ++callCount;
        }

        public int getCallCount() {
            return callCount;
        }
    }

    private class ExceptionPostprocessor extends NoopPostprocessor {
        @Override
        public void process(Request request, Response response) {
            super.process(request, response);
            throw new RuntimeException("RuntimeException thrown...");
        }
    }
}