diff options
author | Steve Baker <sbaker@redhat.com> | 2020-01-27 12:39:15 +1300 |
---|---|---|
committer | Steve Baker <sbaker@redhat.com> | 2020-05-06 11:20:52 +1200 |
commit | a83dfd5b983d5276a53a40713b2186bb8d78f7ec (patch) | |
tree | 12e18597a5610d29df051195dc11d48f770aa140 /ironic/tests/unit | |
parent | c647408f8d4a15e7ee3f88e1929bc64fd68f3cec (diff) | |
download | ironic-a83dfd5b983d5276a53a40713b2186bb8d78f7ec.tar.gz |
Do all serialization in the expose decorator
Instead of delegating to the @wsme.wsexpose decorator, bring the
required logic into this decorator, including a mimimum required
tojson function.
Change-Id: I96661570d77ecb641b4ac7508e65bd7ca83194a5
Story: 1651346
Diffstat (limited to 'ironic/tests/unit')
-rw-r--r-- | ironic/tests/unit/api/controllers/v1/test_expose.py | 228 | ||||
-rw-r--r-- | ironic/tests/unit/api/controllers/v1/test_utils.py | 5 |
2 files changed, 230 insertions, 3 deletions
diff --git a/ironic/tests/unit/api/controllers/v1/test_expose.py b/ironic/tests/unit/api/controllers/v1/test_expose.py index 0c9976dcb..0cb41b22d 100644 --- a/ironic/tests/unit/api/controllers/v1/test_expose.py +++ b/ironic/tests/unit/api/controllers/v1/test_expose.py @@ -12,15 +12,26 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime +from http import client as http_client from importlib import machinery import inspect +import json import os import sys import mock from oslo_utils import uuidutils +import pecan.rest +import pecan.testing +from ironic.api.controllers import root +from ironic.api.controllers import v1 +from ironic.api import expose +from ironic.api import types as atypes +from ironic.common import exception from ironic.tests import base as test_base +from ironic.tests.unit.api import base as test_api_base class TestExposedAPIMethodsCheckPolicy(test_base.TestCase): @@ -85,3 +96,220 @@ class TestExposedAPIMethodsCheckPolicy(test_base.TestCase): def test_conductor_api_policy(self): self._test('ironic.api.controllers.v1.conductor') + + +class UnderscoreStr(atypes.UserType): + basetype = str + name = "custom string" + + def tobasetype(self, value): + return '__' + value + + +class Obj(atypes.Base): + id = int + name = str + unset_me = str + + +class NestedObj(atypes.Base): + o = Obj + + +class TestJsonRenderer(test_base.TestCase): + + def setUp(self): + super(TestJsonRenderer, self).setUp() + self.renderer = expose.JSonRenderer('/', None) + + def test_render_error(self): + error_dict = { + 'faultcode': 500, + 'faultstring': 'ouch' + } + self.assertEqual( + error_dict, + json.loads(self.renderer.render('/', error_dict)) + ) + + def test_render_exception(self): + error_dict = { + 'faultcode': 'Server', + 'faultstring': 'ouch', + 'debuginfo': None + } + try: + raise Exception('ouch') + except Exception: + excinfo = sys.exc_info() + self.assertEqual( + json.dumps(error_dict), + self.renderer.render('/', expose.format_exception(excinfo)) + ) + + def test_render_http_exception(self): + error_dict = { + 'faultcode': '403', + 'faultstring': 'Not authorized', + 'debuginfo': None + } + try: + e = exception.NotAuthorized() + e.code = 403 + except exception.IronicException: + excinfo = sys.exc_info() + self.assertEqual( + json.dumps(error_dict), + self.renderer.render('/', expose.format_exception(excinfo)) + ) + + def test_render_int(self): + self.assertEqual( + '42', + self.renderer.render('/', { + 'result': 42, + 'datatype': int + }) + ) + + def test_render_none(self): + self.assertEqual( + 'null', + self.renderer.render('/', { + 'result': None, + 'datatype': str + }) + ) + + def test_render_str(self): + self.assertEqual( + '"a string"', + self.renderer.render('/', { + 'result': 'a string', + 'datatype': str + }) + ) + + def test_render_datetime(self): + self.assertEqual( + '"2020-04-14T10:35:10.586431"', + self.renderer.render('/', { + 'result': datetime.datetime(2020, 4, 14, 10, 35, 10, 586431), + 'datatype': datetime.datetime + }) + ) + + def test_render_array(self): + self.assertEqual( + json.dumps(['one', 'two', 'three']), + self.renderer.render('/', { + 'result': ['one', 'two', 'three'], + 'datatype': atypes.ArrayType(str) + }) + ) + + def test_render_dict(self): + self.assertEqual( + json.dumps({'one': 'a', 'two': 'b', 'three': 'c'}), + self.renderer.render('/', { + 'result': {'one': 'a', 'two': 'b', 'three': 'c'}, + 'datatype': atypes.DictType(str, str) + }) + ) + + def test_complex_type(self): + o = Obj() + o.id = 1 + o.name = 'one' + o.unset_me = atypes.Unset + + n = NestedObj() + n.o = o + self.assertEqual( + json.dumps({'o': {'id': 1, 'name': 'one'}}), + self.renderer.render('/', { + 'result': n, + 'datatype': NestedObj + }) + ) + + def test_user_type(self): + self.assertEqual( + '"__foo"', + self.renderer.render('/', { + 'result': 'foo', + 'datatype': UnderscoreStr() + }) + ) + + +class MyThingController(pecan.rest.RestController): + + _custom_actions = { + 'no_content': ['GET'], + 'response_content': ['GET'], + 'ouch': ['GET'], + } + + @expose.expose(int, str, int) + def get(self, name, number): + return {name: number} + + @expose.expose(str) + def no_content(self): + return atypes.Response('nothing', status_code=204) + + @expose.expose(str) + def response_content(self): + return atypes.Response('nothing', status_code=200) + + @expose.expose(str) + def ouch(self): + raise Exception('ouch') + + +class MyV1Controller(v1.Controller): + + things = MyThingController() + + +class MyRootController(root.RootController): + + v1 = MyV1Controller() + + +class TestExpose(test_api_base.BaseApiTest): + + block_execute = False + + root_controller = '%s.%s' % (MyRootController.__module__, + MyRootController.__name__) + + def test_expose(self): + self.assertEqual( + {'foo': 1}, + self.get_json('/things/', name='foo', number=1) + ) + + def test_response_204(self): + response = self.get_json('/things/no_content', expect_errors=True) + self.assertEqual(http_client.NO_CONTENT, response.status_int) + self.assertIsNone(response.content_type) + self.assertEqual(b'', response.normal_body) + + def test_response_content(self): + response = self.get_json('/things/response_content', + expect_errors=True) + self.assertEqual(http_client.OK, response.status_int) + self.assertEqual(b'"nothing"', response.normal_body) + self.assertEqual('application/json', response.content_type) + + def test_exception(self): + response = self.get_json('/things/ouch', + expect_errors=True) + error_message = json.loads(response.json['error_message']) + self.assertEqual(http_client.INTERNAL_SERVER_ERROR, + response.status_int) + self.assertEqual('application/json', response.content_type) + self.assertEqual('Server', error_message['faultcode']) + self.assertEqual('ouch', error_message['faultstring']) diff --git a/ironic/tests/unit/api/controllers/v1/test_utils.py b/ironic/tests/unit/api/controllers/v1/test_utils.py index 3defca326..e33617d74 100644 --- a/ironic/tests/unit/api/controllers/v1/test_utils.py +++ b/ironic/tests/unit/api/controllers/v1/test_utils.py @@ -21,7 +21,6 @@ import os_traits from oslo_config import cfg from oslo_utils import uuidutils from webob import static -import wsme from ironic import api from ironic.api.controllers.v1 import node as api_node @@ -692,7 +691,7 @@ class TestVendorPassthru(base.TestCase): passthru_mock.assert_called_once_with( 'fake-context', 'fake-ident', 'squarepants', 'POST', 'fake-data', 'fake-topic') - self.assertIsInstance(response, wsme.api.Response) + self.assertIsInstance(response, atypes.Response) self.assertEqual('SpongeBob', response.obj) self.assertEqual(response.return_type, atypes.Unset) sc = http_client.ACCEPTED if async_call else http_client.OK @@ -731,7 +730,7 @@ class TestVendorPassthru(base.TestCase): self.assertEqual(expct_return_value, mock_response.app_iter.file.read()) # Assert response message is none - self.assertIsInstance(response, wsme.api.Response) + self.assertIsInstance(response, atypes.Response) self.assertIsNone(response.obj) self.assertIsNone(response.return_type) self.assertEqual(http_client.OK, response.status_code) |