diff --git a/falcon/api_helpers.py b/falcon/api_helpers.py index 2e5dbdc..a13545e 100644 --- a/falcon/api_helpers.py +++ b/falcon/api_helpers.py @@ -204,18 +204,20 @@ def create_http_method_map(resource, uri_fields, before, after): # Attach a resource for unsupported HTTP methods allowed_methods = sorted(list(method_map.keys())) + # NOTE(sebasmagri): We want the OPTIONS and 405 (Not Allowed) methods + # responders to be wrapped on global hooks if 'OPTIONS' not in method_map: # OPTIONS itself is intentionally excluded from the Allow header - # This default responder does not run the hooks - method_map['OPTIONS'] = responders.create_default_options( + responder = responders.create_default_options( allowed_methods) + method_map['OPTIONS'] = _wrap_with_hooks(before, after, responder) allowed_methods.append('OPTIONS') na_responder = responders.create_method_not_allowed(allowed_methods) for method in HTTP_METHODS: if method not in allowed_methods: - method_map[method] = na_responder + method_map[method] = _wrap_with_hooks(before, after, na_responder) return method_map diff --git a/tests/test_after_hooks.py b/tests/test_after_hooks.py index f5a4d92..f46e839 100644 --- a/tests/test_after_hooks.py +++ b/tests/test_after_hooks.py @@ -25,6 +25,14 @@ def cuteness(req, resp): resp.body += ' and cute' +def fluffiness_in_the_head(req, resp): + resp.set_header('X-Fluffiness', 'fluffy') + + +def cuteness_in_the_head(req, resp): + resp.set_header('X-Cuteness', 'cute') + + class WrappedRespondersResource(object): @falcon.after(serialize_body) @@ -93,10 +101,6 @@ class TestHooks(testing.TestBase): self.simulate_request(self.test_route) self.assertEqual(b'fluffy', zoo_resource.resp.body_encoded) - # hook does not affect the default on_options - body = self.simulate_request(self.test_route, method='OPTIONS') - self.assertEqual(falcon.HTTP_204, self.srmock.status) - self.assertEqual([], body) def test_multiple_global_hook(self): self.api = falcon.API(after=[fluffiness, cuteness]) @@ -107,6 +111,55 @@ class TestHooks(testing.TestBase): self.simulate_request(self.test_route) self.assertEqual(b'fluffy and cute', zoo_resource.resp.body_encoded) + def test_global_hook_wrap_default_on_options(self): + self.api = falcon.API(after=fluffiness_in_the_head) + zoo_resource = ZooResource() + + self.api.add_route(self.test_route, zoo_resource) + + self.simulate_request(self.test_route, method='OPTIONS') + + self.assertEqual(falcon.HTTP_204, self.srmock.status) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Fluffiness']) + + def test_global_hook_wrap_default_405(self): + self.api = falcon.API(after=fluffiness_in_the_head) + zoo_resource = ZooResource() + + self.api.add_route(self.test_route, zoo_resource) + + self.simulate_request(self.test_route, method='POST') + + self.assertEqual(falcon.HTTP_405, self.srmock.status) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Fluffiness']) + + def test_multiple_global_hooks_wrap_default_on_options(self): + self.api = falcon.API(after=[fluffiness_in_the_head, + cuteness_in_the_head]) + zoo_resource = ZooResource() + + self.api.add_route(self.test_route, zoo_resource) + + self.simulate_request(self.test_route, method='OPTIONS') + + self.assertEqual(falcon.HTTP_204, self.srmock.status) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Fluffiness']) + self.assertEqual('cute', self.srmock.headers_dict['X-Cuteness']) + + def test_multiple_global_hooks_wrap_default_405(self): + self.api = falcon.API(after=[fluffiness_in_the_head, + cuteness_in_the_head]) + zoo_resource = ZooResource() + + self.api.add_route(self.test_route, zoo_resource) + + self.simulate_request(self.test_route, method='POST') + + self.assertEqual(falcon.HTTP_405, self.srmock.status) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Fluffiness']) + self.assertEqual('cute', self.srmock.headers_dict['X-Cuteness']) + + def test_output_validator(self): self.simulate_request(self.test_route) self.assertEqual(falcon.HTTP_723, self.srmock.status) diff --git a/tests/test_before_hooks.py b/tests/test_before_hooks.py index 2e51750..bd41364 100644 --- a/tests/test_before_hooks.py +++ b/tests/test_before_hooks.py @@ -40,6 +40,14 @@ def frogs(req, resp, params): params['frogs'] = 'not fluffy' +def bunnies_in_the_head(req, resp, params): + resp.set_header('X-Bunnies', 'fluffy') + + +def frogs_in_the_head(req, resp, params): + resp.set_header('X-Frogs', 'not fluffy') + + class WrappedRespondersResource(object): @falcon.before(validate_param) @@ -128,6 +136,48 @@ class TestHooks(testing.TestBase): self.assertEqual('fluffy', zoo_resource.bunnies) self.assertEqual('not fluffy', zoo_resource.frogs) + def test_global_hook_wrap_default_on_options(self): + self.api = falcon.API(before=frogs_in_the_head) + bunny_resource = BunnyResource() + + self.api.add_route(self.test_route, bunny_resource) + + self.simulate_request(self.test_route, method='OPTIONS') + self.assertEqual(falcon.HTTP_204, self.srmock.status) + self.assertEqual('not fluffy', self.srmock.headers_dict['X-Frogs']) + + def test_global_hook_wrap_default_405(self): + self.api = falcon.API(before=[frogs_in_the_head]) + bunny_resource = BunnyResource() + + self.api.add_route(self.test_route, bunny_resource) + + # on_post is not defined in ZooResource + self.simulate_request(self.test_route, method='POST') + self.assertEqual(falcon.HTTP_405, self.srmock.status) + self.assertEqual('not fluffy', self.srmock.headers_dict['X-Frogs']) + + def test_multiple_global_hooks_wrap_default_on_options(self): + self.api = falcon.API(before=[frogs_in_the_head, bunnies_in_the_head]) + bunny_resource = BunnyResource() + + self.api.add_route(self.test_route, bunny_resource) + + self.simulate_request(self.test_route, method='OPTIONS') + self.assertEqual('not fluffy', self.srmock.headers_dict['X-Frogs']) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Bunnies']) + + def test_multiple_global_hooks_wrap_default_405(self): + self.api = falcon.API(before=[frogs_in_the_head, bunnies_in_the_head]) + bunny_resource = BunnyResource() + + self.api.add_route(self.test_route, bunny_resource) + + # on_post is not defined in ZooResource + self.simulate_request(self.test_route, method='POST') + self.assertEqual('not fluffy', self.srmock.headers_dict['X-Frogs']) + self.assertEqual('fluffy', self.srmock.headers_dict['X-Bunnies']) + def test_input_validator(self): self.simulate_request(self.test_route, method='PUT') self.assertEqual(falcon.HTTP_400, self.srmock.status)