package org.mockserver.serialization;

import org.apache.commons.io.IOUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.junit.Test;
import org.mockserver.logging.MockServerLogger;
import org.mockserver.model.*;
import org.mockserver.serialization.model.BodyDTO;
import org.mockserver.serialization.model.BodyWithContentTypeDTO;
import org.mockserver.serialization.model.HttpRequestDTO;
import org.mockserver.serialization.model.StringBodyDTO;

import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;

import static com.jayway.jsonpath.internal.path.PathCompiler.fail;
import static java.nio.charset.StandardCharsets.UTF_8;
import static junit.framework.TestCase.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.mockserver.character.Character.NEW_LINE;
import static org.mockserver.file.FileReader.openStreamToFileFromClassPathOrPath;
import static org.mockserver.model.BinaryBody.binary;
import static org.mockserver.model.Cookie.cookie;
import static org.mockserver.model.Header.header;
import static org.mockserver.model.JsonBody.json;
import static org.mockserver.model.JsonPathBody.jsonPath;
import static org.mockserver.model.JsonSchemaBody.jsonSchema;
import static org.mockserver.model.NottableString.string;
import static org.mockserver.model.Parameter.param;
import static org.mockserver.model.ParameterBody.params;
import static org.mockserver.model.RegexBody.regex;
import static org.mockserver.model.StringBody.exact;
import static org.mockserver.model.XPathBody.xpath;
import static org.mockserver.model.XmlBody.xml;
import static org.mockserver.model.XmlSchemaBody.xmlSchema;
import static org.mockserver.validator.jsonschema.JsonSchemaValidator.OPEN_API_SPECIFICATION_URL;

/**
 * @author jamesdbloom
 */
public class HttpRequestSerializerIntegrationTest {

    @Test
    public void shouldValidateHeaderNameValid() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"headers\" : {" + NEW_LINE +
            "        \"someHea derName\" : [ \"someHeaderValue\" ]" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        throwCorrectError(requestBytes, "incorrect request json format for:" + NEW_LINE +
            "" + NEW_LINE +
            "  {" + NEW_LINE +
            "      \"headers\" : {" + NEW_LINE +
            "          \"someHea derName\" : [ \"someHeaderValue\" ]" + NEW_LINE +
            "      }" + NEW_LINE +
            "  }" + NEW_LINE +
            "" + NEW_LINE +
            " schema validation errors:" + NEW_LINE +
            "" + NEW_LINE +
            "  3 errors:" + NEW_LINE +
            "   - $.headers.someHea derName: is not defined in the schema and the schema does not allow additional properties" + NEW_LINE +
            "   - $.headers: invalid header format, the following are valid examples: " + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleRegexHeader\": [" + NEW_LINE +
            "               \"^some +regex$\"" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleNottedAndSimpleStringHeader\": [" + NEW_LINE +
            "               \"!notThisValue\", " + NEW_LINE +
            "               \"simpleStringMatch\"" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "    " + NEW_LINE +
            "    or:" + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleSchemaHeader\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"number\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleMultiSchemaHeader\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"pattern\": \"^some +regex$\"" + NEW_LINE +
            "               }, " + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"format\": \"ipv4\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "   - $.headers: should be valid to one and only one schema, but 0 are valid" + NEW_LINE +
            "  " + NEW_LINE +
            "  " + OPEN_API_SPECIFICATION_URL.replaceAll(NEW_LINE, NEW_LINE + "  "));
    }

    @Test
    public void shouldValidatePathParameterNameValid() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"pathParameters\" : {" + NEW_LINE +
            "        \"path_somePara meterName\" : [ \"path_someParameterValue\" ]" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        throwCorrectError(requestBytes, "incorrect request json format for:" + NEW_LINE +
            "" + NEW_LINE +
            "  {" + NEW_LINE +
            "      \"pathParameters\" : {" + NEW_LINE +
            "          \"path_somePara meterName\" : [ \"path_someParameterValue\" ]" + NEW_LINE +
            "      }" + NEW_LINE +
            "  }" + NEW_LINE +
            "" + NEW_LINE +
            " schema validation errors:" + NEW_LINE +
            "" + NEW_LINE +
            "  3 errors:" + NEW_LINE +
            "   - $.pathParameters.path_somePara meterName: is not defined in the schema and the schema does not allow additional properties" + NEW_LINE +
            "   - $.pathParameters: invalid parameter format, the following are valid examples: " + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleRegexParameter\": [" + NEW_LINE +
            "               \"^some +regex$\"" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleNottedAndSimpleStringParameter\": [" + NEW_LINE +
            "               \"!notThisValue\", " + NEW_LINE +
            "               \"simpleStringMatch\"" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "    " + NEW_LINE +
            "    or:" + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleSchemaParameter\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"number\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleMultiSchemaParameter\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"pattern\": \"^some +regex$\"" + NEW_LINE +
            "               }, " + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"format\": \"ipv4\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "   - $.pathParameters: should be valid to one and only one schema, but 0 are valid" + NEW_LINE +
            "  " + NEW_LINE +
            "  " + OPEN_API_SPECIFICATION_URL.replaceAll(NEW_LINE, NEW_LINE + "  "));
    }

    @Test
    public void shouldValidateQueryStringParameterNameValid() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"queryStringParameters\" : {" + NEW_LINE +
            "        \"somePara meterName\" : [ \"someParameterValue\" ]" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        throwCorrectError(requestBytes, "incorrect request json format for:" + NEW_LINE +
            "" + NEW_LINE +
            "  {" + NEW_LINE +
            "      \"queryStringParameters\" : {" + NEW_LINE +
            "          \"somePara meterName\" : [ \"someParameterValue\" ]" + NEW_LINE +
            "      }" + NEW_LINE +
            "  }" + NEW_LINE +
            "" + NEW_LINE +
            " schema validation errors:" + NEW_LINE +
            "" + NEW_LINE +
            "  3 errors:" + NEW_LINE +
            "   - $.queryStringParameters.somePara meterName: is not defined in the schema and the schema does not allow additional properties" + NEW_LINE +
            "   - $.queryStringParameters: invalid parameter format, the following are valid examples: " + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleRegexParameter\": [" + NEW_LINE +
            "               \"^some +regex$\"" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleNottedAndSimpleStringParameter\": [" + NEW_LINE +
            "               \"!notThisValue\", " + NEW_LINE +
            "               \"simpleStringMatch\"" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "    " + NEW_LINE +
            "    or:" + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleSchemaParameter\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"number\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ], " + NEW_LINE +
            "           \"exampleMultiSchemaParameter\": [" + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"pattern\": \"^some +regex$\"" + NEW_LINE +
            "               }, " + NEW_LINE +
            "               {" + NEW_LINE +
            "                   \"type\": \"string\", " + NEW_LINE +
            "                   \"format\": \"ipv4\"" + NEW_LINE +
            "               }" + NEW_LINE +
            "           ]" + NEW_LINE +
            "       }" + NEW_LINE +
            "   - $.queryStringParameters: should be valid to one and only one schema, but 0 are valid" + NEW_LINE +
            "  " + NEW_LINE +
            "  " + OPEN_API_SPECIFICATION_URL.replaceAll(NEW_LINE, NEW_LINE + "  "));
    }

    @Test
    public void shouldValidateCookieNameValid() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"cookies\" : {" + NEW_LINE +
            "        \"someCoo kieName\" : \"someCookieValue\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // then
        throwCorrectError(requestBytes, "incorrect request json format for:" + NEW_LINE +
            "" + NEW_LINE +
            "  {" + NEW_LINE +
            "      \"cookies\" : {" + NEW_LINE +
            "          \"someCoo kieName\" : \"someCookieValue\"" + NEW_LINE +
            "      }" + NEW_LINE +
            "  }" + NEW_LINE +
            "" + NEW_LINE +
            " schema validation errors:" + NEW_LINE +
            "" + NEW_LINE +
            "  3 errors:" + NEW_LINE +
            "   - $.cookies.someCoo kieName: is not defined in the schema and the schema does not allow additional properties" + NEW_LINE +
            "   - $.cookies: invalid cookie format, the following are valid examples: " + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleRegexCookie\": \"^some +regex$\", " + NEW_LINE +
            "           \"exampleNottedRegexCookie\": \"!notThisValue\", " + NEW_LINE +
            "           \"exampleSimpleStringCookie\": \"simpleStringMatch\"" + NEW_LINE +
            "       }" + NEW_LINE +
            "    " + NEW_LINE +
            "    or:" + NEW_LINE +
            "    " + NEW_LINE +
            "       {" + NEW_LINE +
            "           \"exampleNumberSchemaCookie\": {" + NEW_LINE +
            "               \"type\": \"number\"" + NEW_LINE +
            "           }, " + NEW_LINE +
            "           \"examplePatternSchemaCookie\": {" + NEW_LINE +
            "               \"type\": \"string\", " + NEW_LINE +
            "               \"pattern\": \"^some regex$\"" + NEW_LINE +
            "           }, " + NEW_LINE +
            "           \"exampleFormatSchemaCookie\": {" + NEW_LINE +
            "               \"type\": \"string\", " + NEW_LINE +
            "               \"format\": \"ipv4\"" + NEW_LINE +
            "           }" + NEW_LINE +
            "       }" + NEW_LINE +
            "   - $.cookies: should be valid to one and only one schema, but 0 are valid" + NEW_LINE +
            "  " + NEW_LINE +
            "  " + OPEN_API_SPECIFICATION_URL.replaceAll(NEW_LINE, NEW_LINE + "  "));
    }

    @Test
    public void shouldValidateExtraFields() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"path\": \"somePath\"," + NEW_LINE +
            "    \"extra_field\": \"extra_value\"" + NEW_LINE +
            "}";

        // then
        throwCorrectError(requestBytes, "incorrect request json format for:" + NEW_LINE +
            "" + NEW_LINE +
            "  {" + NEW_LINE +
            "      \"path\": \"somePath\"," + NEW_LINE +
            "      \"extra_field\": \"extra_value\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "" + NEW_LINE +
            " schema validation errors:" + NEW_LINE +
            "" + NEW_LINE +
            "  1 error:" + NEW_LINE +
            "   - $.extra_field: is not defined in the schema and the schema does not allow additional properties" + NEW_LINE +
            "  " + NEW_LINE +
            "  " + OPEN_API_SPECIFICATION_URL.replaceAll(NEW_LINE, NEW_LINE + "  "));
    }

    @Test
    public void shouldDeserializeCompleteObject() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"method\" : \"someMethod\"," + NEW_LINE +
            "  \"path\" : \"somePath\"," + NEW_LINE +
            "  \"secure\" : true," + NEW_LINE +
            "  \"keepAlive\" : false," + NEW_LINE +
            "  \"protocol\" : \"HTTP_2\"," + NEW_LINE +
            "  \"pathParameters\" : [ {" + NEW_LINE +
            "    \"name\" : \"pathParameterName\"," + NEW_LINE +
            "    \"values\" : [ \"pathParameterValue\" ]" + NEW_LINE +
            "  } ]," + NEW_LINE +
            "  \"queryStringParameters\" : [ {" + NEW_LINE +
            "    \"name\" : \"queryParameterName\"," + NEW_LINE +
            "    \"values\" : [ \"queryParameterValue\" ]" + NEW_LINE +
            "  } ]," + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"STRING\"," + NEW_LINE +
            "    \"string\" : \"someBody\"" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"cookies\" : [ {" + NEW_LINE +
            "    \"name\" : \"someCookieName\"," + NEW_LINE +
            "    \"value\" : \"someCookieValue\"" + NEW_LINE +
            "  } ]," + NEW_LINE +
            "  \"headers\" : [ {" + NEW_LINE +
            "    \"name\" : \"someHeaderName\"," + NEW_LINE +
            "    \"values\" : [ \"someHeaderValue\" ]" + NEW_LINE +
            "  } ]," + NEW_LINE +
            "  \"socketAddress\" : {" + NEW_LINE +
            "    \"host\" : \"someHost\"," + NEW_LINE +
            "    \"port\" : 1234," + NEW_LINE +
            "    \"scheme\" : \"HTTPS\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setMethod(string("someMethod"))
            .setPath(string("somePath"))
            .setPathParameters(new Parameters().withEntries(
                param("pathParameterName", "pathParameterValue")
            ))
            .setQueryStringParameters(new Parameters().withEntries(
                param("queryParameterName", "queryParameterValue")
            ))
            .setBody(new StringBodyDTO(exact("someBody")))
            .setHeaders(new Headers().withEntries(
                header("someHeaderName", "someHeaderValue")
            ))
            .setCookies(new Cookies().withEntries(
                cookie("someCookieName", "someCookieValue")
            ))
            .setSecure(true)
            .setKeepAlive(false)
            .setProtocol(Protocol.HTTP_2)
            .setSocketAddress(new SocketAddress()
                .withHost("someHost")
                .withPort(1234)
                .withScheme(SocketAddress.Scheme.HTTPS)
            )
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeHeaderValueWithSingleValue() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"headers\" : {" + NEW_LINE +
            "        \"someHeaderName\" : \"someHeaderValue\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequest()
            .withHeader("someHeaderName", "someHeaderValue"), httpRequest);
    }

    @Test
    public void shouldDeserializeQueryStringParameterValueWithSingleValue() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"queryStringParameters\" : {" + NEW_LINE +
            "        \"someParameterName\" : \"someParameterValue\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequest()
            .withQueryStringParameter("someParameterName", "someParameterValue"), httpRequest);
    }

    @Test
    public void shouldDeserializePathParameterValueWithSingleValue() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"pathParameters\" : {" + NEW_LINE +
            "        \"path_someParameterName\" : \"path_someParameterValue\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequest()
            .withPathParameter("path_someParameterName", "path_someParameterValue"), httpRequest);
    }

    @Test
    public void shouldDeserializeStringBodyShorthand() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : \"somebody\"" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(exact("somebody")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeStringBodyWithType() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"STRING\"," + NEW_LINE +
            "    \"string\" : \"somebody\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(exact("somebody")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeRegexBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"REGEX\"," + NEW_LINE +
            "    \"regex\" : \"some[a-z]{3}\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(regex("some[a-z]{3}")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeJsonBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"JSON\"," + NEW_LINE +
            "    \"json\" : {" + NEW_LINE +
            "      \"key\" : \"value\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        HttpRequest expected = new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(json("{" + NEW_LINE +
                "  \"key\" : \"value\"" + NEW_LINE +
                "}")))
            .buildObject();
        assertEquals(expected, httpRequest);
    }

    @Test
    public void shouldDeserializeJsonSchemaBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"JSON_SCHEMA\"," + NEW_LINE +
            "    \"jsonSchema\" : {" + NEW_LINE +
            "      \"key\" : \"value\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(jsonSchema("{" + NEW_LINE +
                "  \"key\" : \"value\"" + NEW_LINE +
                "}")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeJsonPathBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"JSON_PATH\"," + NEW_LINE +
            "    \"jsonPath\" : \"$..book[?(@.price <= $['expensive'])]\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(jsonPath("$..book[?(@.price <= $['expensive'])]")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeXmlBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XML\"," + NEW_LINE +
            "    \"xml\" : \"<some><xml></xml></some>\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(xml("<some><xml></xml></some>")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeXmlSchemaBody() {
        // given
        String xmlSchema = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">" +
            "    <!-- XML Schema Generated from XML Document on Wed Jun 28 2017 21:52:45 GMT+0100 (BST) -->" +
            "    <!-- with XmlGrid.net Free Online Service http://xmlgrid.net -->" +
            "    <xs:element name=\"notes\">" +
            "        <xs:complexType>" +
            "            <xs:sequence>" +
            "                <xs:element name=\"note\" maxOccurs=\"unbounded\">" +
            "                    <xs:complexType>" +
            "                        <xs:sequence>" +
            "                            <xs:element name=\"to\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"from\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"heading\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"body\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                        </xs:sequence>" +
            "                    </xs:complexType>" +
            "                </xs:element>" +
            "            </xs:sequence>" +
            "        </xs:complexType>" +
            "    </xs:element>" +
            "</xs:schema>";
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XML_SCHEMA\"," + NEW_LINE +
            "    \"xmlSchema\" : \"" + StringEscapeUtils.escapeJava(xmlSchema) + "\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";


        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(xmlSchema(xmlSchema)))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeXpathBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XPATH\"," + NEW_LINE +
            "    \"xpath\" : \"/element[key = 'some_key' and value = 'some_value']\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(xpath("/element[key = 'some_key' and value = 'some_value']")))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeParameterBody() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"PARAMETERS\"," + NEW_LINE +
            "    \"parameters\" : [ {" + NEW_LINE +
            "      \"name\" : \"nameOne\"," + NEW_LINE +
            "      \"values\" : [ \"valueOne\" ]" + NEW_LINE +
            "    }, {" + NEW_LINE +
            "      \"name\" : \"nameTwo\"," + NEW_LINE +
            "      \"values\" : [ \"valueTwo_One\", \"valueTwo_Two\" ]" + NEW_LINE +
            "    } ]" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyDTO.createDTO(params(
                new Parameter("nameOne", "valueOne"),
                new Parameter("nameTwo", "valueTwo_One", "valueTwo_Two")
            )))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeBinaryBody() throws IOException {
        // given
        String requestBytes = "{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"BINARY\"," + NEW_LINE +
            "    \"base64Bytes\" : \" \"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setBody(BodyWithContentTypeDTO.createWithContentTypeDTO(binary(IOUtils.toByteArray(openStreamToFileFromClassPathOrPath("org/mockserver/serialization/forkme_right_red.png")))))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializePartialObject() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"path\": \"somePath\"" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setPath(string("somePath"))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldDeserializeAsHttpRequestField() {
        // given
        String requestBytes = "{" + NEW_LINE +
            "    \"httpRequest\": {" + NEW_LINE +
            "        \"path\": \"somePath\"," + NEW_LINE +
            "        \"pathParameters\" : [ {" + NEW_LINE +
            "            \"name\" : \"pathParameterName\"," + NEW_LINE +
            "            \"values\" : [ \"pathParameterValue\" ]" + NEW_LINE +
            "        } ]," + NEW_LINE +
            "        \"queryStringParameters\" : [ {" + NEW_LINE +
            "            \"name\" : \"queryParameterName\"," + NEW_LINE +
            "            \"values\" : [ \"queryParameterValue\" ]" + NEW_LINE +
            "        } ]" + NEW_LINE +
            "    }" + NEW_LINE +
            "}";

        // when
        HttpRequest httpRequest = new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

        // then
        assertEquals(new HttpRequestDTO()
            .setPath(string("somePath"))
            .setPathParameters(new Parameters().withEntries(
                param("pathParameterName", "pathParameterValue")
            ))
            .setQueryStringParameters(new Parameters().withEntries(
                param("queryParameterName", "queryParameterValue")
            ))
            .buildObject(), httpRequest);
    }

    @Test
    public void shouldSerializeCompleteObject() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setMethod(string("someMethod"))
                .setPath(string("somePath"))
                .setPathParameters(new Parameters().withEntries(
                    param("pathParameterName", "pathParameterValue")
                ))
                .setQueryStringParameters(new Parameters().withEntries(
                    param("queryParameterName", "queryParameterValue")
                ))
                .setBody(new StringBodyDTO(exact("someBody")))
                .setHeaders(new Headers().withEntries(
                    header("headerName", "headerValue")
                ))
                .setCookies(new Cookies().withEntries(
                    cookie("cookieName", "cookieValue")
                ))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"method\" : \"someMethod\"," + NEW_LINE +
            "  \"path\" : \"somePath\"," + NEW_LINE +
            "  \"pathParameters\" : {" + NEW_LINE +
            "    \"pathParameterName\" : [ \"pathParameterValue\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"queryStringParameters\" : {" + NEW_LINE +
            "    \"queryParameterName\" : [ \"queryParameterValue\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"headers\" : {" + NEW_LINE +
            "    \"headerName\" : [ \"headerValue\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"cookies\" : {" + NEW_LINE +
            "    \"cookieName\" : \"cookieValue\"" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"body\" : \"someBody\"" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    @SuppressWarnings("RedundantArrayCreation")
    public void shouldSerializeArray() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequest[]{
                new HttpRequestDTO()
                    .setMethod(string("some_method_one"))
                    .setPath(string("some_path_one"))
                    .setBody(BodyDTO.createDTO(new StringBody("some_body_one")))
                    .setHeaders(new Headers().withEntries(
                        header("some_header_name_one", "some_header_value_one")
                    ))
                    .buildObject(),
                new HttpRequestDTO()
                    .setMethod(string("some_method_two"))
                    .setPath(string("some_path_two"))
                    .setBody(BodyDTO.createDTO(new StringBody("some_body_two")))
                    .setHeaders(new Headers().withEntries(
                        header("some_header_name_two", "some_header_value_two")
                    ))
                    .buildObject()
            }
        );

        // then
        assertEquals("[ {" + NEW_LINE +
            "  \"method\" : \"some_method_one\"," + NEW_LINE +
            "  \"path\" : \"some_path_one\"," + NEW_LINE +
            "  \"headers\" : {" + NEW_LINE +
            "    \"some_header_name_one\" : [ \"some_header_value_one\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"body\" : \"some_body_one\"" + NEW_LINE +
            "}, {" + NEW_LINE +
            "  \"method\" : \"some_method_two\"," + NEW_LINE +
            "  \"path\" : \"some_path_two\"," + NEW_LINE +
            "  \"headers\" : {" + NEW_LINE +
            "    \"some_header_name_two\" : [ \"some_header_value_two\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"body\" : \"some_body_two\"" + NEW_LINE +
            "} ]", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeList() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            Arrays.asList(
                new HttpRequestDTO()
                    .setMethod(string("some_method_one"))
                    .setPath(string("some_path_one"))
                    .setBody(BodyDTO.createDTO(new StringBody("some_body_one")))
                    .setHeaders(new Headers().withEntries(
                        header("some_header_name_one", "some_header_value_one")
                    ))
                    .buildObject(),
                new HttpRequestDTO()
                    .setMethod(string("some_method_two"))
                    .setPath(string("some_path_two"))
                    .setBody(BodyDTO.createDTO(new StringBody("some_body_two")))
                    .setHeaders(new Headers().withEntries(
                        header("some_header_name_two", "some_header_value_two")
                    ))
                    .buildObject()
            )
        );

        // then
        assertEquals("[ {" + NEW_LINE +
            "  \"method\" : \"some_method_one\"," + NEW_LINE +
            "  \"path\" : \"some_path_one\"," + NEW_LINE +
            "  \"headers\" : {" + NEW_LINE +
            "    \"some_header_name_one\" : [ \"some_header_value_one\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"body\" : \"some_body_one\"" + NEW_LINE +
            "}, {" + NEW_LINE +
            "  \"method\" : \"some_method_two\"," + NEW_LINE +
            "  \"path\" : \"some_path_two\"," + NEW_LINE +
            "  \"headers\" : {" + NEW_LINE +
            "    \"some_header_name_two\" : [ \"some_header_value_two\" ]" + NEW_LINE +
            "  }," + NEW_LINE +
            "  \"body\" : \"some_body_two\"" + NEW_LINE +
            "} ]", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeStringBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(exact("somebody")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : \"somebody\"" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeRegexBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(regex("some[a-z]{3}")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"REGEX\"," + NEW_LINE +
            "    \"regex\" : \"some[a-z]{3}\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeJsonBody() {
        // given
        String json = "{\"key\": \"value\"}";

        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(json(json)))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"key\" : \"value\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeJsonSchemaBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(jsonSchema("{ \"key\": \"value\" }")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"JSON_SCHEMA\"," + NEW_LINE +
            "    \"jsonSchema\" : {" + NEW_LINE +
            "      \"key\" : \"value\"" + NEW_LINE +
            "    }" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeJsonPathBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(jsonPath("$..book[?(@.price <= $['expensive'])]")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"JSON_PATH\"," + NEW_LINE +
            "    \"jsonPath\" : \"$..book[?(@.price <= $['expensive'])]\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeXmlBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(xml("<some><xml></xml></some>")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XML\"," + NEW_LINE +
            "    \"xml\" : \"<some><xml></xml></some>\"," + NEW_LINE +
            "    \"rawBytes\" : \"" + Base64.getEncoder().encodeToString("<some><xml></xml></some>".getBytes(UTF_8)) + "\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeXmlSchemaBody() {
        // given
        String xmlSchema = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
            "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\">" +
            "    <!-- XML Schema Generated from XML Document on Wed Jun 28 2017 21:52:45 GMT+0100 (BST) -->" +
            "    <!-- with XmlGrid.net Free Online Service http://xmlgrid.net -->" +
            "    <xs:element name=\"notes\">" +
            "        <xs:complexType>" +
            "            <xs:sequence>" +
            "                <xs:element name=\"note\" maxOccurs=\"unbounded\">" +
            "                    <xs:complexType>" +
            "                        <xs:sequence>" +
            "                            <xs:element name=\"to\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"from\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"heading\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                            <xs:element name=\"body\" minOccurs=\"1\" maxOccurs=\"1\" type=\"xs:string\"></xs:element>" +
            "                        </xs:sequence>" +
            "                    </xs:complexType>" +
            "                </xs:element>" +
            "            </xs:sequence>" +
            "        </xs:complexType>" +
            "    </xs:element>" +
            "</xs:schema>";

        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(xmlSchema(xmlSchema)))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XML_SCHEMA\"," + NEW_LINE +
            "    \"xmlSchema\" : \"" + StringEscapeUtils.escapeJava(xmlSchema) + "\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeXpathBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(xpath("/element[key = 'some_key' and value = 'some_value']")))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"XPATH\"," + NEW_LINE +
            "    \"xpath\" : \"/element[key = 'some_key' and value = 'some_value']\"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeParameterBody() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyDTO.createDTO(params(
                    new Parameter("nameOne", "valueOne"),
                    new Parameter("nameTwo", "valueTwo_One", "valueTwo_Two")
                )))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"PARAMETERS\"," + NEW_LINE +
            "    \"parameters\" : {" + NEW_LINE +
            "      \"nameTwo\" : [ \"valueTwo_One\", \"valueTwo_Two\" ]," + NEW_LINE +
            "      \"nameOne\" : [ \"valueOne\" ]" + NEW_LINE +
            "    }" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializeBinaryBody() throws IOException {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(
            new HttpRequestDTO()
                .setBody(BodyWithContentTypeDTO.createWithContentTypeDTO(binary(IOUtils.toByteArray(openStreamToFileFromClassPathOrPath("org/mockserver/serialization/forkme_right_red.png")))))
                .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"body\" : {" + NEW_LINE +
            "    \"type\" : \"BINARY\"," + NEW_LINE +
            "    \"base64Bytes\" : \" \"" + NEW_LINE +
            "  }" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    @Test
    public void shouldSerializePartialHttpRequest() {
        // when
        String jsonHttpRequest = new HttpRequestSerializer(new MockServerLogger()).serialize(new HttpRequestDTO()
            .setPath(string("somePath"))
            .buildObject()
        );

        // then
        assertEquals("{" + NEW_LINE +
            "  \"path\" : \"somePath\"" + NEW_LINE +
            "}", jsonHttpRequest);
    }

    private void throwCorrectError(String requestBytes, String errorMessage) {
        try {
            // when
            new HttpRequestSerializer(new MockServerLogger()).deserialize(requestBytes);

            // then
            fail("expected exception");
        } catch (IllegalArgumentException iae) {
            assertThat(iae.getMessage(), is(errorMessage));
        }
    }
}
