# Copyright (c) 2016 AT&T inc.
# 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.

import errno
import eventlet
import socket
from unittest import mock

import webob

from oslo_config import cfg
from xml.dom import minidom

from murano.common import exceptions
from murano.common.i18n import _
from murano.common import wsgi
from murano.tests.unit import base

CONF = cfg.CONF


class TestServiceBrokerResponseSerializer(base.MuranoTestCase):

    def test_service_broker_response_serializer(self):
        self.sbrs = wsgi.ServiceBrokerResponseSerializer()
        result = self.sbrs.serialize("test_response", "application/json")
        self.assertEqual(200, result.status_code)

        response = webob.Response("test_body")
        response.data = "test_data"
        result = self.sbrs.serialize(response, "application/json")
        self.assertEqual(200, result.status_code)


class TestResponseSerializer(base.MuranoTestCase):

    def test_response_serializer(self):
        self.response_serializer = wsgi.ResponseSerializer(None)
        self.assertRaises(exceptions.UnsupportedContentType,
                          self.response_serializer.get_body_serializer,
                          "not_valid")


class TestRequestDeserializer(base.MuranoTestCase):

    def test_request_deserializer_deserialize_body(self):
        self.request_deserializer = wsgi.RequestDeserializer()
        request = mock.Mock()
        request.get_content_type.side_effect = (
            exceptions.UnsupportedContentType
        )
        request.body = "body"
        self.assertRaises(exceptions.UnsupportedContentType,
                          self.request_deserializer.deserialize_body,
                          request, "act")

        request.get_content_type.side_effect = None
        request.get_content_type.return_value = None
        result = self.request_deserializer.deserialize_body(request, "act")
        self.assertEqual({}, result)

        request.get_content_type.return_value = ""
        self.assertRaises(exceptions.UnsupportedContentType,
                          self.request_deserializer.deserialize_body,
                          request, "act")

    def test_request_deserializer_get_action_args(self):
        self.request_deserializer = wsgi.RequestDeserializer()
        request_environment = []
        result = self.request_deserializer.get_action_args(request_environment)
        self.assertEqual({}, result)

        request_environment = {'wsgiorg.routing_args': ["", {}]}
        result = self.request_deserializer.get_action_args(request_environment)
        self.assertEqual({}, result)


class TestService(base.MuranoTestCase):

    def setUp(self):
        super(TestService, self).setUp()
        # Greatly speed up the time it takes to run the tests that call
        # wsgi._get_socket below: retry_until will be set to 31, the 1st
        # iteration of the while loop will execute because it will evaluate to
        # 30 < 31, but 2nd one will not because it will evaluate to 31 < 31.
        self.mock_time = mock.patch.object(
            wsgi, 'time', **{'time.side_effect': [1, 30, 31]}).start()
        self.addCleanup(mock.patch.stopall)

    @mock.patch("murano.common.wsgi.socket")
    def test_service_get_socket(self, socket):
        self.service = wsgi.Service(None, 1)
        new_socket = self.service._get_socket(None, None, None)
        self.assertIsInstance(new_socket, eventlet.greenio.base.GreenSocket)

        self.mock_time.time.side_effect = [1, 30, 31]
        socket.TCP_KEEPIDLE = True
        new_socket_2 = self.service._get_socket(None, None, None)
        self.assertIsInstance(new_socket_2, eventlet.greenio.base.GreenSocket)

    @mock.patch("murano.common.wsgi.socket")
    @mock.patch("murano.common.wsgi.sslutils")
    def test_service_get_socket_sslutils_enabled(self, sslutils, mock_socket):
        self.service = wsgi.Service(None, 1)
        new_socket = self.service._get_socket(None, None, None)
        self.assertIsNotNone(new_socket)

    @mock.patch("murano.common.wsgi.socket")
    @mock.patch("murano.common.wsgi.eventlet")
    def test_service_get_socket_no_bind(self, eventlet, mock_socket):
        self.service = wsgi.Service(None, 1)
        eventlet.listen.return_value = None
        self.assertRaises(RuntimeError, self.service._get_socket,
                          None, None, None)

    @mock.patch("murano.common.wsgi.socket")
    @mock.patch("murano.common.wsgi.eventlet")
    def test_service_get_socket_os_error(self, eventlet, mock_socket):
        mock_socket.error = socket.error
        self.service = wsgi.Service(None, 1)
        sock_err = socket.error(1)
        eventlet.listen.side_effect = sock_err
        self.assertRaises(socket.error, self.service._get_socket,
                          None, None, None)

    @mock.patch("murano.common.wsgi.socket")
    @mock.patch("murano.common.wsgi.eventlet")
    def test_service_get_socket_socket_error_EADDRINUSE(self, eventlet,
                                                        mock_socket):
        mock_socket.error = socket.error
        self.service = wsgi.Service(None, 1)
        sock_err = socket.error(errno.EADDRINUSE)
        eventlet.listen.side_effect = sock_err
        self.assertRaises(RuntimeError, self.service._get_socket,
                          None, None, None)

    @mock.patch("murano.common.wsgi.eventlet")
    def test_service_start(self, eventlet):
        self.service = wsgi.Service(None, 1)
        self.service.start()
        self.assertTrue(eventlet.spawn.called)

    def test_service_stop(self):
        self.service = wsgi.Service(None, 1)
        self.service.greenthread = mock.Mock()
        self.service.stop()
        self.assertTrue(self.service.greenthread.kill.called)

    def test_service_properties(self):
        self.service = wsgi.Service(None, 1)
        self.service._socket = mock.Mock()
        self.service._socket.getsockname.return_value = ["host", "port"]
        host = self.service.host
        port = self.service.port
        self.assertEqual("host", host)
        self.assertEqual("port", port)

    @mock.patch("murano.common.wsgi.logging")
    def test_service_reset(self, logging):
        self.service = wsgi.Service(None, 1)
        self.service.reset()
        self.assertTrue(logging.setup.called)

    @mock.patch("murano.common.wsgi.eventlet")
    def test_service_run(self, eventlet):
        self.service = wsgi.Service(None, 1)
        self.service._run(None, None)
        self.assertTrue(eventlet.wsgi.server.called)

    def test_backlog_prop(self):
        service = wsgi.Service(None, None)
        service._backlog = mock.sentinel.backlog
        self.assertEqual(mock.sentinel.backlog, service.backlog)


class TestMiddleware(base.MuranoTestCase):

    def test_middleware_call(self):
        self.middleware = wsgi.Middleware(None)
        mock_request = mock.Mock()
        mock_request.get_response.return_value = "a response"
        self.assertEqual("a response", self.middleware(mock_request))

    def test_call_with_response(self):
        middleware = wsgi.Middleware(None)
        middleware.process_request = mock.Mock(return_value=mock.sentinel.resp)

        resp = middleware(mock.sentinel.req)

        self.assertEqual(mock.sentinel.resp, resp)
        middleware.process_request.assert_called_once_with(mock.sentinel.req)


class TestDebug(base.MuranoTestCase):

    def test_debug_call(self):
        self.debug = wsgi.Debug(None)
        mock_request = mock.Mock()
        mock_request.environ = {"one": 1, "two": 2}

        mock_response = mock.Mock()
        mock_response.headers = {"one": 1, "two": 2}
        mock_request.get_response.return_value = mock_response

        self.assertEqual(mock_response, self.debug(mock_request))

    @mock.patch('sys.stdout')
    def test_print_generator(self, mock_stdout):
        for x in wsgi.Debug.print_generator(['foo', 'bar', 'baz']):
            pass
        mock_stdout.write.assert_has_calls([
            mock.call('**************************************** BODY'),
            mock.call('\n'),
            mock.call('foo'),
            mock.call('bar'),
            mock.call('baz'),
            mock.call(''),
            mock.call('\n')
        ])


class TestRequest(base.MuranoTestCase):

    @mock.patch.object(wsgi.webob.BaseRequest, 'path',
                       **{'rsplit.return_value': ['foo.bar', 'baz']})
    def test_best_match_content_type_with_multi_part_path(self, mock_path):
        request = wsgi.Request({})
        supported_content_types = ['application/baz']
        result = request.best_match_content_type(None, supported_content_types)
        self.assertEqual('application/baz', result)


class TestResource(base.MuranoTestCase):

    def test_resource_call_exceptions(self):
        self.resource = wsgi.Resource(None)
        self.resource.deserialize_request = mock.Mock()
        self.resource.deserialize_request.side_effect = (
            exceptions.UnsupportedContentType
        )
        mock_request = mock.Mock()
        mock_request.headers = {}
        result = self.resource(mock_request)
        self.assertEqual(415, result.status_code)

        self.resource.deserialize_request.side_effect = (
            exceptions.MalformedRequestBody
        )
        result = self.resource(mock_request)
        self.assertEqual(400, result.status_code)

        self.resource.deserialize_request.side_effect = None
        self.resource.deserialize_request.return_value = ["", {"k": "v"}, ""]
        self.resource.execute_action = mock.Mock()
        self.resource.serialize_response = mock.Mock()
        self.resource.serialize_response.side_effect = Exception

        result = self.resource(mock_request)
        self.assertIsNotNone(result)

    def test_get_action_args(self):
        self.resource = wsgi.Resource(None)
        result = self.resource.get_action_args(None)
        self.assertEqual({}, result)

        request_environment = {'wsgiorg.routing_args': ["arg_0", {"k": "v"}]}
        result = self.resource.get_action_args(request_environment)
        self.assertEqual({"k": "v"}, result)

    def test_dispatch_except_attribute_error(self):
        mock_obj = mock.Mock(spec=wsgi.Resource)
        setattr(mock_obj, 'default',
                mock.Mock(return_value=mock.sentinel.ret_value))

        resource = wsgi.Resource(None)
        result = resource.dispatch(mock_obj, 'invalid_action')

        self.assertEqual(mock.sentinel.ret_value, result)
        mock_obj.default.assert_called_once_with()


class TestActionDispatcher(base.MuranoTestCase):

    def test_default(self):
        action_dispatcher = wsgi.ActionDispatcher()
        self.assertRaises(NotImplementedError, action_dispatcher.default, None)


class TestDictSerializer(base.MuranoTestCase):

    def test_default(self):
        dict_serializer = wsgi.DictSerializer()
        self.assertEqual("", dict_serializer.default(None))


class TestXMLDictSerializer(base.MuranoTestCase):

    def test_router_dispatch_not_found(self):
        self.router = wsgi.Router(None)
        req = mock.Mock()
        req.environ = {'wsgiorg.routing_args': [False, False]}
        response = self.router._dispatch(req)
        self.assertIsInstance(response, webob.exc.HTTPNotFound)

    def test_XML_Dict_Serializer_default_string(self):
        self.serializer = wsgi.XMLDictSerializer()
        actual_xml = self.serializer.default({"XML Root": "some data"})
        expected_xml = b'<XML Root>some data</XML Root>\n'
        self.assertEqual(expected_xml, actual_xml)

    def test_XML_Dict_Serializer_node_dict(self):
        self.serializer = wsgi.XMLDictSerializer()
        doc = minidom.Document()
        metadata = mock.Mock()
        metadata.get.return_value = {"node": {"item_name": "name",
                                              "item_key": "key"}}
        nodename = "node"
        data = {"data_key": "data_value"}
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual('<name key="data_key">data_value</name>',
                         result.childNodes[0].toxml())

        mock_get_nodename = mock.Mock()
        mock_get_nodename.get.return_vale = "k"
        metadata.get.return_value = {"k": "v"}
        nodename = "node"
        data = {"data_key": "data_value"}
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual("node", result.nodeName)

        metadata.get.return_value = {}
        nodename = "node"
        data = {"data_key": "data_value"}
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual('<data_key>data_value</data_key>',
                         result.childNodes[0].toxml())

    def test_XML_Dict_Serializer_node_list(self):
        self.serializer = wsgi.XMLDictSerializer()
        doc = minidom.Document()
        metadata = mock.Mock()
        metadata.get.return_value = {"node": {"item_name": "name",
                                              "item_key": "key"}}
        nodename = "node"
        data = ["data_1", "data_2", "data_3"]
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual('<name key="data_1"/>', result.childNodes[0].toxml())

        nodename = "not_node"
        data = ["data_1", "data_2", "data_3"]
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual(3, len(result.childNodes))

        nodename = "nodes"
        data = ["data_1", "data_2", "data_3"]
        result = self.serializer._to_xml_node(doc, metadata, nodename, data)
        self.assertEqual(3, len(result.childNodes))

    def test_XML_Dict_Serializer_create_link_nodes(self):
        self.serializer = wsgi.XMLDictSerializer()
        xml_doc = minidom.Document()
        links = [{"rel": "rel", "href": "href", "type": "type"}]
        link_nodes = self.serializer._create_link_nodes(xml_doc, links)
        result = link_nodes[0].toxml()
        for item in ['<atom:link', 'rel="rel"', 'href="href"', 'type="type"']:
            self.assertIn(item, result)

    def test_add_xmlns(self):
        xml_dict_serializer = wsgi.XMLDictSerializer()
        xml_dict_serializer.xmlns = mock.sentinel.xmlns
        mock_node = mock.Mock()

        xml_dict_serializer._add_xmlns(mock_node, has_atom=False)
        mock_node.setAttribute.assert_called_once_with(
            'xmlns', mock.sentinel.xmlns)

        mock_node.reset_mock()
        xml_dict_serializer._add_xmlns(mock_node, has_atom=True)
        mock_node.setAttribute.assert_has_calls([
            mock.call('xmlns', mock.sentinel.xmlns),
            mock.call('xmlns:atom', "http://www.w3.org/2005/Atom")
        ])


class TestTextDeserializer(base.MuranoTestCase):

    def test_default(self):
        text_deserializer = wsgi.TextDeserializer()
        self.assertEqual({}, text_deserializer.default(None))


class TestJSONDeserializer(base.MuranoTestCase):

    def test_from_json_except_value_error(self):
        json_deserializer = wsgi.JSONDeserializer()
        e = self.assertRaises(exceptions.MalformedRequestBody,
                              json_deserializer._from_json,
                              "throw value error")
        self.assertIn('cannot understand JSON', str(e))


class TestJSONPatchDeserializer(base.MuranoTestCase):

    def test_from_json_patch_except_value_error(self):
        json_patch_deserializer = wsgi.JSONPatchDeserializer()
        e = self.assertRaises(exceptions.MalformedRequestBody,
                              json_patch_deserializer._from_json_patch,
                              "throw value error")
        self.assertIn('cannot understand JSON', str(e))

    def test_validate_allowed_methods_except_http_forbidden(self):
        json_patch_deserializer = wsgi.JSONPatchDeserializer()
        json_patch_deserializer.allowed_operations = mock.Mock(
            spec_set=wsgi.JSONPatchDeserializer.allowed_operations,
            **{'get.return_value': None}
        )

        e = self.assertRaises(
            webob.exc.HTTPForbidden,
            json_patch_deserializer._validate_allowed_methods,
            {'op': 'foo_op', 'path': 'foo_path'}, allow_unknown_path=False)
        self.assertEqual("Attribute 'f/o/o/_/p/a/t/h' is invalid", str(e))

    def test_validate_json_pointer(self):
        json_patch_deserializer = wsgi.JSONPatchDeserializer()
        expected_exception = webob.exc.HTTPBadRequest
        bad_pointers = ['pointer', '/ /\n/', '//', '// ', '/~2']

        for pointer in bad_pointers:
            self.assertRaises(expected_exception,
                              json_patch_deserializer._validate_json_pointer,
                              pointer)

    @mock.patch.object(wsgi, 'jsonschema', autospec=True)
    def test_validate_schema_cannot_validate(self, mock_jsonschema):
        json_patch_deserializer = wsgi.JSONPatchDeserializer()
        json_patch_deserializer.schema = {'type': 'array'}
        test_change = {'path': ['foo_path'], 'value': 'foo_value'}
        # Assert that jsonschema validation was not performed because
        # can_validate = False.
        json_patch_deserializer._validate_schema(test_change)
        self.assertFalse(mock_jsonschema.validate.called)

        mock_jsonschema.validate.reset_mock()
        json_patch_deserializer = wsgi.JSONPatchDeserializer()
        json_patch_deserializer.schema = {'type': 'object'}
        json_patch_deserializer._validate_schema(test_change)
        self.assertFalse(mock_jsonschema.validate.called)


class TestMuranoPackageJSONPatchDeserializer(base.MuranoTestCase):

    def test_validate_path(self):
        patch_deserializer = wsgi.MuranoPackageJSONPatchDeserializer()
        e = self.assertRaises(webob.exc.HTTPBadRequest,
                              patch_deserializer._validate_path,
                              ['foo_path', 'bar_path'])
        self.assertEqual(_('Nested paths are not allowed'), str(e))


class TestXMLDeserializer(base.MuranoTestCase):

    def test_XMLDeserializer_from_xml(self):
        self.deserializer = wsgi.XMLDeserializer()
        datastring = "<XML>HELLO</XML>"
        request = mock.Mock()
        request.body = datastring
        result = self.deserializer._from_xml(request)
        self.assertEqual({'XML': 'HELLO'}, result)

        datastring = "<NOT XML><NOT XML>"
        request.body = datastring
        self.assertRaises(exceptions.MalformedRequestBody,
                          self.deserializer._from_xml, request)
        datastring = "<XML><XML1>HELLO</XML1></XML>"
        request.body = datastring
        result = self.deserializer._from_xml(request)
        self.assertEqual({'XML': {'XML1': 'HELLO'}}, result)

        datastring = "<XML><XML1><XML2>HELLO</XML2></XML1></XML>"
        request.body = datastring
        result = self.deserializer._from_xml(request)
        self.assertEqual({'XML': {'XML1': {'XML2': 'HELLO'}}}, result)

    def test_XMLDeserializer_default(self):
        self.deserializer = wsgi.XMLDeserializer()
        datastring = "<XML>HELLO</XML>"
        request = mock.Mock()
        request.body = datastring

        result = self.deserializer.default(request)
        self.assertEqual({'body': {'XML': 'HELLO'}}, result)

    def test_XMLDeserializer_find_first_child_named(self):
        self.deserializer = wsgi.XMLDeserializer()
        parent = mock.Mock()
        mock_child = mock.Mock()
        mock_child.nodeName = "name"
        parent.childNodes = [mock_child]
        result = self.deserializer.find_first_child_named(parent, "name")
        self.assertIsNotNone(result)

        parent.childNodes = []
        result = self.deserializer.find_first_child_named(parent, "name")
        self.assertIsNone(result)

        mock_child.nodeName = "other_name"
        parent.childNodes = [mock_child]
        result = self.deserializer.find_first_child_named(parent, "name")
        self.assertIsNone(result)

    def test_XMLDeserializer_extract_text(self):
        self.deserializer = wsgi.XMLDeserializer()
        mock_node = mock.Mock()
        mock_child = mock.Mock()
        mock_child.nodeType = mock_child.TEXT_NODE
        mock_node.childNodes = [mock_child]
        result = self.deserializer.extract_text(mock_node)

        mock_child.nodeType = None
        mock_node.childNodes = [mock_child]
        result = self.deserializer.extract_text(mock_node)
        self.assertEqual("", result)

        mock_node.childNodes = []
        result = self.deserializer.extract_text(mock_node)
        self.assertEqual("", result)

    def test_from_xml_node_all_branches(self):
        mock_grandchild_node = mock.MagicMock(**{
            'nodeName': 'qux',
            'nodeType': mock.sentinel.grandchild_node_type,
            'childNodes': [
                mock.Mock(nodeType=3, nodeValue=mock.sentinel.qux_node)
            ]
        })
        mock_child_node = mock.MagicMock(**{
            'nodeName': mock.sentinel.child_node,
            'attributes.keys.return_value': ['foo', 'bar', 'baz'],
            'attributes.__getitem__.side_effect': [
                mock.Mock(nodeValue=mock.sentinel.foo_node),
                mock.Mock(nodeValue=mock.sentinel.bar_node),
                mock.Mock(nodeValue=mock.sentinel.baz_node)
            ],
            'childNodes': [
                mock_grandchild_node
            ],
            'TEXT_NODE': mock.sentinel.child_node_type
        })
        mock_node = mock.Mock(**{
            'nodeName': mock.sentinel.parent_node,
            'childNodes': [
                mock_child_node
            ]
        })
        listnames = [mock.sentinel.parent_node]

        xml_deserializer = wsgi.XMLDeserializer()
        result = xml_deserializer._from_xml_node(mock_node, listnames)
        expected_result = [{
            'foo': mock.sentinel.foo_node,
            'bar': mock.sentinel.bar_node,
            'baz': mock.sentinel.baz_node,
            'qux': mock.sentinel.qux_node
        }]

        self.assertEqual(1, len(result))
        self.assertEqual(len(expected_result[0]), len(result[0]))
        for key, val in expected_result[0].items():
            self.assertEqual(val, result[0][key])

    def test_find_children_named(self):
        child_node = mock.Mock(nodeName=mock.sentinel.node_name)
        parent_node = mock.Mock(childNodes=[child_node])

        result = []
        xml_deserializer = wsgi.XMLDeserializer()
        for n in xml_deserializer.\
                find_children_named(parent_node, mock.sentinel.node_name):
            result.append(n)

        self.assertEqual(1, len(result))
        self.assertEqual(child_node, result[0])


class TestFormDataDeserializer(base.MuranoTestCase):

    @mock.patch.object(wsgi, 'LOG', autospec=True)
    def test_from_json_except_value_error(self, mock_log):
        data_serializer = wsgi.FormDataDeserializer()
        value = data_serializer._from_json('value error')

        self.assertEqual('value error', value)
        mock_log.debug.assert_called_once_with(
            "Trying to deserialize 'value error' to json")
        mock_log.warning.assert_called_once_with(
            'Unable to deserialize to json, using raw text')
