diff --git a/README.rst b/README.rst index a70464a..f1b9add 100644 --- a/README.rst +++ b/README.rst @@ -89,6 +89,16 @@ Out-of-the-box it supports the following frameworks: You can use any of them, and you must pull them in via your own test requirements. +----------- +Error Codes +----------- + +StackInABox has some specific error codes to help with diagnosing issues: + +- 597 - StackInABox - Base URL is correct, but service is unknown +- 596 - StackInABox - Handling StackInABoxService generated an exception +- 595 - StackInABoxService - Route Not handled + Both of these are extremely easy to use as shown in the following examples: --------- diff --git a/setup.cfg b/setup.cfg index a73ab59..28a75bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,4 +6,4 @@ cover-package=stackinabox cover-erase=1 cover-inclusive=true cover-branches=true -cover-min-percentage=83 +cover-min-percentage=88 diff --git a/stackinabox/services/service.py b/stackinabox/services/service.py index 0cbd17a..f4a1d88 100644 --- a/stackinabox/services/service.py +++ b/stackinabox/services/service.py @@ -96,7 +96,7 @@ def __call__(self, method, request, uri, headers): request, uri, headers) - else: + elif self.obj is not None: logger.debug('Service Router ({0} - {1}): Located Subservice {2} ' 'on Route {3}. Calling...' .format(id(self), @@ -109,6 +109,16 @@ def __call__(self, method, request, uri, headers): uri, headers) + else: + logger.debug('Service Router ({0} - {1}): ' + 'No Method handler for service' + .format(id(self), + self.service_name)) + return (405, + headers, + '{0} not supported. Supported Methods are {1}'.format( + method, self.methods)) + class StackInABoxService(object): DELETE = 'DELETE' @@ -236,7 +246,7 @@ def try_handle_route(self, route_uri, method, request, uri, headers): request, uri, headers) - return (500, headers, 'Server Error') + return (595, headers, 'Route ({0}) Not Handled'.format(uri)) def request(self, method, request, uri, headers): logger.debug('StackInABoxService ({0}:{1}): Request Received {2} - {3}' diff --git a/stackinabox/stack.py b/stackinabox/stack.py index 038a259..9bd6197 100644 --- a/stackinabox/stack.py +++ b/stackinabox/stack.py @@ -69,7 +69,7 @@ def __get_service_url(base_url, service_name): return '{0}/{1}'.format(base_url, service_name) @staticmethod - def __get_services_url(url, base_url): + def get_services_url(url, base_url): length = len(base_url) checks = ['http://', 'https://'] for check in checks: @@ -117,7 +117,7 @@ def register(self, service): if service.name not in self.services.keys(): logger.debug('StackInABox({0}): Registering Service {1}' .format(self.__id, service.name)) - regex = '^/{0}'.format(service.name) + regex = '^/{0}/'.format(service.name) self.services[service.name] = [ re.compile(regex), service @@ -133,7 +133,8 @@ def register(self, service): def call(self, method, request, uri, headers): logger.debug('StackInABox({0}): Received call to {1} - {2}' .format(self.__id, method, uri)) - service_uri = StackInABox.__get_services_url(uri, self.base_url) + service_uri = StackInABox.get_services_url(uri, self.base_url) + for k, v in six.iteritems(self.services): matcher, service = v logger.debug('StackInABox({0}): Checking if Service {1} handles...' @@ -155,10 +156,10 @@ def call(self, method, request, uri, headers): logger.exception('StackInABox({0}): Service {1} - ' 'Internal Failure' .format(self.__id, service.name)) - return (500, + return (596, headers, 'Service Handler had an error: {0}'.format(ex)) - return (500, headers, 'Unknown service') + return (597, headers, 'Unknown service - {0}'.format(service_uri)) def into_hold(self, name, obj): logger.debug('StackInABox({0}): Holding onto {1} of type {2} ' diff --git a/stackinabox/tests/test_httpretty.py b/stackinabox/tests/test_httpretty.py index c704eb3..d1aaeb3 100644 --- a/stackinabox/tests/test_httpretty.py +++ b/stackinabox/tests/test_httpretty.py @@ -73,7 +73,10 @@ def test_basic(self): self.assertEqual(res.text, 'okay') res = requests.get('http://localhost/advanced/_234567890') - self.assertEqual(res.status_code, 500) + self.assertEqual(res.status_code, 595) res = requests.put('http://localhost/advanced/h') - self.assertEqual(res.status_code, 500) + self.assertEqual(res.status_code, 405) + + res = requests.put('http://localhost/advanced2/i') + self.assertEqual(res.status_code, 597) diff --git a/stackinabox/tests/test_requests_mock.py b/stackinabox/tests/test_requests_mock.py index 23894dd..0c78b8c 100644 --- a/stackinabox/tests/test_requests_mock.py +++ b/stackinabox/tests/test_requests_mock.py @@ -87,10 +87,13 @@ def test_basic(self): self.assertEqual(res.text, 'okay') res = self.session.get('http://localhost/advanced/_234567890') - self.assertEqual(res.status_code, 500) + self.assertEqual(res.status_code, 595) res = self.session.put('http://localhost/advanced/h') - self.assertEqual(res.status_code, 500) + self.assertEqual(res.status_code, 405) + + res = self.session.put('http://localhost/advanced2/i') + self.assertEqual(res.status_code, 597) def test_context_requests_mock(self): with stackinabox.util_requests_mock.activate(): diff --git a/stackinabox/tests/test_responses.py b/stackinabox/tests/test_responses.py index 81ba4ed..60c030f 100644 --- a/stackinabox/tests/test_responses.py +++ b/stackinabox/tests/test_responses.py @@ -18,7 +18,6 @@ logger = logging.getLogger(__name__) -@unittest.skipIf(six.PY3, 'Responses fails on PY3') def test_basic_responses(): @responses.activate @@ -34,7 +33,6 @@ def run(): run() -@unittest.skipIf(six.PY3, 'Responses fails on PY3') def test_advanced_responses(): def run(): @@ -65,10 +63,13 @@ def run(): assert res.text == 'okay' res = requests.get('http://localhost/advanced/_234567890') - assert res.status_code == 500 + assert res.status_code == 595 res = requests.put('http://localhost/advanced/h') - assert res.status_code == 500 + assert res.status_code == 405 + + res = requests.put('http://localhost/advanced2/i') + assert res.status_code == 597 StackInABox.reset_services() diff --git a/stackinabox/tests/test_stack.py b/stackinabox/tests/test_stack.py new file mode 100644 index 0000000..7213e40 --- /dev/null +++ b/stackinabox/tests/test_stack.py @@ -0,0 +1,63 @@ +import re +import unittest + +import ddt +import httpretty +import requests + +import stackinabox.util_httpretty +from stackinabox.stack import ( + StackInABox, ServiceAlreadyRegisteredError) +from stackinabox.services.service import * +from stackinabox.services.hello import HelloService + + +class ExceptionalServices(StackInABoxService): + + def __init__(self): + super(ExceptionalServices, self).__init__('except') + self.register(StackInABoxService.GET, + '/', + ExceptionalServices.handler) + + def handler(self, request, uri, headers): + raise Exception('Exceptional Service Failure') + + +@ddt.ddt +class TestStack(unittest.TestCase): + + def setUp(self): + super(TestStack, self).setUp() + + def tearDown(self): + super(TestStack, self).tearDown() + StackInABox.reset_services() + + def test_double_service_registration(self): + service1 = HelloService() + service2 = HelloService() + + StackInABox.register_service(service1) + with self.assertRaises(ServiceAlreadyRegisteredError): + StackInABox.register_service(service2) + + @ddt.data( + ('http://honeymoon/', 'honeymoon', '/'), + ('https://honeymoon/', 'honeymoon', '/'), + ('honeymoon/', 'honeymoon', '/') + ) + @ddt.unpack + def test_get_services_url(self, url, base, value): + result = StackInABox.get_services_url(url, base) + self.assertEqual(result, value) + + @httpretty.activate + def test_service_exception(self): + exceptional = ExceptionalServices() + StackInABox.register_service(exceptional) + + stackinabox.util_httpretty.httpretty_registration('localhost') + + res = requests.get('http://localhost/except/') + self.assertEqual(res.status_code, 596) diff --git a/stackinabox/tests/utils/services.py b/stackinabox/tests/utils/services.py index 9b6fa80..4492a03 100644 --- a/stackinabox/tests/utils/services.py +++ b/stackinabox/tests/utils/services.py @@ -8,6 +8,9 @@ from stackinabox.services.service import StackInABoxService +logger = logging.getLogger(__name__) + + class AdvancedService(StackInABoxService): def __init__(self): diff --git a/stackinabox/util_httpretty.py b/stackinabox/util_httpretty.py index 034f38a..66be11c 100644 --- a/stackinabox/util_httpretty.py +++ b/stackinabox/util_httpretty.py @@ -6,6 +6,8 @@ from httpretty import register_uri from httpretty.http import HttpBaseClass +import httpretty.http +import six from stackinabox.stack import StackInABox from stackinabox.utils import CaseInsensitiveDict @@ -28,6 +30,16 @@ def httpretty_callback(request, uri, headers): def httpretty_registration(uri): + + status_data = { + 595: 'StackInABoxService - Unknown Route', + 596: 'StackInABox - Exception in Service Handler', + 597: 'StackInABox - Unknown Service' + } + for k, v in six.iteritems(status_data): + if k not in httpretty.http.STATUSES: + httpretty.http.STATUSES[k] = v + logger.debug('Registering Stack-In-A-Box at {0} under Python HTTPretty' .format(uri)) StackInABox.update_uri(uri) diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index ff30ee1..1db1984 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -1,9 +1,10 @@ coverage +ddt httpretty==0.8.6 nose nose-exclude pep8 requests requests-mock -responses +responses>=0.4.0 six