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}