#
# 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.
"""Test response handlers.
"""

import json
import os
import unittest

from gabbi import case
from gabbi.exception import GabbiFormatError
from gabbi.handlers import core
from gabbi.handlers import jsonhandler
from gabbi.handlers import yaml_disk_loading_jsonhandler
from gabbi import suitemaker


class HandlersTest(unittest.TestCase):
    """Test the response handlers.

    Note that this does not test the magic template variables, that
    should be tested somewhere else.
    """

    def setUp(self):
        super(HandlersTest, self).setUp()
        self.test_class = case.HTTPTestCase
        self.test = suitemaker.TestBuilder('mytest', (self.test_class,),
                                           {'test_data': {},
                                            'content_handlers': []})

    def test_empty_response_handler(self):
        self.test.test_data = {'url': '$RESPONSE["barnabas"]'}
        self.test.response = {'content-type': 'unmatchable'}
        self.test.response_data = ''
        self.test.prior = self.test

        url = self.test('test_request').replace_template(
            self.test.test_data['url'])
        self.assertEqual('barnabas', url)

        self.test.response_data = None
        self.test.content_handlers = [jsonhandler.JSONHandler()]
        url = self.test('test_request').replace_template(
            self.test.test_data['url'])
        self.assertEqual('barnabas', url)

    def test_response_strings(self):
        handler = core.StringResponseHandler()
        self.test.content_type = "text/plain"
        self.test.response_data = None
        self.test.test_data = {'response_strings': ['alpha', 'beta']}
        self.test.output = 'alpha\nbeta\n'
        self._assert_handler(handler)

    def test_response_strings_fail(self):
        handler = core.StringResponseHandler()
        self.test.content_type = "text/plain"
        self.test.response_data = None
        self.test.test_data = {'response_strings': ['alpha', 'beta']}
        self.test.output = 'alpha\nbta\n'
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_strings_fail_big_output(self):
        handler = core.StringResponseHandler()
        self.test.content_type = "text/plain"
        self.test.response_data = None
        self.test.test_data = {'response_strings': ['alpha', 'beta']}
        self.test.output = 'alpha\nbta\n' * 1000
        with self.assertRaises(AssertionError) as cm:
            self._assert_handler(handler)

        msg = str(cm.exception)
        self.assertEqual(2036, len(msg))

    def test_response_strings_fail_big_payload(self):
        string_handler = core.StringResponseHandler()
        # Register the JSON handler so response_data is set.
        json_handler = jsonhandler.JSONHandler()
        self.test.response_handlers = [string_handler, json_handler]
        self.test.content_handlers = [json_handler]
        self.test.content_type = "application/json"
        self.test.test_data = {'response_strings': ['foobar']}
        self.test.response_data = {
            'objects': [{'name': 'cw',
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}] * 100
        }
        self.test.output = json.dumps(self.test.response_data)
        with self.assertRaises(AssertionError) as cm:
            self._assert_handler(string_handler)

        msg = str(cm.exception)
        self.assertEqual(2038, len(msg))
        # Check the pprint of the json
        self.assertIn('      "location": "house"', msg)

    def test_response_string_list_type(self):
        handler = core.StringResponseHandler()
        self.test.test_data = {
            'name': 'omega test',
            'response_strings': 'omega'
        }
        self.test.output = 'omega\n'
        with self.assertRaises(GabbiFormatError) as exc:
            self._assert_handler(handler)
            self.assertIn('has incorrect type', str(exc))
            self.assertIn("response_strings in 'omega test'",
                          str(exc))

    def test_response_json_paths(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.objects[0].name': 'cow',
            '$.objects[1].location': 'house',
        }}
        self.test.response_data = {
            'objects': [{'name': 'cow',
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}]
        }
        self._assert_handler(handler)

    def test_response_json_paths_fail_data(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.objects[0].name': 'cow',
            '$.objects[1].location': 'house',
        }}
        self.test.response_data = {
            'objects': [{'name': 'cw',
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}]
        }
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_json_paths_fail_path(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.objects[1].name': 'cow',
        }}
        self.test.response_data = {
            'objects': [{'name': 'cow',
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}]
        }
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_json_paths_regex(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.objects[0].name': '/ow/',
        }}
        self.test.response_data = {
            'objects': [{'name': u'cow\U0001F404',
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}]
        }
        self._assert_handler(handler)

    def test_response_json_paths_regex_path_match(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.pathtest': '//bar//',
        }}
        self.test.response_data = {
            'pathtest': '/foo/bar/baz'
        }
        self._assert_handler(handler)

    def test_response_json_paths_regex_path_nomatch(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.pathtest': '//bar//',
        }}
        self.test.response_data = {
            'pathtest': '/foo/foobar/baz'
        }
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_json_paths_substitution_regex(self):
        handler = jsonhandler.JSONHandler()
        self.test.location = '/foo/bar'
        self.test.prior = self.test
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.pathtest': '/$LOCATION/',
        }}
        self.test.response_data = {
            'pathtest': '/foo/bar/baz'
        }
        self._assert_handler(handler)

    def test_response_json_paths_substitution_noregex(self):
        handler = jsonhandler.JSONHandler()
        self.test.location = '/foo/bar/'
        self.test.prior = self.test
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.pathtest': '$LOCATION',
        }}
        self.test.response_data = {
            'pathtest': '/foo/bar/baz'
        }
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_json_paths_substitution_esc_regex(self):
        handler = jsonhandler.JSONHandler()
        self.test.location = '/foo/bar?query'
        self.test.prior = self.test
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.pathtest': '/$LOCATION/',
        }}
        self.test.response_data = {
            'pathtest': '/foo/bar?query=value'
        }
        self._assert_handler(handler)

    def test_response_json_paths_regex_number(self):
        handler = jsonhandler.JSONHandler()
        self.test.content_type = "application/json"
        self.test.test_data = {'response_json_paths': {
            '$.objects[0].name': r'/\d+/',
        }}
        self.test.response_data = {
            'objects': [{'name': 99,
                         'location': 'barn'},
                        {'name': 'chris',
                         'location': 'house'}]
        }
        self._assert_handler(handler)

    def test_response_json_paths_dict_type(self):
        handler = jsonhandler.JSONHandler()
        self.test.test_data = {
            'name': 'omega test',
            'response_json_paths': ['alpha', 'beta']
        }
        self.test.output = 'omega\n'
        with self.assertRaises(GabbiFormatError) as exc:
            self._assert_handler(handler)
            self.assertIn('has incorrect type', str(exc))
            self.assertIn("response_json_paths in 'omega test'",
                          str(exc))

    def test_response_json_paths_from_disk_json_path(self):
        handler = jsonhandler.JSONHandler()
        lhs = '$.pets[?type = "cat"].sound'
        rhs = '$.values[0].pets[?type = "cat"].sound'
        self.test.test_directory = os.path.dirname(__file__)
        self.test.test_data = {'response_json_paths': {
            lhs: '<@gabbits_handlers/values.json:' + rhs,
        }}
        self.test.response_data = {
            "pets": [
                {"type": "cat", "sound": "meow"},
                {"type": "dog", "sound": "woof"}
            ]
        }
        self._assert_handler(handler)

    def test_response_json_paths_from_disk_json_path_fail(self):
        handler = jsonhandler.JSONHandler()
        lhs = '$.pets[?type = "cat"].sound'
        rhs = '$.values[0].pets[?type = "bad"].sound'
        self.test.test_directory = os.path.dirname(__file__)
        self.test.test_data = {'response_json_paths': {
            lhs: '<@gabbits_handlers/values.json:' + rhs,
        }}
        self.test.response_data = {
            "pets": [
                {"type": "cat", "sound": "meow"},
                {"type": "dog", "sound": "woof"}
            ]
        }
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_json_paths_yamlhandler(self):
        handler = yaml_disk_loading_jsonhandler.YAMLDiskLoadingJSONHandler()
        lhs = '$.pets[?type = "cat"].sound'
        rhs = '$.values[0].pets[?type = "cat"].sound'
        self.test.test_directory = os.path.dirname(__file__)
        self.test.test_data = {'response_json_paths': {
            lhs: '<@gabbits_handlers/subdir/values.yaml:' + rhs,
        }}
        self.test.response_data = {
            "pets": [
                {"type": "cat", "sound": "meow"},
                {"type": "dog", "sound": "woof"}
            ]
        }
        self._assert_handler(handler)

    def test_response_headers(self):
        handler = core.HeadersResponseHandler()
        self.test.response = {'content-type': 'text/plain'}

        self.test.test_data = {'response_headers': {
            'content-type': 'text/plain',
        }}
        self._assert_handler(handler)

        self.test.test_data = {'response_headers': {
            'Content-Type': 'text/plain',
        }}
        self._assert_handler(handler)

    def test_response_headers_regex(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'content-type': '/text/plain/',
        }}
        self.test.response = {'content-type': 'text/plain; charset=UTF-8'}
        self._assert_handler(handler)

    def test_response_headers_substitute_noregex(self):
        handler = core.HeadersResponseHandler()
        self.test.location = '/foo/bar/'
        self.test.prior = self.test
        self.test.test_data = {'response_headers': {
            'location': '$LOCATION',
        }}
        self.test.response = {'location': '/foo/bar/baz'}
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_headers_substitute_regex(self):
        handler = core.HeadersResponseHandler()
        self.test.location = '/foo/bar/'
        self.test.prior = self.test
        self.test.test_data = {'response_headers': {
            'location': '/^$LOCATION/',
        }}
        self.test.response = {'location': '/foo/bar/baz'}
        self._assert_handler(handler)

    def test_response_headers_substitute_esc_regex(self):
        handler = core.HeadersResponseHandler()
        self.test.scheme = 'git+ssh'
        self.test.test_data = {'response_headers': {
            'location': '/^$SCHEME://.*/',
        }}
        self.test.response = {'location': 'git+ssh://example.test'}
        self._assert_handler(handler)

    def test_response_headers_noregex_path_match(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'location': '/',
        }}
        self.test.response = {'location': '/'}
        self._assert_handler(handler)

    def test_response_headers_noregex_path_nomatch(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'location': '/',
        }}
        self.test.response = {'location': '/foo'}
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_headers_regex_path_match(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'location': '//bar//',
        }}
        self.test.response = {'location': '/foo/bar/baz'}
        self._assert_handler(handler)

    def test_response_headers_regex_path_nomatch(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'location': '//bar//',
        }}
        self.test.response = {'location': '/foo/foobar/baz'}
        with self.assertRaises(AssertionError):
            self._assert_handler(handler)

    def test_response_headers_fail_data(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'content-type': 'text/plain',
        }}
        self.test.response = {'content-type': 'application/json'}
        with self.assertRaises(AssertionError) as failure:
            self._assert_handler(handler)
        self.assertIn("Expect header content-type with value text/plain,"
                      " got application/json",
                      str(failure.exception))

    def test_response_headers_fail_header(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'location': '/somewhere',
        }}
        self.test.response = {'content-type': 'application/json'}
        with self.assertRaises(AssertionError) as failure:
            self._assert_handler(handler)
        self.assertIn("'location' header not present in response:",
                      str(failure.exception))

    def test_response_headers_stringify(self):
        handler = core.HeadersResponseHandler()
        self.test.test_data = {'response_headers': {
            'x-alpha-beta': 2.0,
        }}
        self.test.response = {'x-alpha-beta': '2.0'}
        self._assert_handler(handler)

        self.test.response = {'x-alpha-beta': 2.0}
        self._assert_handler(handler)

    def _assert_handler(self, handler):
        # Instantiate our contained test class by naming its test
        # method and then run its tests to confirm.
        test = self.test('test_request')
        handler(test)


class TestJSONHandlerAccept(unittest.TestCase):
    """Test that the json handler accepts function.

    We need to confirm that it returns True and False at the right
    times. This is somewhat tricky as there are a fair number of
    MIME-types that include the string "JSON" but aren't, as a
    whole document, capable of being decoded.
    """

    def _test_content_type(self, content_type, expected):
        if expected:
            self.assertTrue(
                jsonhandler.JSONHandler.accepts(content_type),
                "expected %s to be accepted but it was not!" % content_type)
        else:
            self.assertFalse(
                jsonhandler.JSONHandler.accepts(content_type),
                "expected %s to not be accepted but it was!" % content_type)

    def test_many_content_types(self):
        cases = [
            ("application/json", True),
            ("application/JSON", True),
            ("text/plain", False),
            ("application/jsonlines", False),
            ("application/json;stream=true", False),
            ("application/json;streamable=pony", True),
            ("application/stream+json", True),
            ("application/xml", False),
            ("application/json-seq", False),
            ("application/json-home", False),
        ]
        for test in cases:
            with self.subTest(test[0]):
                self._test_content_type(*test)
