diff --git a/refstack/api/app.py b/refstack/api/app.py index b60576a3..a844dcb5 100644 --- a/refstack/api/app.py +++ b/refstack/api/app.py @@ -23,7 +23,6 @@ from oslo_config import cfg from oslo_log import log from oslo_log import loggers import pecan -from pecan import hooks import webob LOG = log.getLogger(__name__) @@ -71,7 +70,7 @@ CONF.register_opts(API_OPTS, opt_group) log.register_options(CONF) -class JSONErrorHook(hooks.PecanHook): +class JSONErrorHook(pecan.hooks.PecanHook): """ A pecan hook that translates webob HTTP errors into a JSON format. """ @@ -134,7 +133,7 @@ def setup_app(config): debug=CONF.api.app_dev_mode, static_root=static_root, template_path=template_path, - hooks=[JSONErrorHook(), hooks.RequestViewerHook( + hooks=[JSONErrorHook(), pecan.hooks.RequestViewerHook( {'items': ['status', 'method', 'controller', 'path', 'body']}, headers=False, writer=loggers.WritableLogger(LOG, logging.DEBUG) )] diff --git a/refstack/api/controllers/v1.py b/refstack/api/controllers/v1.py index 7e261c38..1d035a37 100644 --- a/refstack/api/controllers/v1.py +++ b/refstack/api/controllers/v1.py @@ -37,11 +37,11 @@ class RestControllerWithValidation(rest.RestController): def __init__(self, validator): self.validator = validator - def get_item(self, item_id): + def get_item(self, item_id): # pragma: no cover """Handler for getting item""" raise NotImplemented - def store_item(self, item_in_json): + def store_item(self, item_in_json): # pragma: no cover """Handler for storing item. Should return new item id""" raise NotImplemented diff --git a/refstack/db/utils.py b/refstack/db/utils.py index 31dd4fa2..40d805be 100644 --- a/refstack/db/utils.py +++ b/refstack/db/utils.py @@ -32,11 +32,11 @@ class PluggableBackend(object): def __get_backend(self): if not self.__backend: backend_name = CONF[self.__pivot] - if backend_name not in self.__backends: + if backend_name not in self.__backends: # pragma: no cover raise Exception('Invalid backend: %s' % backend_name) backend = self.__backends[backend_name] - if isinstance(backend, tuple): + if isinstance(backend, tuple): # pragma: no cover name = backend[0] fromlist = backend[1] else: diff --git a/refstack/tests/unit/test_api.py b/refstack/tests/unit/test_api.py new file mode 100644 index 00000000..b4d84914 --- /dev/null +++ b/refstack/tests/unit/test_api.py @@ -0,0 +1,159 @@ +# Copyright (c) 2015 Mirantis, Inc. +# 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. + +"""Tests for API's controllers""" + +import mock +from oslotest import base + +from refstack.api.controllers import root +from refstack.api.controllers import v1 + + +class RootControllerTestCase(base.BaseTestCase): + + def test_index(self): + controller = root.RootController() + result = controller.index() + self.assertEqual(result, {'Root': 'OK'}) + + +class ResultsControllerTestCase(base.BaseTestCase): + + def setUp(self): + super(ResultsControllerTestCase, self).setUp() + self.validator = mock.Mock() + self.controller = v1.ResultsController(self.validator) + + @mock.patch('refstack.db.get_test') + @mock.patch('refstack.db.get_test_results') + def test_get(self, mock_get_test_res, mock_get_test): + self.validator.assert_id.return_value = True + + test_info = mock.Mock() + test_info.cpid = 'foo' + test_info.created_at = 'bar' + test_info.duration_seconds = 999 + mock_get_test.return_value = test_info + + mock_get_test_res.return_value = [('test1',), ('test2',), ('test3',)] + + actual_result = self.controller.get_one('fake_arg') + expected_result = { + 'cpid': 'foo', + 'created_at': 'bar', + 'duration_seconds': 999, + 'results': ['test1', 'test2', 'test3'] + } + + self.assertEqual(actual_result, expected_result) + mock_get_test_res.assert_called_once_with('fake_arg') + mock_get_test.assert_called_once_with('fake_arg') + self.validator.assert_id.assert_called_once_with('fake_arg') + + @mock.patch('refstack.db.store_results') + @mock.patch('pecan.response') + @mock.patch('refstack.common.validators.safe_load_json_body') + def test_post(self, mock_safe_load, mock_response, mock_store_results): + mock_safe_load.return_value = 'fake_item' + mock_store_results.return_value = 'fake_test_id' + + result = self.controller.post() + + self.assertEqual(result, {'test_id': 'fake_test_id'}) + self.assertEqual(mock_response.status, 201) + mock_safe_load.assert_called_once_with(self.validator) + mock_store_results.assert_called_once_with('fake_item') + + @mock.patch('pecan.abort') + @mock.patch('refstack.db.get_test') + def test_get_item_failed(self, mock_get_test, mock_abort): + mock_get_test.return_value = None + mock_abort.side_effect = Exception() + self.assertRaises(Exception, + self.controller.get_item, + 'fake_id') + + @mock.patch('refstack.db.get_test') + @mock.patch('refstack.db.get_test_results') + def test_get_item(self, mock_get_test_res, mock_get_test): + test_info = mock.Mock() + test_info.cpid = 'foo' + test_info.created_at = 'bar' + test_info.duration_seconds = 999 + mock_get_test.return_value = test_info + + mock_get_test_res.return_value = [('test1',), ('test2',), ('test3',)] + + actual_result = self.controller.get_item('fake_id') + expected_result = { + 'cpid': 'foo', + 'created_at': 'bar', + 'duration_seconds': 999, + 'results': ['test1', 'test2', 'test3'] + } + self.assertEqual(actual_result, expected_result) + mock_get_test_res.assert_called_once_with('fake_id') + mock_get_test.assert_called_once_with('fake_id') + + @mock.patch('refstack.db.store_results') + def test_store_item(self, mock_store_item): + mock_store_item.return_value = 'fake_result' + result = self.controller.store_item('fake_item') + self.assertEqual(result, {'test_id': 'fake_result'}) + mock_store_item.assert_called_once_with('fake_item') + + +class RestControllerWithValidationTestCase(base.BaseTestCase): + + def setUp(self): + super(RestControllerWithValidationTestCase, self).setUp() + self.validator = mock.Mock() + self.controller = v1.RestControllerWithValidation(self.validator) + + @mock.patch('pecan.response') + @mock.patch('refstack.common.validators.safe_load_json_body') + def test_post(self, mock_safe_load, mock_response): + mock_safe_load.return_value = 'fake_item' + self.controller.store_item = mock.Mock(return_value='fake_id') + + result = self.controller.post() + + self.assertEqual(result, 'fake_id') + self.assertEqual(mock_response.status, 201) + mock_safe_load.assert_called_once_with(self.validator) + self.controller.store_item.assert_called_once_with('fake_item') + + def test_get_one_return_item(self): + self.validator.assert_id.return_value = True + self.controller.get_item = mock.Mock(return_value='fake_item') + + result = self.controller.get_one('fake_arg') + + self.assertEqual(result, 'fake_item') + self.validator.assert_id.assert_called_once_with('fake_arg') + self.controller.get_item.assert_called_once_with(item_id='fake_arg') + + def test_get_one_return_schema(self): + self.validator.assert_id.return_value = False + self.validator.schema = 'fake_schema' + result = self.controller.get_one('schema') + self.assertEqual(result, 'fake_schema') + + @mock.patch('pecan.abort') + def test_get_one_aborut(self, mock_abort): + self.validator.assert_id.return_value = False + self.controller.get_one('fake_arg') + mock_abort.assert_called_once_with(404) diff --git a/refstack/tests/unit/test_app.py b/refstack/tests/unit/test_app.py new file mode 100644 index 00000000..ce0e6340 --- /dev/null +++ b/refstack/tests/unit/test_app.py @@ -0,0 +1,163 @@ +# Copyright (c) 2015 Mirantis, Inc. +# 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. + +"""Tests for API's utility""" + +import json + +import mock +from oslo_config import fixture as config_fixture +from oslotest import base +import webob + +from refstack.api import app + + +class JSONErrorHookTestCase(base.BaseTestCase): + + def setUp(self): + super(JSONErrorHookTestCase, self).setUp() + self.config_fixture = config_fixture.Config() + self.CONF = self.useFixture(self.config_fixture).conf + + def test_on_error_with_webob_instance(self): + self.CONF.set_override('app_dev_mode', + False, + 'api') + exc = mock.Mock(spec=webob.exc.HTTPError) + exc.status_int = 999 + exc.status = 111 + exc.title = 'fake_title' + + with mock.patch.object(webob, 'Response') as response: + response.return_value = 'fake_value' + + hook = app.JSONErrorHook() + result = hook.on_error(mock.Mock(), exc) + + self.assertEqual(result, 'fake_value') + body = {'code': exc.status_int, 'title': exc.title} + response.assert_called_once_with(body=json.dumps(body), + status=exc.status, + content_type='application/json') + + def test_on_error_with_webob_instance_with_debug(self): + self.CONF.set_override('app_dev_mode', + True, + 'api') + exc = mock.Mock(spec=webob.exc.HTTPError) + exc.status_int = 999 + exc.status = 111 + exc.title = 'fake_title' + + with mock.patch.object(webob, 'Response') as response: + response.return_value = 'fake_value' + + hook = app.JSONErrorHook() + result = hook.on_error(mock.Mock(), exc) + + self.assertEqual(result, 'fake_value') + body = { + 'code': exc.status_int, + 'title': exc.title, + 'detail': str(exc) + } + response.assert_called_once_with(body=json.dumps(body), + status=exc.status, + content_type='application/json') + + @mock.patch.object(webob, 'Response') + def test_on_error_not_webob_instance(self, response): + self.CONF.set_override('app_dev_mode', + False, + 'api') + response.return_value = 'fake_value' + exc = mock.Mock() + + hook = app.JSONErrorHook() + result = hook.on_error(mock.Mock(), exc) + + self.assertEqual(result, 'fake_value') + body = {'code': 500, 'title': 'Internal Server Error'} + response.assert_called_once_with(body=json.dumps(body), + status=500, + content_type='application/json') + + @mock.patch.object(webob, 'Response') + def test_on_error_not_webob_instance_with_debug(self, response): + self.CONF.set_override('app_dev_mode', + True, + 'api') + response.return_value = 'fake_value' + exc = mock.Mock() + + hook = app.JSONErrorHook() + result = hook.on_error(mock.Mock(), exc) + + self.assertEqual(result, 'fake_value') + body = { + 'code': 500, + 'title': 'Internal Server Error', + 'detail': str(exc) + } + response.assert_called_once_with(body=json.dumps(body), + status=500, + content_type='application/json') + + +class SetupAppTestCase(base.BaseTestCase): + + def setUp(self): + super(SetupAppTestCase, self).setUp() + self.config_fixture = config_fixture.Config() + self.CONF = self.useFixture(self.config_fixture).conf + + @mock.patch('pecan.hooks') + @mock.patch.object(app, 'JSONErrorHook') + @mock.patch('os.path.join') + @mock.patch('pecan.make_app') + def test_setup_app(self, make_app, os_join, + json_error_hook, pecan_hooks): + + self.CONF.set_override('app_dev_mode', + True, + 'api') + self.CONF.set_override('template_path', + 'fake_template_path', + 'api') + self.CONF.set_override('static_root', + 'fake_static_root', + 'api') + + os_join.return_value = 'fake_project_root' + + json_error_hook.return_value = 'json_error_hook' + pecan_hooks.RequestViewerHook.return_value = 'request_viewer_hook' + pecan_config = mock.Mock() + pecan_config.app = {'root': 'fake_pecan_config'} + make_app.return_value = 'fake_app' + + result = app.setup_app(pecan_config) + + self.assertEqual(result, 'fake_app') + + app_conf = dict(pecan_config.app) + make_app.assert_called_once_with( + app_conf.pop('root'), + debug=True, + static_root='fake_static_root', + template_path='fake_template_path', + hooks=['json_error_hook', 'request_viewer_hook'] + ) diff --git a/refstack/tests/unit/test_db.py b/refstack/tests/unit/test_db.py index 4b1023be..2cb8ec4c 100644 --- a/refstack/tests/unit/test_db.py +++ b/refstack/tests/unit/test_db.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Tests for database API.""" +"""Tests for database.""" import six import mock @@ -21,6 +21,26 @@ from oslotest import base from refstack import db from refstack.db.sqlalchemy import api +from refstack.db.sqlalchemy import models + + +class RefStackBaseTestCase(base.BaseTestCase): + """Test case for RefStackBase model.""" + + @mock.patch('oslo_utils.timeutils.utcnow') + def test_delete(self, utcnow): + utcnow.return_value = '123' + + base_model = models.RefStackBase() + base_model.id = 'fake_id' + base_model.save = mock.Mock() + session = mock.MagicMock() + + base_model.delete(session) + + self.assertEqual(base_model.deleted, 'fake_id') + self.assertEqual(base_model.deleted_at, '123') + base_model.save.assert_called_once_with(session=session) class DBAPITestCase(base.BaseTestCase): @@ -67,6 +87,12 @@ class DBHelpersTestCase(base.BaseTestCase): facade.get_session.assert_called_once_with(**fake_kwargs) self.assertEqual(result, 'fake_session') + @mock.patch('oslo_db.sqlalchemy.session.EngineFacade.from_config') + def test_create_facade_lazily(self, session): + session.return_value = 'fake_session' + result = api._create_facade_lazily() + self.assertEqual(result, 'fake_session') + class DBBackendTestCase(base.BaseTestCase): """Test case for database backend.""" diff --git a/refstack/tests/unit/test_migration.py b/refstack/tests/unit/test_migration.py index 6fdfb9b2..c140be47 100644 --- a/refstack/tests/unit/test_migration.py +++ b/refstack/tests/unit/test_migration.py @@ -20,6 +20,19 @@ import mock from oslotest import base from refstack.db import migration +from refstack.db.migrations.alembic import migration as alembic_migration + + +class AlembicConfigTestCase(base.BaseTestCase): + + @mock.patch('alembic.config.Config') + @mock.patch('os.path.join') + def test_alembic_config(self, os_join, alembic_config): + os_join.return_value = 'fake_path' + alembic_config.return_value = 'fake_config' + result = alembic_migration._alembic_config() + self.assertEqual(result, 'fake_config') + alembic_config.assert_called_once_with('fake_path') class MigrationTestCase(base.BaseTestCase): diff --git a/refstack/tests/unit/test_validators.py b/refstack/tests/unit/test_validators.py index 13c86141..32264306 100644 --- a/refstack/tests/unit/test_validators.py +++ b/refstack/tests/unit/test_validators.py @@ -33,6 +33,13 @@ class ValidatorsTestCase(base.BaseTestCase): def test_is_uuid_fail(self): self.assertFalse(validators.is_uuid('some_string')) + def test_checker_uuid(self): + value = validators.checker_uuid('12345678123456781234567812345678') + self.assertTrue(value) + + def test_checker_uuid_fail(self): + self.assertFalse(validators.checker_uuid('some_string')) + class TestResultValidatorTestCase(base.BaseTestCase): """Test case for database TestResultValidator.""" @@ -50,6 +57,13 @@ class TestResultValidatorTestCase(base.BaseTestCase): super(TestResultValidatorTestCase, self).setUp() self.validator = validators.TestResultValidator() + def test_assert_id(self): + value = self.validator.assert_id('12345678123456781234567812345678') + self.assertTrue(value) + + def test_assert_id_fail(self): + self.assertFalse(self.validator.assert_id('some_string')) + def test_validation(self): with mock.patch('jsonschema.validate') as mock_validate: self.validator.validate(self.FAKE_TESTS_RESULTS_JSON) @@ -64,12 +78,6 @@ class TestResultValidatorTestCase(base.BaseTestCase): self.validator.validate, wrong_tests_result) - def test_assert_id(self): - self.assertTrue(validators.is_uuid('12345678123456781234567812345678')) - - def test_assert_id_fail(self): - self.assertFalse(validators.is_uuid('some_string')) - @mock.patch('pecan.request') def test_safe_load_json_body(self, mock_request): mock_request.body = self.FAKE_TESTS_RESULTS_JSON diff --git a/tox.ini b/tox.ini index 358b9fbf..70e76bf0 100644 --- a/tox.ini +++ b/tox.ini @@ -44,7 +44,9 @@ commands = commands = {posargs} [testenv:py27-cover] -commands = python setup.py testr --coverage --omit='*/tests*' --testr-args='{posargs}' +commands = python setup.py testr --coverage \ + --omit='{toxinidir}/refstack/tests*,{toxinidir}/refstack/api/config.py,{toxinidir}/refstack/db/migrations/alembic/env.py,{toxinidir}/refstack/opts.py' \ + --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip