diff options
| author | Rajaram Mallya <rajarammallya@gmail.com> | 2011-09-07 17:07:19 +0530 |
|---|---|---|
| committer | Rajaram Mallya <rajarammallya@gmail.com> | 2011-09-07 17:07:19 +0530 |
| commit | 2a529c6cb7cfed08a7afe8fc0f8249bc9bdb2621 (patch) | |
| tree | cfd35e1300ccfba21740cb6aacd2f3bd607530bf /tests | |
| parent | 96b9a548521cefb7c1b7dd5229c89b8fec53de85 (diff) | |
Vinkesh/Rajaram|Added nova's extension framework into common and tests for it
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/unit/extension_stubs.py | 53 | ||||
| -rw-r--r-- | tests/unit/extensions/__init__.py | 15 | ||||
| -rw-r--r-- | tests/unit/extensions/foxinsocks.py | 102 | ||||
| -rw-r--r-- | tests/unit/test_extensions.py | 374 |
4 files changed, 544 insertions, 0 deletions
diff --git a/tests/unit/extension_stubs.py b/tests/unit/extension_stubs.py new file mode 100644 index 0000000..0352614 --- /dev/null +++ b/tests/unit/extension_stubs.py @@ -0,0 +1,53 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. +# 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. + +from openstack.common import wsgi + + +class StubExtension(object): + + def __init__(self, alias="stub_extension"): + self.alias = alias + + def get_name(self): + return "Stub Extension" + + def get_alias(self): + return self.alias + + def get_description(self): + return "" + + def get_namespace(self): + return "" + + def get_updated(self): + return "" + + +class StubBaseAppController(object): + + def index(self, request): + return "base app index" + + def show(self, request, id): + return {'fort': 'knox'} + + def update(self, request, id): + return {'uneditable': 'original_value'} + + def create_resource(self): + return wsgi.Resource(self) diff --git a/tests/unit/extensions/__init__.py b/tests/unit/extensions/__init__.py new file mode 100644 index 0000000..848908a --- /dev/null +++ b/tests/unit/extensions/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# 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. diff --git a/tests/unit/extensions/foxinsocks.py b/tests/unit/extensions/foxinsocks.py new file mode 100644 index 0000000..a0efd7e --- /dev/null +++ b/tests/unit/extensions/foxinsocks.py @@ -0,0 +1,102 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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 json + +from openstack.common import extensions +from openstack.common import wsgi + + +class FoxInSocksController(object): + + def index(self, request): + return "Try to say this Mr. Knox, sir..." + + def create_resource(self): + return wsgi.Resource(self) + + +class Foxinsocks(object): + + def __init__(self): + pass + + def get_name(self): + return "Fox In Socks" + + def get_alias(self): + return "FOXNSOX" + + def get_description(self): + return "The Fox In Socks Extension" + + def get_namespace(self): + return "http://www.fox.in.socks/api/ext/pie/v1.0" + + def get_updated(self): + return "2011-01-22T13:25:27-06:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('foxnsocks', + FoxInSocksController()) + resources.append(resource) + return resources + + def get_actions(self): + return [extensions.ActionExtension('dummy_resources', + 'FOXNSOX:add_tweedle', + self._add_tweedle_handler), + extensions.ActionExtension('dummy_resources', + 'FOXNSOX:delete_tweedle', + self._delete_tweedle_handler)] + + def get_request_extensions(self): + request_exts = [] + + def _goose_handler(req, res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['FOXNSOX:googoose'] = req.GET.get('chewing') + res.body = json.dumps(data) + return res + + req_ext1 = extensions.RequestExtension('GET', '/dummy_resources/:(id)', + _goose_handler) + request_exts.append(req_ext1) + + def _bands_handler(req, res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['FOXNSOX:big_bands'] = 'Pig Bands!' + res.body = json.dumps(data) + return res + + req_ext2 = extensions.RequestExtension('GET', '/dummy_resources/:(id)', + _bands_handler) + request_exts.append(req_ext2) + return request_exts + + def _add_tweedle_handler(self, input_dict, req, id): + return "Tweedle {0} Added.".format( + input_dict['FOXNSOX:add_tweedle']['name']) + + def _delete_tweedle_handler(self, input_dict, req, id): + return "Tweedle {0} Deleted.".format( + input_dict['FOXNSOX:delete_tweedle']['name']) diff --git a/tests/unit/test_extensions.py b/tests/unit/test_extensions.py new file mode 100644 index 0000000..a234b15 --- /dev/null +++ b/tests/unit/test_extensions.py @@ -0,0 +1,374 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. +# 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 json +import os.path +import routes +import unittest +from webtest import TestApp + +from openstack.common import wsgi +from openstack.common import config +from openstack.common import extensions +from tests.unit.extension_stubs import (StubExtension, + StubBaseAppController) +from openstack.common.extensions import (ExtensionManager, + ExtensionMiddleware) + + +test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir, + os.pardir, 'etc', 'openstack-common.conf.test') +extensions_path = os.path.join(os.path.dirname(__file__), "extensions") + + +class ExtensionsTestApp(wsgi.Router): + + def __init__(self, options={}): + mapper = routes.Mapper() + controller = StubBaseAppController() + mapper.resource("dummy_resource", "/dummy_resources", + controller=controller.create_resource()) + super(ExtensionsTestApp, self).__init__(mapper) + + +class ResourceExtensionTest(unittest.TestCase): + + class ResourceExtensionController(object): + + def index(self, request): + return "resource index" + + def show(self, request, id): + return {'data': {'id': id}} + + def custom_member_action(self, request, id): + return {'member_action': 'value'} + + def custom_collection_action(self, request, **kwargs): + return {'collection': 'value'} + + def test_resource_can_be_added_as_extension(self): + res_ext = extensions.ResourceExtension('tweedles', + self.ResourceExtensionController()) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + index_response = test_app.get("/tweedles") + self.assertEqual(200, index_response.status_int) + self.assertEqual("resource index", index_response.json) + + show_response = test_app.get("/tweedles/25266") + self.assertEqual({'data': {'id': "25266"}}, show_response.json) + + def test_resource_extension_with_custom_member_action(self): + controller = self.ResourceExtensionController() + member = {'custom_member_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + member_actions=member) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/tweedles/some_id/custom_member_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['member_action'], "value") + + def skip_resource_extension_for_get_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "PUT"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.put("/tweedles/custom_collection_action") + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + + def skip_resource_extension_for_put_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "PUT"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.put("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def test_resource_extension_for_post_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "POST"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.post("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def skip_resource_extension_for_delete_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "DELETE"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.delete("/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], 'value') + + def test_resource_ext_for_formatted_req_on_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "GET"} + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/tweedles/custom_collection_action.json") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + + def test_resource_ext_for_nested_resource_custom_collection_action(self): + controller = self.ResourceExtensionController() + collections = {'custom_collection_action': "GET"} + parent = dict(collection_name='beetles', member_name='beetle') + res_ext = extensions.ResourceExtension('tweedles', controller, + collection_actions=collections, + parent=parent) + test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext)) + + response = test_app.get("/beetles/beetle_id" + "/tweedles/custom_collection_action") + + self.assertEqual(200, response.status_int) + self.assertEqual(json.loads(response.body)['collection'], "value") + + def test_returns_404_for_non_existant_extension(self): + test_app = setup_extensions_test_app(SimpleExtensionManager(None)) + + response = test_app.get("/non_extistant_extension", status='*') + + self.assertEqual(404, response.status_int) + + +class ActionExtensionTest(unittest.TestCase): + + def setUp(self): + super(ActionExtensionTest, self).setUp() + self.extension_app = setup_extensions_test_app() + + def test_extended_action_for_adding_extra_data(self): + action_name = 'FOXNSOX:add_tweedle' + action_params = dict(name='Beetle') + req_body = json.dumps({action_name: action_params}) + response = self.extension_app.post('/dummy_resources/1/action', + req_body, content_type='application/json') + + self.assertEqual("Tweedle Beetle Added.", response.json) + + def test_extended_action_for_deleting_extra_data(self): + action_name = 'FOXNSOX:delete_tweedle' + action_params = dict(name='Bailey') + req_body = json.dumps({action_name: action_params}) + response = self.extension_app.post("/dummy_resources/1/action", + req_body, content_type='application/json') + self.assertEqual("Tweedle Bailey Deleted.", response.json) + + def test_returns_404_for_non_existant_action(self): + non_existant_action = 'blah_action' + action_params = dict(name="test") + req_body = json.dumps({non_existant_action: action_params}) + + response = self.extension_app.post("/dummy_resources/1/action", + req_body, content_type='application/json', + status='*') + + self.assertEqual(404, response.status_int) + + def test_returns_404_for_non_existant_resource(self): + action_name = 'add_tweedle' + action_params = dict(name='Beetle') + req_body = json.dumps({action_name: action_params}) + + response = self.extension_app.post("/asdf/1/action", req_body, + content_type='application/json', status='*') + self.assertEqual(404, response.status_int) + + +class RequestExtensionTest(unittest.TestCase): + + def test_headers_can_be_extended(self): + def extend_headers(req, res): + assert req.headers['X-NEW-REQUEST-HEADER'] == "sox" + res.headers['X-NEW-RESPONSE-HEADER'] = "response_header_data" + return res + + app = self._setup_app_with_request_handler(extend_headers, 'GET') + response = app.get("/dummy_resources/1", + headers={'X-NEW-REQUEST-HEADER': "sox"}) + + self.assertEqual(response.headers['X-NEW-RESPONSE-HEADER'], + "response_header_data") + + def test_extend_get_resource_response(self): + def extend_response_data(req, res): + data = json.loads(res.body) + data['FOXNSOX:extended_key'] = req.GET.get('extended_key') + res.body = json.dumps(data) + return res + + app = self._setup_app_with_request_handler(extend_response_data, 'GET') + response = app.get("/dummy_resources/1?extended_key=extended_data") + + self.assertEqual(200, response.status_int) + + response_data = json.loads(response.body) + self.assertEqual('extended_data', + response_data['FOXNSOX:extended_key']) + self.assertEqual('knox', response_data['fort']) + + def test_get_resources(self): + app = setup_extensions_test_app() + + response = app.get("/dummy_resources/1?chewing=newblue") + + response_data = json.loads(response.body) + self.assertEqual('newblue', response_data['FOXNSOX:googoose']) + self.assertEqual("Pig Bands!", response_data['FOXNSOX:big_bands']) + + def test_edit_previously_uneditable_field(self): + + def _update_handler(req, res): + data = json.loads(res.body) + data['uneditable'] = req.params['uneditable'] + res.body = json.dumps(data) + return res + + base_app = TestApp(setup_base_app()) + response = base_app.put("/dummy_resources/1", + {'uneditable': "new_value"}) + self.assertEqual(response.json['uneditable'], "original_value") + + ext_app = self._setup_app_with_request_handler(_update_handler, + 'PUT') + ext_response = ext_app.put("/dummy_resources/1", + {'uneditable': "new_value"}) + self.assertEqual(ext_response.json['uneditable'], "new_value") + + def _setup_app_with_request_handler(self, handler, verb): + req_ext = extensions.RequestExtension(verb, + '/dummy_resources/:(id)', handler) + manager = SimpleExtensionManager(None, None, req_ext) + return setup_extensions_test_app(manager) + + +class ExtensionManagerTest(unittest.TestCase): + + def test_invalid_extensions_are_not_registered(self): + + class InvalidExtension(object): + """ + This Extension doesn't implement extension methods : + get_name, get_description, get_namespace and get_updated + """ + def get_alias(self): + return "invalid_extension" + + ext_mgr = ExtensionManager('') + ext_mgr.add_extension(InvalidExtension()) + ext_mgr.add_extension(StubExtension("valid_extension")) + + self.assertTrue('valid_extension' in ext_mgr.extensions) + self.assertFalse('invalid_extension' in ext_mgr.extensions) + + +class ExtensionControllerTest(unittest.TestCase): + + def setUp(self): + super(ExtensionControllerTest, self).setUp() + self.test_app = setup_extensions_test_app() + + def test_index_gets_all_registerd_extensions(self): + response = self.test_app.get("/extensions") + foxnsox = response.json["extensions"][0] + + self.assertEqual(foxnsox["alias"], "FOXNSOX") + self.assertEqual(foxnsox["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") + + def test_extension_can_be_accessed_by_alias(self): + json_response = self.test_app.get("/extensions/FOXNSOX").json + foxnsox_extension = json_response['extension'] + + self.assertEqual(foxnsox_extension["alias"], "FOXNSOX") + self.assertEqual(foxnsox_extension["namespace"], + "http://www.fox.in.socks/api/ext/pie/v1.0") + + def test_show_returns_not_found_for_non_existant_extension(self): + response = self.test_app.get("/extensions/non_existant", status="*") + + self.assertEqual(response.status_int, 404) + + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return ExtensionsTestApp(conf) + + +def setup_base_app(): + options = {'config_file': test_conf_file} + conf, app = config.load_paste_app('extensions_test_app', options, None) + return app + + +def setup_extensions_middleware(extension_manager=None): + extension_manager = (extension_manager or + ExtensionManager(extensions_path)) + options = {'config_file': test_conf_file} + conf, app = config.load_paste_app('extensions_test_app', options, None) + return ExtensionMiddleware(app, extension_manager) + + +def setup_extensions_test_app(extension_manager=None): + return TestApp(setup_extensions_middleware(extension_manager)) + + +class SimpleExtensionManager(object): + + def __init__(self, resource_ext=None, action_ext=None, request_ext=None): + self.resource_ext = resource_ext + self.action_ext = action_ext + self.request_ext = request_ext + + def get_resources(self): + resource_exts = [] + if self.resource_ext: + resource_exts.append(self.resource_ext) + return resource_exts + + def get_actions(self): + action_exts = [] + if self.action_ext: + action_exts.append(self.action_ext) + return action_exts + + def get_request_extensions(self): + request_extensions = [] + if self.request_ext: + request_extensions.append(self.request_ext) + return request_extensions |
