From 6e2a8adcf5707c5d953128a788ff76bf9b4d3b0b Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Thu, 5 Oct 2017 01:26:31 +0100 Subject: [PATCH] Fix AttributeError being raised in buckets controller This PS fixes an AttributeError raised in buckets controller when Deckhand tries to parse a malformed YAML which in turn causes Deckhand to attempt to raise a 400, but accesses an invalid attribute in the exception object, causing the AttributeError. Example error: AttributeError: 'ScannerError' object has no attribute 'format_message' Change-Id: I54865e4830d3d34e813b1ecfa3105cf6243f2ca0 --- deckhand/control/buckets.py | 3 +- deckhand/tests/unit/control/base.py | 27 +++++++++++++ ...test_api.py => test_api_initialization.py} | 4 +- .../{test_base.py => test_base_resource.py} | 15 ++++--- .../unit/control/test_buckets_controller.py | 40 +++++++++++++++++++ ...in-bucket-controller-b5c9e410823abd19.yaml | 7 ++++ 6 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 deckhand/tests/unit/control/base.py rename deckhand/tests/unit/control/{test_api.py => test_api_initialization.py} (97%) rename deckhand/tests/unit/control/{test_base.py => test_base_resource.py} (74%) create mode 100644 deckhand/tests/unit/control/test_buckets_controller.py create mode 100644 releasenotes/notes/fix-attribute-error-in-bucket-controller-b5c9e410823abd19.yaml diff --git a/deckhand/control/buckets.py b/deckhand/control/buckets.py index c5c332c3..d105e29a 100644 --- a/deckhand/control/buckets.py +++ b/deckhand/control/buckets.py @@ -16,6 +16,7 @@ import yaml import falcon from oslo_log import log as logging +import six from deckhand.control import base as api_base from deckhand.control.views import document as document_view @@ -42,7 +43,7 @@ class BucketsResource(api_base.BaseResource): error_msg = ("Could not parse the document into YAML data. " "Details: %s." % e) LOG.error(error_msg) - raise falcon.HTTPBadRequest(description=e.format_message()) + raise falcon.HTTPBadRequest(description=six.text_type(e)) # All concrete documents in the payload must successfully pass their # JSON schema validations. Otherwise raise an error. diff --git a/deckhand/tests/unit/control/base.py b/deckhand/tests/unit/control/base.py new file mode 100644 index 00000000..ff98e58f --- /dev/null +++ b/deckhand/tests/unit/control/base.py @@ -0,0 +1,27 @@ +# Copyright 2017 AT&T Intellectual Property. All other 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 falcon import testing as falcon_testing + +from deckhand.control import api +from deckhand.tests.unit import base as test_base + + +class BaseControllerTest(test_base.DeckhandWithDBTestCase, + falcon_testing.TestCase): + """Base class for unit testing falcon controllers.""" + + def setUp(self): + super(BaseControllerTest, self).setUp() + self.app = falcon_testing.TestClient(api.start_api()) diff --git a/deckhand/tests/unit/control/test_api.py b/deckhand/tests/unit/control/test_api_initialization.py similarity index 97% rename from deckhand/tests/unit/control/test_api.py rename to deckhand/tests/unit/control/test_api_initialization.py index 8b19e056..96895a89 100644 --- a/deckhand/tests/unit/control/test_api.py +++ b/deckhand/tests/unit/control/test_api_initialization.py @@ -33,9 +33,9 @@ class TestApi(test_base.DeckhandTestCase): for resource in (buckets, revision_diffing, revision_documents, revision_tags, revisions, rollback, versions): resource_name = resource.__name__.split('.')[-1] - resource_obj = mock.patch.object( + resource_obj = self.patchobject( resource, '%sResource' % resource_name.title().replace( - '_', ''), autospec=True).start() + '_', ''), autospec=True) setattr(self, '%s_resource' % resource_name, resource_obj) @mock.patch.object(api, 'db_api', autospec=True) diff --git a/deckhand/tests/unit/control/test_base.py b/deckhand/tests/unit/control/test_base_resource.py similarity index 74% rename from deckhand/tests/unit/control/test_base.py rename to deckhand/tests/unit/control/test_base_resource.py index 4b8a12f6..8d813018 100644 --- a/deckhand/tests/unit/control/test_base.py +++ b/deckhand/tests/unit/control/test_base_resource.py @@ -15,21 +15,20 @@ import mock from deckhand.control import base as api_base -from deckhand.tests.unit import base as test_base +from deckhand.tests.unit.control import base as test_base -class TestBaseResource(test_base.DeckhandTestCase): +class TestBaseResource(test_base.BaseControllerTest): def setUp(self): super(TestBaseResource, self).setUp() self.base_resource = api_base.BaseResource() - def test_on_options(self): - # Override `dir` so that ``dir(self)`` returns `methods`. - expected_methods = ['on_get', 'on_heat', 'on_post', 'on_put', - 'on_delete', 'on_patch'] - api_base.BaseResource.__dir__ = lambda x: expected_methods - + @mock.patch.object(api_base, 'dir') # noqa + def test_on_options(self, mock_dir): + expected_methods = ['on_get', 'on_post', 'on_put', 'on_delete', + 'on_patch'] + mock_dir.return_value = expected_methods mock_resp = mock.Mock(headers={}) self.base_resource.on_options(None, mock_resp) diff --git a/deckhand/tests/unit/control/test_buckets_controller.py b/deckhand/tests/unit/control/test_buckets_controller.py new file mode 100644 index 00000000..2ee9a529 --- /dev/null +++ b/deckhand/tests/unit/control/test_buckets_controller.py @@ -0,0 +1,40 @@ +# Copyright 2017 AT&T Intellectual Property. All other 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 deckhand.tests.unit.control import base as test_base + + +class TestBucketsController(test_base.BaseControllerTest): + """Test suite for validating positive scenarios bucket controller.""" + + +class TestBucketsControllerNegative(test_base.BaseControllerTest): + """Test suite for validating negative scenarios bucket controller.""" + + def test_put_bucket_with_invalid_document_payload(self): + no_colon_spaces = """ +name:foo +schema: + layeringDefinition: + layer:site +""" + invalid_payloads = ['garbage', no_colon_spaces] + error_re = ['.*The provided YAML failed schema validation.*', + '.*mapping values are not allowed here.*'] + + for idx, payload in enumerate(invalid_payloads): + resp = self.app.simulate_put('/api/v1.0/bucket/mop/documents', + body=payload) + self.assertEqual(400, resp.status_code) + self.assertRegexpMatches(resp.text, error_re[idx]) diff --git a/releasenotes/notes/fix-attribute-error-in-bucket-controller-b5c9e410823abd19.yaml b/releasenotes/notes/fix-attribute-error-in-bucket-controller-b5c9e410823abd19.yaml new file mode 100644 index 00000000..73e8263e --- /dev/null +++ b/releasenotes/notes/fix-attribute-error-in-bucket-controller-b5c9e410823abd19.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Deckhand will no longer throw an ``AttributeError`` after a + ``yaml.scanner.ScannerError`` is raised when attempting to parse a + malformed YAML document. Deckhand should now correctly raise a + "400 Bad Request" instead.