From e7e19fa5223147d02fb5a16a6d0275809c5b43fb Mon Sep 17 00:00:00 2001 From: liusheng <liusheng@huawei.com> Date: Tue, 11 Oct 2016 15:39:31 +0800 Subject: [PATCH] Add function tests for nimble API This change add a functional test base for nimble and add some functional tests for instance_type APIs. Change-Id: I44fc4686891d75884860cab86e8c8e42186b5703 --- .testr.conf | 2 +- nimble/tests/functional/__init__.py | 0 .../base.py => functional/api/__init__.py} | 61 ++++++----------- .../{unit => functional}/api/test_root.py | 6 +- nimble/tests/functional/api/v1/__init__.py | 20 ++++++ .../functional/api/v1/test_instance_types.py | 66 +++++++++++++++++++ nimble/tests/unit/api/test_hooks.py | 15 +++-- tox.ini | 6 +- 8 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 nimble/tests/functional/__init__.py rename nimble/tests/{unit/api/base.py => functional/api/__init__.py} (82%) rename nimble/tests/{unit => functional}/api/test_root.py (87%) create mode 100644 nimble/tests/functional/api/v1/__init__.py create mode 100644 nimble/tests/functional/api/v1/test_instance_types.py diff --git a/.testr.conf b/.testr.conf index 6d83b3c4..afee0a77 100644 --- a/.testr.conf +++ b/.testr.conf @@ -2,6 +2,6 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover ${OS_TEST_PATH:-./nimble/tests} -t . $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/nimble/tests/functional/__init__.py b/nimble/tests/functional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/nimble/tests/unit/api/base.py b/nimble/tests/functional/api/__init__.py similarity index 82% rename from nimble/tests/unit/api/base.py rename to nimble/tests/functional/api/__init__.py index aada4bc7..e2039778 100644 --- a/nimble/tests/unit/api/base.py +++ b/nimble/tests/functional/api/__init__.py @@ -16,19 +16,14 @@ # under the License. """Base classes for API tests.""" -# NOTE: Ported from ceilometer/tests/api.py (subsequently moved to -# ceilometer/tests/api/__init__.py). This should be oslo'ified: -# https://bugs.launchpad.net/ironic/+bug/1255115. from oslo_config import cfg import pecan import pecan.testing -from six.moves.urllib import parse as urlparse +from nimble import objects from nimble.tests.unit.db import base -PATH_PREFIX = '/v1' - cfg.CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token') @@ -40,7 +35,7 @@ class BaseApiTest(base.DbTestCase): framework. """ - SOURCE_DATA = {'test_source': {'somekey': '666'}} + PATH_PREFIX = '' def setUp(self): super(BaseApiTest, self).setUp() @@ -48,6 +43,8 @@ class BaseApiTest(base.DbTestCase): group='keystone_authtoken') cfg.CONF.set_override("admin_user", "admin", group='keystone_authtoken') + + objects.register_all() self.app = self._make_app() def reset_pecan(): @@ -65,14 +62,13 @@ class BaseApiTest(base.DbTestCase): 'modules': ['nimble.api'], 'static_root': '%s/public' % root_dir, 'template_path': '%s/api/templates' % root_dir, - 'acl_public_routes': ['/', '/v1'], + 'acl_public_routes': ['/', '/v1/.*'], }, } return pecan.testing.load_test_app(self.app_config) def _request_json(self, path, params, expect_errors=False, headers=None, - method="post", extra_environ=None, status=None, - path_prefix=PATH_PREFIX): + method="post", extra_environ=None, status=None): """Sends simulated HTTP request to Pecan test app. :param path: url path of target service @@ -87,17 +83,16 @@ class BaseApiTest(base.DbTestCase): :param status: expected status code of response :param path_prefix: prefix of the url path """ - full_path = path_prefix + path - print('%s: %s %s' % (method.upper(), full_path, params)) response = getattr(self.app, "%s_json" % method)( - str(full_path), + str(path), params=params, headers=headers, status=status, extra_environ=extra_environ, expect_errors=expect_errors ) - print('GOT:%s' % response) + if not expect_errors: + response = response.json return response def put_json(self, path, params, expect_errors=False, headers=None, @@ -113,7 +108,8 @@ class BaseApiTest(base.DbTestCase): with the request :param status: expected status code of response """ - return self._request_json(path=path, params=params, + full_path = self.PATH_PREFIX + path + return self._request_json(path=full_path, params=params, expect_errors=expect_errors, headers=headers, extra_environ=extra_environ, status=status, method="put") @@ -131,7 +127,8 @@ class BaseApiTest(base.DbTestCase): with the request :param status: expected status code of response """ - return self._request_json(path=path, params=params, + full_path = self.PATH_PREFIX + path + return self._request_json(path=full_path, params=params, expect_errors=expect_errors, headers=headers, extra_environ=extra_environ, status=status, method="post") @@ -149,13 +146,14 @@ class BaseApiTest(base.DbTestCase): with the request :param status: expected status code of response """ - return self._request_json(path=path, params=params, + full_path = self.PATH_PREFIX + path + return self._request_json(path=full_path, params=params, expect_errors=expect_errors, headers=headers, extra_environ=extra_environ, status=status, method="patch") def delete(self, path, expect_errors=False, headers=None, - extra_environ=None, status=None, path_prefix=PATH_PREFIX): + extra_environ=None, status=None): """Sends simulated HTTP DELETE request to Pecan test app. :param path: url path of target service @@ -167,18 +165,16 @@ class BaseApiTest(base.DbTestCase): :param status: expected status code of response :param path_prefix: prefix of the url path """ - full_path = path_prefix + path - print('DELETE: %s' % (full_path)) + full_path = self.PATH_PREFIX + path response = self.app.delete(str(full_path), headers=headers, status=status, extra_environ=extra_environ, expect_errors=expect_errors) - print('GOT:%s' % response) return response def get_json(self, path, expect_errors=False, headers=None, - extra_environ=None, q=[], path_prefix=PATH_PREFIX, **params): + extra_environ=None, q=[], **params): """Sends simulated HTTP GET request to Pecan test app. :param path: url path of target service @@ -192,7 +188,7 @@ class BaseApiTest(base.DbTestCase): :param path_prefix: prefix of the url path :param params: content for wsgi.input of request """ - full_path = path_prefix + path + full_path = self.PATH_PREFIX + path query_params = {'q.field': [], 'q.value': [], 'q.op': [], @@ -204,7 +200,6 @@ class BaseApiTest(base.DbTestCase): all_params.update(params) if q: all_params.update(query_params) - print('GET: %s %r' % (full_path, all_params)) response = self.app.get(full_path, params=all_params, headers=headers, @@ -212,22 +207,4 @@ class BaseApiTest(base.DbTestCase): expect_errors=expect_errors) if not expect_errors: response = response.json - print('GOT:%s' % response) return response - - def validate_link(self, link, bookmark=False): - """Checks if the given link can get correct data.""" - # removes the scheme and net location parts of the link - url_parts = list(urlparse.urlparse(link)) - url_parts[0] = url_parts[1] = '' - - # bookmark link should not have the version in the URL - if bookmark and url_parts[2].startswith(PATH_PREFIX): - return False - - full_path = urlparse.urlunparse(url_parts) - try: - self.get_json(full_path, path_prefix='') - return True - except Exception: - return False diff --git a/nimble/tests/unit/api/test_root.py b/nimble/tests/functional/api/test_root.py similarity index 87% rename from nimble/tests/unit/api/test_root.py rename to nimble/tests/functional/api/test_root.py index ad1c43fa..f0673243 100644 --- a/nimble/tests/unit/api/test_root.py +++ b/nimble/tests/functional/api/test_root.py @@ -13,13 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. -from nimble.tests.unit.api import base +from nimble.tests.functional import api -class TestRoot(base.BaseApiTest): +class TestRoot(api.BaseApiTest): def test_get_root(self): - response = self.get_json('/', path_prefix='') + response = self.get_json('/') # Check fields are not empty [self.assertNotIn(f, ['', []]) for f in response] diff --git a/nimble/tests/functional/api/v1/__init__.py b/nimble/tests/functional/api/v1/__init__.py new file mode 100644 index 00000000..faf186b7 --- /dev/null +++ b/nimble/tests/functional/api/v1/__init__.py @@ -0,0 +1,20 @@ +# +# Copyright 2016 Huawei Technologies Co., Ltd. +# +# 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 nimble.tests.functional import api + + +class APITestV1(api.BaseApiTest): + PATH_PREFIX = '/v1' diff --git a/nimble/tests/functional/api/v1/test_instance_types.py b/nimble/tests/functional/api/v1/test_instance_types.py new file mode 100644 index 00000000..079ff692 --- /dev/null +++ b/nimble/tests/functional/api/v1/test_instance_types.py @@ -0,0 +1,66 @@ +# +# Copyright 2016 Huawei Technologies Co., Ltd. +# +# 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 mock + +from nimble.tests.functional.api import v1 as v1_test + + +class TestInstanceType(v1_test.APITestV1): + + TYPE_UUIDS = ['ff28b5a2-73e5-431c-b4b7-1b96b74bca7b', + '94baf80e-2bae-4c3e-8dab-38b5441e7097', + '4bcfff85-c55a-493b-b544-b6c90b8fb397', + 'e50fb289-4ee1-47dd-b371-6ca39af12888'] + + def setUp(self): + super(TestInstanceType, self).setUp() + + @mock.patch('oslo_utils.uuidutils.generate_uuid') + def _prepare_instance_types(self, mocked): + mocked.side_effect = self.TYPE_UUIDS + for i in xrange(4): + body = {"name": "test" + str(i), + "description": "just test" + str(i)} + self.post_json('/types', body, status=201) + + def test_instance_type_post(self): + body = {"name": "test", "description": "just test"} + resp = self.post_json('/types', body, status=201) + self.assertEqual('test', resp['name']) + self.assertEqual('just test', resp['description']) + self.assertEqual(True, resp['is_public']) + self.assertIn('uuid', resp) + self.assertIn('extra_specs', resp) + self.assertIn('links', resp) + + def test_instance_type_get_all(self): + self._prepare_instance_types() + resp = self.get_json('/types') + self.assertEqual(4, len(resp['types'])) + + def test_instance_type_get_one(self): + self._prepare_instance_types() + resp = self.get_json('/types/' + self.TYPE_UUIDS[0]) + self.assertEqual('test0', resp['name']) + self.assertEqual('just test0', resp['description']) + + def test_instance_type_delete(self): + self._prepare_instance_types() + resp = self.get_json('/types') + self.assertEqual(4, len(resp['types'])) + self.delete('/types/' + self.TYPE_UUIDS[0], status=204) + resp = self.get_json('/types') + self.assertEqual(3, len(resp['types'])) diff --git a/nimble/tests/unit/api/test_hooks.py b/nimble/tests/unit/api/test_hooks.py index 45bcda72..936626af 100644 --- a/nimble/tests/unit/api/test_hooks.py +++ b/nimble/tests/unit/api/test_hooks.py @@ -20,7 +20,7 @@ from oslo_config import cfg from nimble.api import hooks from nimble.common import context -from nimble.tests.unit.api import base +from nimble.tests import base class FakeRequest(object): @@ -91,7 +91,7 @@ def fake_headers(admin=False): return headers -class TestContextHook(base.BaseApiTest): +class TestContextHook(base.BaseTestCase): @mock.patch.object(context, 'RequestContext') def test_context_hook(self, mock_ctx): headers = fake_headers(admin=True) @@ -142,13 +142,16 @@ class TestContextHook(base.BaseApiTest): reqstate.response.headers['Openstack-Request-Id']) def test_context_hook_after_miss_context(self): - response = self.get_json('/bad/path', - expect_errors=True) + headers = fake_headers(admin=True) + reqstate = FakeRequestState(headers=headers) + reqstate.request.context = {} + context_hook = hooks.ContextHook(None) + context_hook.after(reqstate) self.assertNotIn('Openstack-Request-Id', - response.headers) + reqstate.response.headers) -class TestPublicUrlHook(base.BaseApiTest): +class TestPublicUrlHook(base.BaseTestCase): def test_before_host_url(self): headers = fake_headers() diff --git a/tox.ini b/tox.ini index 2dda524d..72736466 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.0 -envlist = py35-constraints,py34-constraints,py27-constraints,pypy-constraints,pep8-constraints +envlist = py35-constraints,py34-constraints,py27-constraints,pypy-constraints,pep8-constraints,functional skipsdist = True [testenv] @@ -51,6 +51,10 @@ commands = python setup.py build_sphinx [testenv:debug] commands = oslo_debug_helper {posargs} +[testenv:functional] +setenv = OS_TEST_PATH=nimble/tests/functional/ +commands = python setup.py testr --slowest --testr-args="{posargs}" + [testenv:debug-constraints] install_command = {[testenv:common-constraints]install_command} commands = oslo_debug_helper {posargs}