from datetime import datetime
from typing import List, Dict, Optional
from uuid import UUID

import pytest
import iso8601
from httpmessageprocessor import ApiRequest
from httpmessageprocessorutil.automapped import AutoMappedMessageProcessor, auto_map
from httpmessageprocessorutil.automapped.exception import BodyNotJson, PropertyError


class DummyInteractor(object):
    def execute(self, o):
        pass


class MyRequestNoType(object):
    def __init__(self, variable_name):
        self.variable_name = variable_name


class MyRequestIntType(object):
    def __init__(self, variable_name: int):
        self.variable_name = variable_name


class MyRequestBoolType(object):
    def __init__(self, variable_name: bool):
        self.variable_name = variable_name


class MyRequestStrType(object):
    def __init__(self, variable_name: str):
        self.variable_name = variable_name


class MyRequestDateTimeType(object):
    def __init__(self, variable_name: datetime):
        self.variable_name = variable_name


def create_post_request(body):
    return ApiRequest("POST", "/path", {}, {}, {}, body)

no_type_tests = [
    ({"variable_name": "23"}, MyRequestNoType, "23"),
    ({"variable_name": 23}, MyRequestNoType, 23),
    ({"variable_name": True}, MyRequestNoType, True),
    ({"variable_name": False}, MyRequestNoType, False)
]

valid_datetime_formats = [
    "2020-02-07T19:46:23.234Z",
    "2020-02-07T19:46:23.234-0300",
    "2020-02-07T19:46:23.234-03:00",
    "2020-02-07T19:46:23.234",
    "2020-02-07T19:46:23Z",
    "2020-02-07T19:46:23-0300",
    "2020-02-07T19:46:23-03:00",
    "2020-02-07T19:46:23",
    "2020-02-07 19:46:23.234Z",
    "2020-02-07 19:46:23.234-0300",
    "2020-02-07 19:46:23.234-03:00",
    "2020-02-07 19:46:23.234",
    "2020-02-07 19:46:23Z",
    "2020-02-07 19:46:23-0300",
    "2020-02-07 19:46:23-03:00",
    "2020-02-07 19:46:23"
]

with_type_tests = [
    ({"variable_name": "23"}, MyRequestIntType, 23),
    ({"variable_name": "true"}, MyRequestBoolType, True),
    ({"variable_name": "false"}, MyRequestBoolType, False),
    ({"variable_name": "True"}, MyRequestBoolType, True),
    ({"variable_name": "nothing"}, MyRequestBoolType, False),
    ({"variable_name": "23"}, MyRequestStrType, "23"),
]
for datetime_format in valid_datetime_formats:
    with_type_tests.append(({"variable_name": datetime_format},
                            MyRequestDateTimeType,
                            iso8601.parse_date(datetime_format)))


class TestAutoMappedMessageProcessorParseData37(object):
    @classmethod
    def setup_class(cls):
        d = iso8601.parse_date("2020-02-07")
        print(d)

    def test_NoneAsType_NoneIsReturned(self):
        api_request = ApiRequest("GET",
                                 "/path",
                                 {},
                                 {},
                                 {},
                                 b"")

        req = AutoMappedMessageProcessor(None, DummyInteractor()).parse_data(api_request, None)
        assert req is None

    class TestPathVariables(object):
        @pytest.mark.parametrize("query_string, cls, result", no_type_tests)
        def test_variable_found_in_query_without_typing(self, query_string, cls, result):
            api_request = ApiRequest("GET",
                                     "/path",
                                     {},
                                     query_string,
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(cls, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == result

        def test_variable_is_also_found_in_query_string_with_camel_case(self):
            api_request = ApiRequest("GET",
                                     "/path",
                                     {},
                                     {"variableName": "23"},
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "23"

        @pytest.mark.parametrize("query_string, cls, result", with_type_tests)
        def test_variable_found_in_query_string_with_typing(self, query_string, cls, result):
            api_request = ApiRequest("GET",
                                     "/path/",
                                     {},
                                     query_string,
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(cls, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == result

    class TestQueryStringVariables(object):
        @pytest.mark.parametrize("path_variable, cls, result", no_type_tests)
        def test_variable_found_in_path_without_typing(self, path_variable, cls, result):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     path_variable,
                                     {},
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(cls, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == result

        def test_variable_is_also_found_in_path_with_camel_case(self):
            api_request = ApiRequest("GET",
                                     "/path/{variableName}/",
                                     {"variableName": "23"},
                                     {},
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "23"

        @pytest.mark.parametrize("query_string, cls, result", with_type_tests)
        def test_variable_found_in_path_with_typing(self, query_string, cls, result):
            api_request = ApiRequest("GET",
                                     "/path/",
                                     {},
                                     query_string,
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(cls, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == result

        def test_variable_is_retrieved_from_path_if_in_path_and_query_string(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"variable_name": "23"},
                                     {"variable_name": "28"},
                                     {},
                                     b"")

            req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "23"

    class TestOverrideDefaultsWithAutoMap(object):
        def test_default_location_can_be_overridden(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"variable_name": "path"},
                                     {"variable_name": "query"},
                                     {},
                                     b"""{"variable_name": "body"}""")

            @auto_map({"variable_name": {"location": "query"}})
            class MyRequest(object):
                def __init__(self, variable_name):
                    self.variable_name = variable_name

            req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "query"

        def test_nothing_changes_if_default_location_is_specified(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"variable_name": "23"},
                                     {"variable_name": "28"},
                                     {},
                                     b"")

            @auto_map({"variable_name": {"location": "path"}})
            class MyRequest(object):
                def __init__(self, variable_name):
                    self.variable_name = variable_name

            req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "23"

        def test_type_can_be_overridden(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"variable_name": "23"},
                                     {},
                                     {},
                                     b"")

            @auto_map({"variable_name": {"type": int}})
            class MyRequest(object):
                def __init__(self, variable_name: str):
                    self.variable_name = variable_name

            req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == 23

        def test_name_can_be_overridden(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"strange_name": "23"},
                                     {},
                                     {},
                                     b"")

            @auto_map({"variable_name": {"name": "strange_name"}})
            class MyRequest(object):
                def __init__(self, variable_name: str):
                    self.variable_name = variable_name

            req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == "23"

        def test_everything_can_be_overridden(self):
            api_request = ApiRequest("GET",
                                     "/path/{variable_name}",
                                     {"strange_name": "23"},
                                     {"strange_name": "28"},
                                     {},
                                     b"")

            @auto_map({"variable_name": {"location": "query", "name": "strange_name", "type": int}})
            class MyRequest(object):
                def __init__(self, variable_name: str):
                    self.variable_name = variable_name

            req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
            assert req.variable_name == 28

    class TestVariablesInTheBody(object):
        class TestBodyIsNotAValidJson(object):
            def test_whenNoTypeIsSpecified_TheEntireBodyIsMappedAsStr(self):
                api_request = ApiRequest("POST",
                                         "/path/",
                                         {},
                                         {},
                                         {},
                                         b"body")

                req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
                assert req.variable_name == "body"

            def test_whenNoContentTypeEncodingIsSpecified_BodyIsAssumedToBeEncodedInUTF8(self):
                body_str = "váriable~ç"
                api_request = ApiRequest("POST",
                                         "/path/",
                                         {},
                                         {},
                                         {},
                                         body_str.encode("utf-8"))

                req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
                assert req.variable_name == body_str

            def test_whenContentTypeEncodingIsSpecified_BodyIsEncodedWithSpecifiedEncoding(self):
                body_str = "váriable~ç"
                api_request = ApiRequest("POST",
                                         "/path",
                                         {},
                                         {},
                                         {"Content-Type": "text/plain; charset=ISO-8859-1"},
                                         body_str.encode("ISO-8859-1"))

                req = AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
                assert req.variable_name == body_str

            def test_allVariablesNotFoundAreMappedToTheBody(self):
                api_request = ApiRequest("POST",
                                         "/path/{var_1}",
                                         {"var_1": "var_1"},
                                         {"var_2": "var_2"},
                                         {},
                                         b"body")

                class MyRequest(object):
                    def __init__(self, var_1, var_2, var_3, var_4):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3
                        self.var_4 = var_4

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(api_request, None)
                assert req.var_1 == "var_1"
                assert req.var_2 == "var_2"
                assert req.var_3 == "body"
                assert req.var_4 == "body"

            def test_whenTheContentTypeSpecifiesJsonButNoJsonContent_ExceptionIsRaised(self):
                api_request = ApiRequest("POST",
                                         "/path/{var_1}",
                                         {"var_1": "var_1"},
                                         {"var_2": "var_2"},
                                         {"Content-Type": "application/json; charset=ISO-8859-1"},
                                         b"body")

                with pytest.raises(BodyNotJson) as ctx:
                    AutoMappedMessageProcessor(MyRequestNoType, DummyInteractor()).parse_data(api_request, None)
                assert ctx.value.body == "body"

        class TestBodyIsAValidJson(object):
            def setup_method(self, test_method):
                self.body_content_with_type = b"""
                {
                    "var_1": "1",
                    "var_2": "1.2",
                    "var_3": "2020-02-07T13:22:39",
                    "var_4": "true"
                }"""

                self.body_content_no_type = b"""
                {
                    "var_1": "var_1",
                    "var_2": "var_2",
                    "var_3": "var_3"
                }"""

                self.body_content_with_null_and_missing = b"""
                {
                    "var_1": null,
                    "var_2": "var_2"
                }
                """

                self.api_request_no_type = ApiRequest("GET",
                                                      "/path/",
                                                      {},
                                                      {},
                                                      {},
                                                      self.body_content_no_type)

                self.api_request_with_type = ApiRequest("GET",
                                                        "/path/",
                                                        {},
                                                        {},
                                                        {},
                                                        self.body_content_with_type)

                self.api_request_with_null_and_missing = ApiRequest(
                    "GET",
                    "/path/",
                    {},
                    {},
                    {},
                    self.body_content_with_null_and_missing)

            def test_whenBodyIsAValidJson_ItIsParsedAndTheVariablesAreLookedInside(self):
                class MyRequest(object):
                    def __init__(self, var_1, var_2, var_3):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    self.api_request_no_type,
                    None)
                assert req.var_1 == "var_1"
                assert req.var_2 == "var_2"
                assert req.var_3 == "var_3"

            def test_whenBodyIsAValidJsonButTheVariableIsNotFound_NoneIsReturnedInTheVariable(self):
                class MyRequest(object):
                    def __init__(self, var_1, var_2, var_3, var_4):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3
                        self.var_4 = var_4

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    self.api_request_no_type,
                    None)
                assert req.var_1 == "var_1"
                assert req.var_2 == "var_2"
                assert req.var_3 == "var_3"
                assert req.var_4 is None

            def test_theTypingsAreAppliedToTheVariablesExtractedFromTheBody(self):
                class MyRequest(object):
                    def __init__(self, var_1: int, var_2: float, var_3: datetime, var_4: bool):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3
                        self.var_4 = var_4

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    self.api_request_with_type,
                    None)
                assert req.var_1 == 1
                assert req.var_2 == 1.2
                assert req.var_3 == iso8601.parse_date("2020-02-07T13:22:39")
                assert req.var_4

            def test_whenNoExtraMappingArePresent_thenNullAndMissingPropertiesAreTreatedAsNone(self):
                class MyRequest(object):
                    def __init__(self, var_1, var_2, var_3):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3

                req = AutoMappedMessageProcessor(MyRequest,
                                                 DummyInteractor()).parse_data(
                    self.api_request_with_null_and_missing,
                    None)
                assert req.var_1 is None
                assert req.var_2 == "var_2"
                assert req.var_3 is None

            def test_whenExtraMappingIsPresent_thenNullAndMissingPropertiesAreTreatedAsNone(self):
                class MyRequest(object):
                    def __init__(self, var_1: Optional[str], var_2: str, var_3: Optional[str]):
                        self.var_1 = var_1
                        self.var_2 = var_2
                        self.var_3 = var_3

                req = AutoMappedMessageProcessor(MyRequest,
                                                 DummyInteractor(),
                                                 extra_mapping={"removeNotSent": True}
                                                 ).parse_data(self.api_request_with_null_and_missing, None)
                assert req.var_1 is None
                assert req.var_2 == "var_2"
                assert not hasattr(req, "var_3")

        class TestMapInnerObjectsFromTheBody(object):
            def test_whenNoTypingIsSpecified_DictIsUsed(self):
                body_content = b"""{
                                    "var_1": {
                                        "id": 1,
                                        "name": "my name",
                                        "value": 1.5,
                                        "created_date": "2020-02-07T13:22:39"
                                    }, 
                                    "var_2": {
                                        "id": 1,
                                        "name": "my name",
                                        "value": 1.5,
                                        "created_date": "2020-02-07T13:22:39"
                                    }
                                }"""

                class MyType(object):
                    def __init__(self, id, name, value, created_date):
                        self.id = id
                        self.name = name
                        self.value = value
                        self.created_date = created_date

                class MyRequest(object):
                    def __init__(self, var_1, var_2):
                        self.var_1 = var_1
                        self.var_2 = var_2

                expected_value = {
                    "id": 1,
                    "name": "my name",
                    "value": 1.5,
                    "created_date": "2020-02-07T13:22:39"
                }
                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert req.var_1 == expected_value
                assert req.var_2 == expected_value

            def test_whenTypingIsSpecified_TheInnerObjectIsParsedToTheCorrectType(self):
                body_content = b"""{
                                    "var_1": {
                                        "id": "1",
                                        "name": "my name",
                                        "value": "1.5",
                                        "created_date": "2020-02-07T13:22:39"
                                    }, 
                                    "var_2": {
                                        "id": 65468,
                                        "description": "my description",
                                        "alt_d": "0d8e42f9-724c-4518-aa6a-f4a004230869"
                                    }
                                }"""

                class MyType1(object):
                    def __init__(self, id: int, name: str, value: float, created_date: datetime):
                        self.id = id
                        self.name = name
                        self.value = value
                        self.created_date = created_date

                class MyType2(object):
                    def __init__(self, id: int, description: str, alt_d: UUID):
                        self.id = id
                        self.description = description
                        self.alt_d = alt_d

                class MyRequest(object):
                    def __init__(self, var_1: MyType1, var_2: MyType2):
                        self.var_1 = var_1
                        self.var_2 = var_2

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req.var_1, MyType1)
                assert req.var_1.id == 1
                assert req.var_1.name == "my name"
                assert req.var_1.value == 1.5
                assert req.var_1.created_date == iso8601.parse_date("2020-02-07T13:22:39")

                assert isinstance(req.var_2, MyType2)
                assert req.var_2.id == 65468
                assert req.var_2.description == "my description"
                assert req.var_2.alt_d == UUID("0d8e42f9-724c-4518-aa6a-f4a004230869")

            def test_listTypesAreCorrectlyMapped(self):
                body_content = b"""{
                                    "list_1": [{"id": "1"}, {"id": "2"}, {"id": "3"}], 
                                    "list_2": [1, 2, 3, 4],
                                    "list_3": ["1", "2", "3"]
                                }"""

                class MyType1(object):
                    def __init__(self, id: int):
                        self.id = id

                class MyRequest(object):
                    def __init__(self, list_1: List[MyType1], list_2: List[int], list_3: List[str]):
                        self.list_1 = list_1
                        self.list_2 = list_2
                        self.list_3 = list_3

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req, MyRequest)
                assert isinstance(req.list_1, list)
                assert len(req.list_1) == 3
                assert isinstance(req.list_1[0], MyType1)
                assert req.list_1[0].id == 1
                assert isinstance(req.list_1[1], MyType1)
                assert req.list_1[1].id == 2
                assert isinstance(req.list_1[2], MyType1)
                assert req.list_1[2].id == 3

                assert isinstance(req.list_2, list)
                assert len(req.list_2) == 4
                assert req.list_2[0] == 1
                assert req.list_2[1] == 2
                assert req.list_2[2] == 3
                assert req.list_2[3] == 4

                assert isinstance(req.list_3, list)
                assert len(req.list_3) == 3
                assert req.list_3[0] == "1"
                assert req.list_3[1] == "2"
                assert req.list_3[2] == "3"

            def test_dictsAreCorrectlyMapped(self):
                body_content = b"""{
                                    "dict_1": {"key1": {"id": "1"}, "key2": {"id": "2"}, "key3": {"id": "3"}}, 
                                    "dict_2": {"id1": 1, "id2": 2, "id3": 3, "id4": 4}
                                }"""

                class MyType1(object):
                    def __init__(self, id: int):
                        self.id = id

                class MyRequest(object):
                    def __init__(self, dict_1: Dict[str, MyType1], dict_2: Dict[str, int]):
                        self.dict_1 = dict_1
                        self.dict_2 = dict_2

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req, MyRequest)
                assert isinstance(req.dict_1, dict)
                assert "key1" in req.dict_1
                assert isinstance(req.dict_1["key1"], MyType1)
                assert req.dict_1["key1"].id == 1
                assert "key2" in req.dict_1
                assert isinstance(req.dict_1["key2"], MyType1)
                assert req.dict_1["key2"].id == 2
                assert "key3" in req.dict_1
                assert isinstance(req.dict_1["key3"], MyType1)
                assert req.dict_1["key3"].id == 3

                assert isinstance(req.dict_2, dict)
                assert "id1" in req.dict_2
                assert req.dict_2["id1"] == 1
                assert "id2" in req.dict_2
                assert req.dict_2["id2"] == 2
                assert "id3" in req.dict_2
                assert req.dict_2["id3"] == 3

            def test_whenNoExtraMappingIsSpecified_NotAndNotSentAreTreatedAsNone(self):
                body_content = b"""{
                                    "var_1": {
                                        "id": "1",
                                        "name": null,
                                        "value": "1.5"
                                    }, 
                                    "var_2": {
                                        "id": 65468,
                                        "description": "my description",
                                        "alt_d": "0d8e42f9-724c-4518-aa6a-f4a004230869"
                                    }
                                }"""

                class MyType1(object):
                    def __init__(self, id: int, name: Optional[str], value: float, created_date: Optional[datetime]):
                        self.id = id
                        self.name = name
                        self.value = value
                        self.created_date = created_date

                class MyType2(object):
                    def __init__(self, id: int, description: str, alt_d: UUID):
                        self.id = id
                        self.description = description
                        self.alt_d = alt_d

                class MyRequest(object):
                    def __init__(self, var_1: MyType1, var_2: MyType2):
                        self.var_1 = var_1
                        self.var_2 = var_2

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req.var_1, MyType1)
                assert req.var_1.id == 1
                assert req.var_1.name is None
                assert req.var_1.value == 1.5
                assert req.var_1.created_date is None

                assert isinstance(req.var_2, MyType2)
                assert req.var_2.id == 65468
                assert req.var_2.description == "my description"
                assert req.var_2.alt_d == UUID("0d8e42f9-724c-4518-aa6a-f4a004230869")

            def test_whenExtraMappingIsSpecified_NullAndNotSentAreTreatedDifferent(self):
                body_content = b"""{
                                    "var_1": {
                                        "id": "1",
                                        "name": null,
                                        "value": "1.5"
                                    }, 
                                    "var_2": {
                                        "id": 65468,
                                        "description": "my description",
                                        "alt_d": "0d8e42f9-724c-4518-aa6a-f4a004230869"
                                    }
                                }"""

                class MyType1(object):
                    def __init__(self, id: int, name: Optional[str], value: float, created_date: Optional[datetime]):
                        self.id = id
                        self.name = name
                        self.value = value
                        self.created_date = created_date

                class MyType2(object):
                    def __init__(self, id: int, description: str, alt_d: UUID):
                        self.id = id
                        self.description = description
                        self.alt_d = alt_d

                class MyRequest(object):
                    def __init__(self, var_1: MyType1, var_2: MyType2):
                        self.var_1 = var_1
                        self.var_2 = var_2

                req = AutoMappedMessageProcessor(MyRequest,
                                                 DummyInteractor(),
                                                 extra_mapping={"removeNotSent": True})\
                    .parse_data(create_post_request(body_content), None)
                assert isinstance(req.var_1, MyType1)
                assert req.var_1.id == 1
                assert req.var_1.name is None
                assert req.var_1.value == 1.5
                assert not hasattr(req, "created_date")

                assert isinstance(req.var_2, MyType2)
                assert req.var_2.id == 65468
                assert req.var_2.description == "my description"
                assert req.var_2.alt_d == UUID("0d8e42f9-724c-4518-aa6a-f4a004230869")

            def test_innerObjectsPatternsCanBeOverriden(self):
                body_content = b"""{
                                    "var_1": {
                                        "id": "1",
                                        "name": null,
                                        "value": "1.5",
                                        "created-date": "2020-05-26T15:30:00"
                                    }, 
                                    "var_2": {
                                        "id": 65468,
                                        "desc": "my description",
                                        "alt_d": "0d8e42f9-724c-4518-aa6a-f4a004230869"
                                    }
                                }"""

                class MyType1(object):
                    def __init__(self, id, name, value, created_date):
                        self.id = id
                        self.name = name
                        self.value = value
                        self.created_date = created_date

                class MyType2(object):
                    def __init__(self, id: int, description: str, alt_d: UUID):
                        self.id = id
                        self.description = description
                        self.alt_d = alt_d

                class MyRequest(object):
                    def __init__(self, var_1: MyType1, var_2: MyType2):
                        self.var_1 = var_1
                        self.var_2 = var_2

                req = AutoMappedMessageProcessor(MyRequest,
                                                 DummyInteractor(),
                                                 extra_mapping={
                                                     "var_1": {
                                                         "id": {"type": int},
                                                         "name": {"type": Optional[str]},
                                                         "value": {"type": float},
                                                         "created_date": {
                                                             "type": datetime,
                                                             "name": "created-date"
                                                         }
                                                     },
                                                     "var_2": {
                                                         "description": {"name": "desc"}
                                                     }
                                                 })\
                    .parse_data(create_post_request(body_content), None)

                assert isinstance(req.var_1, MyType1)
                assert req.var_1.id == 1
                assert req.var_1.name is None
                assert req.var_1.value == 1.5
                assert req.var_1.created_date == iso8601.parse_date("2020-05-26T15:30:00")
                assert not hasattr(req, "created_date")

                assert isinstance(req.var_2, MyType2)
                assert req.var_2.id == 65468
                assert req.var_2.description == "my description"
                assert req.var_2.alt_d == UUID("0d8e42f9-724c-4518-aa6a-f4a004230869")

        class TestCamelToSnakeConversionOnBody(object):
            def test_thePropertiesAreConvertedToSnakeCase(self):
                body_content = b"""{
                                        "propertyId": 1,
                                        "propertyName": "my name",
                                        "propertyValueWithVariousCamelCases": 1.5
                                }"""

                class MyRequest(object):
                    def __init__(self,
                                 property_id: int,
                                 property_name: str,
                                 property_value_with_various_camel_cases: float):
                        self.property_id = property_id
                        self.property_name = property_name
                        self.property_value_with_various_camel_cases = property_value_with_various_camel_cases

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert req.property_id == 1
                assert req.property_name == "my name"
                assert req.property_value_with_various_camel_cases == 1.5

            def test_innerPropertiesAreAlsoConverted(self):
                body_content = b"""{
                                        "propertyType": {
                                            "snakeCase": "camelCase"
                                        }
                                }"""

                class SnakeTestType(object):
                    def __init__(self, snake_case: str):
                        self.snake_case = snake_case

                class MyRequest(object):
                    def __init__(self, property_type: SnakeTestType):
                        self.property_type = property_type

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req.property_type, SnakeTestType)
                assert req.property_type.snake_case == "camelCase"

            def test_propertiesInsideADictAreNotConverted(self):
                body_content = b"""{
                                        "propertyType": {
                                            "snakeCase": "camelCase",
                                            "customer_name": "Customer Name"
                                        }
                                }"""

                class MyRequest(object):
                    def __init__(self, property_type: Dict[str, str]):
                        self.property_type = property_type

                req = AutoMappedMessageProcessor(MyRequest, DummyInteractor()).parse_data(
                    create_post_request(body_content), None)
                assert isinstance(req.property_type, dict)
                assert req.property_type["snakeCase"] == "camelCase"
                assert req.property_type["customer_name"] == "Customer Name"
