'''Test suite for the api module.'''

import unittest
import unittest.mock as mock

from werkzeug.exceptions import HTTPException
from werkzeug.routing import Rule

from .. import api
from .dummymodule import OperationsClass, DummyAPI, DummyResource


class TestApi(unittest.TestCase):

    '''Test the Api class.'''

    def test_description(self):
        '''An API description is the first line of its docstring.'''
        expected = 'This should be the description.'
        self.assertEqual(expected, DummyAPI.description)

    def test_fragment(self):
        '''An API swagger fragment has 'path' and 'description' as keys'''
        fragment = DummyAPI.get_swagger_fragment()
        self.assertIn('path', fragment)
        self.assertIn('description', fragment)


class TestResource(unittest.TestCase):

    '''Test the Resource class.'''

    def test_parse_signature(self):
        '''Parsing an endpoint signature bears [<swagger-dictionary>, ...].'''
        keys = {'paramType', 'name', 'description', 'type', 'required'}
        params = DummyResource.parse_signature(DummyResource.get_verb)
        self.assertEqual(1, len(params))
        self.assertTrue(keys.issubset(set(params[0].keys())))

    def test_get_fragment(self):
        '''A Resource swagger fragment has the relevant keys in it.'''
        keys = {'path', 'description', 'operations'}
        actual = DummyResource.get_swagger_fragment()
        self.assertTrue(keys.issubset(actual))
        actual = DummyResource.get_swagger_fragment()  # Use cache, coverage

    def test_get_operations(self):
        '''Each operation is rendered as a swagger dictionary.'''
        keys = {
            'method',
            'nickname',
            'type',
            'parameters',
            'summary',
            'notes',
            'responseMessages'}
        operations = DummyResource.get_resource_operations()
        for op in operations:
            self.assertIn(op['method'], ('GET', 'POST'))
            self.assertTrue(keys.issubset(op.keys()))

    def test_endpoint_path(self):
        '''Werkzeug paths are generated by joining api.path and subpath.'''
        expected = '/dummyapi/dummyres/<param>'
        self.assertEqual(expected, DummyResource.endpoint_path)

    def test_get_routing_tuples(self):
        '''Werkzeug rules are coupled to the relevant callback method.'''
        tuples = list(DummyResource.get_routing_tuples())
        self.assertEqual(2, len(tuples))
        self.assertTrue(all((isinstance(t[0], Rule)) for t in tuples))

    def test_description(self):
        '''The description of a Resource is its first docstring line.'''
        expected = 'A dummy resource.'
        self.assertEqual(expected, DummyResource.description)

    def test_callbacks(self):
        '''It is possible to discern endpoint methods from other methods.'''
        cbacks = DummyResource.callbacks
        self.assertEqual(2, len(cbacks))
        self.assertNotIn(DummyResource.i_am_not_an_endpoint, cbacks)

    def test_implemented_methods(self):
        '''It is possible to map HTTP verbs to specific Resource methods.'''
        expected = {'GET': DummyResource.get_verb,
                    'POST': DummyResource.post_verb}
        self.assertEqual(expected, DummyResource.implemented_methods)


class TestOperations(unittest.TestCase):

    '''Test the operations decorator.'''

    def setUp(self):
        self.original = OperationsClass.foo
        decorator = api.operations('GET', 'POST')
        with mock.patch('builtins.classmethod') as mock_clsmethod:
            mock_clsmethod.side_effect = lambda v: v
            self.mock_clsmethod = mock_clsmethod
            self.decorated = decorator(OperationsClass.foo)

    def test_transfer_metadata(self):
        '''@operations() transefers metadata from method to wrapper.'''
        self.assertEqual(self.original.__doc__, self.decorated.__doc__)
        self.assertEqual(self.original.__annotations__,
                         self.decorated.__annotations__)
        self.assertEqual(self.original.signature, self.decorated.signature)

    def test_convert_to_classmethod(self):
        '''@operations() turn the decorated method in a class method.'''
        self.assertEqual(1, self.mock_clsmethod.call_count)

    def test_intercept_fail(self):
        '''@operations() transforms Fail exceptions in HTTPException ones.'''
        with self.assertRaises(HTTPException) as context_manager:
            next(self.decorated(mock.MagicMock(), mock.MagicMock(), 42))
        self.assertEqual(404, context_manager.exception.code)
