/**
 * Copyright 2007-2016, Kaazing Corporation. All rights reserved.
 *
 * 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.kaazing.gateway.transport.http.bridge.filter;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.jboss.netty.util.CharsetUtil.UTF_8;
import static org.kaazing.gateway.transport.http.HttpMethod.GET;
import static org.kaazing.gateway.transport.http.HttpMethod.POST;
import static org.kaazing.gateway.transport.http.HttpStatus.CLIENT_NOT_FOUND;
import static org.kaazing.gateway.transport.http.HttpStatus.CLIENT_UNAUTHORIZED;
import static org.kaazing.gateway.transport.http.HttpStatus.REDIRECT_FOUND;
import static org.kaazing.gateway.transport.http.HttpStatus.REDIRECT_MULTIPLE_CHOICES;
import static org.kaazing.gateway.transport.http.HttpStatus.REDIRECT_NOT_MODIFIED;
import static org.kaazing.gateway.transport.http.HttpStatus.SUCCESS_OK;
import static org.kaazing.gateway.transport.http.HttpVersion.HTTP_1_0;
import static org.kaazing.gateway.transport.http.HttpVersion.HTTP_1_1;
import static org.kaazing.gateway.transport.http.bridge.HttpContentMessage.EMPTY;
import static org.kaazing.gateway.util.Utils.join;

import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.apache.mina.core.filterchain.IoFilter.NextFilter;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.write.DefaultWriteRequest;
import org.apache.mina.filter.codec.ProtocolCodecException;
import org.junit.Test;
import org.kaazing.gateway.transport.http.DefaultHttpCookie;
import org.kaazing.gateway.transport.http.HttpAcceptSession;
import org.kaazing.gateway.transport.http.HttpConnectSession;
import org.kaazing.gateway.transport.http.HttpCookie;
import org.kaazing.gateway.transport.http.HttpHeaders;
import org.kaazing.gateway.transport.http.HttpStatus;
import org.kaazing.gateway.transport.http.bridge.HttpContentMessage;
import org.kaazing.gateway.transport.http.bridge.HttpRequestMessage;
import org.kaazing.gateway.transport.http.bridge.HttpResponseMessage;
import org.kaazing.gateway.transport.test.Expectations;
import org.kaazing.test.util.Mockery;
import org.kaazing.mina.core.buffer.IoBufferEx;
import org.kaazing.mina.core.buffer.SimpleBufferAllocator;

public class HttpxeProtocolFilterTest {

    private Mockery context = new Mockery();
    private HttpAcceptSession serverSession = context.mock(HttpAcceptSession.class);
    private HttpConnectSession clientSession = context.mock(HttpConnectSession.class);
    @SuppressWarnings("unchecked")
    private Set<HttpCookie> writeCookies = context.mock(Set.class);
    private IoFilterChain filterChain = context.mock(IoFilterChain.class);
    private NextFilter nextFilter = context.mock(NextFilter.class);

    @Test
    public void shouldWriteResponseWithInsertedStatusNotFound() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(CLIENT_NOT_FOUND);

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(CLIENT_NOT_FOUND);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue(null));
            oneOf(serverSession).setWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL, "no-cache");
            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(CLIENT_NOT_FOUND);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithoutInsertingStatusClientUnauthorized() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(CLIENT_UNAUTHORIZED);
        expectedResponse.setHeader("WWW-Authenticate", "Basic realm=\"WallyWorld\"");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue(null));
            oneOf(serverSession).setWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL, "no-cache");

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(CLIENT_UNAUTHORIZED);
        httpResponse.setHeader("WWW-Authenticate", "Basic realm=\"WallyWorld\"");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithoutInsertingStatusRedirectFound() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(REDIRECT_FOUND);
        expectedResponse.setHeader("Location", "https://www.w3.org/");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue(null));
            oneOf(serverSession).setWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL, "no-cache");
            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(REDIRECT_FOUND);
        httpResponse.setHeader("Location", "https://www.w3.org/");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }


    private static HttpBufferAllocator httpBufferAllocator = new HttpBufferAllocator(SimpleBufferAllocator.BUFFER_ALLOCATOR);

    private static IoBufferEx wrap(byte[] array) {
        return httpBufferAllocator.wrap(ByteBuffer.wrap(array));
    }

    @Test
    public void shouldWriteResponseWithoutInsertingStatusRedirectMultipleChoices() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(REDIRECT_MULTIPLE_CHOICES);
        expectedResponse.setHeader("Location", "https://www.w3.org/");

        String[] alternatives = new String[] { "https://www.w3.org/", "http://www.w3.org/" };
        byte[] array = join(alternatives, "\n").getBytes(UTF_8);
        final HttpContentMessage expectedContent = new HttpContentMessage(wrap(array), true);
        expectedResponse.setContent(expectedContent);

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue(null));
            oneOf(serverSession).setWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL, "no-cache");

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(REDIRECT_MULTIPLE_CHOICES);
        httpResponse.setHeader("Location", "https://www.w3.org/");

        HttpContentMessage httpContent = new HttpContentMessage(wrap(array), true);
        httpResponse.setContent(httpContent);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedCookies() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(REDIRECT_FOUND);
        expectedResponse.setReason("Cross-Origin Redirect");
        expectedResponse.setHeader("Location", "https://www.w3.org/");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue(null));
            oneOf(serverSession).setWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL, "no-cache");
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Set-Cookie")), with(stringListMatching("KSSOID=12345;")));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(REDIRECT_FOUND);
        httpResponse.setReason("Cross-Origin Redirect");
        httpResponse.setHeader("Location", "https://www.w3.org/");
        httpResponse.setHeader("Set-Cookie", "KSSOID=12345;");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedTextPlainContentType() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/plain");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/plain");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithTextContentTypeInsertedAsTextPlain() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=windows-1252");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedAccessControlAllowHeaders() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=windows-1252");
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Access-Control-Allow-Headers")), with(stringListMatching("x-websocket-extensions")));
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");
        httpResponse.setHeader("Access-Control-Allow-Headers", "x-websocket-extensions");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedContentEncoding() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=windows-1252");
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Content-Encoding")), with(stringListMatching("gzip")));
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/xyz;charset=windows-1252");
        httpResponse.setHeader("Content-Encoding", "gzip");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedCacheControl() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(REDIRECT_FOUND);
        expectedResponse.setReason("Cross-Origin Redirect");
        expectedResponse.setHeader("Location", "https://www.w3.org/");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Cache-Control")), with(stringListMatching("private")));
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));
            oneOf(serverSession).getWriteHeader(HttpHeaders.HEADER_CACHE_CONTROL); will(returnValue("private"));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(HttpStatus.REDIRECT_FOUND);
        httpResponse.setReason("Cross-Origin Redirect");
        httpResponse.setHeader("Cache-Control", "private");
        httpResponse.setHeader("Location", "https://www.w3.org/");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedContentTypeApplicationOctetStream() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "application/octet-stream");

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "application/octet-stream");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "application/octet-stream");

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithIncompleteContent() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");

        byte[] array = "Hello, world".getBytes(UTF_8);
        HttpContentMessage expectedContent = new HttpContentMessage(wrap(array), false);
        expectedResponse.setContent(expectedContent);

        context.checking(new Expectations() { {
            allowing(serverSession).getWriteHeader("Content-Length"); will(returnValue(null));
            allowing(serverSession).setWriteHeader("Content-Length", "0");

            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");

        HttpContentMessage httpContent = new HttpContentMessage(wrap(array), false);
        httpResponse.setContent(httpContent);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithInsertedTransferEncodingChunked() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");

        byte[] array = "Hello, world".getBytes(UTF_8);
        HttpContentMessage expectedContent = new HttpContentMessage(wrap(array), false, true, false);
        expectedResponse.setContent(expectedContent);

        context.checking(new Expectations() { {
            allowing(serverSession).getWriteHeader("Content-Length"); will(returnValue(null));
            allowing(serverSession).setWriteHeader("Content-Length", "0");

            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Transfer-Encoding")), with(stringListMatching("chunked")));
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");
        httpResponse.setHeader("Transfer-Encoding", "chunked");

        HttpContentMessage httpContent = new HttpContentMessage(wrap(array), false, true, false);
        httpResponse.setContent(httpContent);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseWithCompleteContent() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");

        byte[] array = "Hello, world".getBytes(UTF_8);
        HttpContentMessage expectedContent = new HttpContentMessage(wrap(array), true);
        expectedResponse.setContent(expectedContent);

        context.checking(new Expectations() { {
            allowing(serverSession).getWriteHeader("Content-Length"); will(returnValue(null));
            allowing(serverSession).setWriteHeader("Content-Length", "0");

            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Type", "text/plain;charset=UTF-8");

        HttpContentMessage httpContent = new HttpContentMessage(wrap(array), true);
        httpResponse.setContent(httpContent);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }

    @Test
    public void shouldWriteResponseAfterPrependingContentLengthFilter() throws Exception {

        final HttpResponseMessage expectedResponse = new HttpResponseMessage();
        expectedResponse.setVersion(HTTP_1_1);
        expectedResponse.setStatus(SUCCESS_OK);
        expectedResponse.setContent(new HttpContentMessage(wrap(new byte[0]), false));

        byte[] array = "Hello, world".getBytes(UTF_8);
        final HttpContentMessage expectedContent = new HttpContentMessage(wrap(array), true);
        expectedResponse.setContent(expectedContent);

        context.checking(new Expectations() { {
            oneOf(serverSession).setVersion(HTTP_1_1);
            oneOf(serverSession).setStatus(SUCCESS_OK);
            oneOf(serverSession).setWriteHeader("Content-Type", "text/plain;charset=UTF-8");
            oneOf(serverSession).setWriteHeaders(with(stringMatching("Content-Length")), with(stringListMatching("12")));
            allowing(serverSession).getWriteHeader("Content-Length"); will(returnValue("12"));
            oneOf(serverSession).getStatus(); will(returnValue(SUCCESS_OK));

            oneOf(serverSession).getFilterChain(); will(returnValue(filterChain));
            oneOf(filterChain).addFirst(with(equal("http#content-length")), with(aNonNull(HttpContentLengthAdjustmentFilter.class)));
            oneOf(nextFilter).filterWrite(with(serverSession), with(hasMessage(expectedResponse)));
        } });

        HttpResponseMessage httpResponse = new HttpResponseMessage();
        httpResponse.setVersion(HTTP_1_1);
        httpResponse.setStatus(SUCCESS_OK);
        httpResponse.setHeader("Content-Length", "12");

        HttpContentMessage httpContent = new HttpContentMessage(wrap(array), true);
        httpResponse.setContent(httpContent);

        HttpxeProtocolFilter filter = new HttpxeProtocolFilter(false);
        filter.filterWrite(nextFilter, serverSession, new DefaultWriteRequest(httpResponse));

        context.assertIsSatisfied();
    }


    @Test
    public void shouldReceivePostRequestWithExtractedHeadersAndContent() throws Exception {

        byte[] array = ">|<".getBytes(UTF_8);

        final HttpRequestMessage expectedRequest = new HttpRequestMessage();
        expectedRequest.setVersion(HTTP_1_1);
        expectedRequest.setMethod(POST);
        expectedRequest.setRequestURI(URI.create("/kerberos5/;api/get-cookies?.kv=10.05"));
        expectedRequest.setHeader("Accept", "*/*");
        expectedRequest.setHeader("Accept-Language", "en-us");
        expectedRequest.addHeader("Accept-Language", "fr-fr");
        expectedRequest.setHeader("Content-Length", "3");
        expectedRequest.setHeader("Content-Type", "text/plain");
        expectedRequest.setHeader("Host", "gateway.kzng.net:8003");
        expectedRequest.setHeader("Referer", "http://gateway.kzng.net:8003/?.kr=xsa");
        expectedRequest.setHeader("User-Agent", "Shockwave Flash");
        expectedRequest.setHeader("X-Origin", "http://gateway.kzng.net:8000");
        expectedRequest.setHeader("x-flash-version", "9,0,124,0");
        expectedRequest.setContent(new HttpContentMessage(wrap(array), true));

        context.checking(new Expectations() { {
            oneOf(serverSession).getVersion(); will(returnValue(HTTP_1_1));
            oneOf(serverSession).getMethod(); will(returnValue(POST));
            oneOf(serverSession).getRequestURI(); will(returnValue(URI.create("/kerberos5/;api/get-cookies?.kv=10.05")));
            oneOf(serverSession).getReadHeaderNames(); will(returnValue(asList("Accept", "Accept-Language", "Content-Length", "Content-Type", "Host", "X-Origin", "Referer", "User-Agent", "x-flash-version")));
            oneOf(serverSession).getReadHeaders(with("Accept")); will(returnValue(Collections.singletonList("*/*")));
            oneOf(serverSession).getReadHeaders(with("Accept-Language")); will(returnValue(asList("en-us","fr-fr")));
            oneOf(serverSession).getReadHeader(with("Content-Type")); will(returnValue("application/x-message-http"));
            oneOf(serverSession).getReadHeaders(with("Host")); will(returnValue(Collections.singletonList("gateway.kzng.net:8003")));
            oneOf(serverSession).getReadHeaders(with("Referer")); will(returnValue(Collections.singletonList("http://gateway.kzng.net:8003/?.kr=xsa")));
            oneOf(serverSession).getReadHeaders(with("User-Agent")); will(returnValue(Collections.singletonList("Shockwave Flash")));
            oneOf(serverSession).getReadHeaders(with("X-Origin")); will(returnValue(Collections.singletonList("http://gateway.kzng.net:8000")));
            oneOf(serverSession).getReadHeaders(with("x-flash-version")); will(returnValue(Collections.singletonList("9,0,124,0")));
            oneOf(serverSession).getReadCookies(); will(returnValue(emptyList()));

            oneOf(nextFilter).messageReceived(with(serverSession), with(equal(expectedRequest)));
        } });

        HttpRequestMessage httpRequest = new HttpRequestMessage();
        httpRequest.setVersion(HTTP_1_1);
        httpRequest.setMethod(POST);
