diff --git a/ironic/tests/api/__init__.py b/ironic/tests/api/__init__.py new file mode 100644 index 0000000000..56425d0fce --- /dev/null +++ b/ironic/tests/api/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. diff --git a/ironic/tests/api/base.py b/ironic/tests/api/base.py new file mode 100644 index 0000000000..133973dc03 --- /dev/null +++ b/ironic/tests/api/base.py @@ -0,0 +1,124 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. +"""Base classes for API tests.""" + +# NOTE: Ported from ceilometer/tests/api.py +# https://bugs.launchpad.net/ceilometer/+bug/1193666 + +from oslo.config import cfg +import pecan +import pecan.testing + +from ironic.api import acl +from ironic.tests.db import base + + +class FunctionalTest(base.DbTestCase): + """Used for functional tests of Pecan controllers where you need to + test your literal application and its integration with the + framework. + """ + + PATH_PREFIX = '/v1' + + SOURCE_DATA = {'test_source': {'somekey': '666'}} + + def setUp(self): + super(FunctionalTest, self).setUp() + cfg.CONF.set_override("auth_version", "v2.0", group=acl.OPT_GROUP_NAME) + cfg.CONF.set_override("policy_file", + self.path_get('tests/policy.json')) + self.app = self._make_app() + + def _make_app(self, enable_acl=False): + # Determine where we are so we can set up paths in the config + root_dir = self.path_get() + + self.config = { + 'app': { + 'root': 'ironic.api.controllers.root.RootController', + 'modules': ['ironic.api'], + 'static_root': '%s/public' % root_dir, + 'template_path': '%s/api/templates' % root_dir, + 'enable_acl': enable_acl, + }, + } + + return pecan.testing.load_test_app(self.config) + + def tearDown(self): + super(FunctionalTest, self).tearDown() + pecan.set_config({}, overwrite=True) + + def put_json(self, path, params, expect_errors=False, headers=None, + extra_environ=None, status=None): + return self.post_json(path=path, params=params, + expect_errors=expect_errors, + headers=headers, extra_environ=extra_environ, + status=status, method="put") + + def post_json(self, path, params, expect_errors=False, headers=None, + method="post", extra_environ=None, status=None): + full_path = self.PATH_PREFIX + path + print('%s: %s %s' % (method.upper(), full_path, params)) + response = getattr(self.app, "%s_json" % method)( + str(full_path), + params=params, + headers=headers, + status=status, + extra_environ=extra_environ, + expect_errors=expect_errors + ) + print('GOT:%s' % response) + return response + + def delete(self, path, expect_errors=False, headers=None, + extra_environ=None, status=None): + full_path = self.PATH_PREFIX + path + print('DELETE: %s' % (full_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=[], **params): + full_path = self.PATH_PREFIX + path + query_params = {'q.field': [], + 'q.value': [], + 'q.op': [], + } + for query in q: + for name in ['field', 'op', 'value']: + query_params['q.%s' % name].append(query.get(name, '')) + all_params = {} + 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, + extra_environ=extra_environ, + expect_errors=expect_errors) + if not expect_errors: + response = response.json + print('GOT:%s' % response) + return response diff --git a/ironic/tests/api/test_base.py b/ironic/tests/api/test_base.py new file mode 100644 index 0000000000..eff0505b70 --- /dev/null +++ b/ironic/tests/api/test_base.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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 ironic.tests.api import base + + +class TestBase(base.FunctionalTest): + + def test_api_setup(self): + pass + + def test_bad_uri(self): + response = self.get_json('/bad/path', + expect_errors=True, + headers={"Accept": "application/json"}) + self.assertEqual(response.status_int, 404) + self.assertEqual(response.content_type, "application/json") + self.assertTrue(response.json['error_message']) diff --git a/ironic/tests/api/test_list_nodes.py b/ironic/tests/api/test_list_nodes.py new file mode 100644 index 0000000000..46f960ff83 --- /dev/null +++ b/ironic/tests/api/test_list_nodes.py @@ -0,0 +1,24 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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 ironic.tests.api import base + + +class TestListNodes(base.FunctionalTest): + def test_empty(self): + data = self.get_json('/nodes') + self.assertEqual([], data) diff --git a/ironic/tests/base.py b/ironic/tests/base.py index 80a47543ac..53fe4a8fd3 100644 --- a/ironic/tests/base.py +++ b/ironic/tests/base.py @@ -203,6 +203,22 @@ class TestCase(testtools.TestCase): for k, v in kw.iteritems(): CONF.set_override(k, v, group) + def path_get(self, project_file=None): + """Get the absolute path to a file. Used for testing the API. + + :param project_file: File whose path to return. Default: None. + :returns: path to the specified file, or path to project root. + """ + root = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', + '..', + ) + ) + if project_file: + return os.path.join(root, project_file) + else: + return root + class APICoverage(object): diff --git a/ironic/tests/policy.json b/ironic/tests/policy.json new file mode 100644 index 0000000000..b9458e8fe8 --- /dev/null +++ b/ironic/tests/policy.json @@ -0,0 +1,6 @@ +{ + "admin_api": "is_admin:True", + "admin_or_owner": "is_admin:True or project_id:%(project_id)s", + "context_is_admin": "role:admin", + "default": "rule:admin_or_owner", +}