summaryrefslogtreecommitdiff
path: root/ironic/tests/unit
diff options
context:
space:
mode:
authorSteve Baker <sbaker@redhat.com>2020-01-27 12:39:15 +1300
committerSteve Baker <sbaker@redhat.com>2020-05-06 11:20:52 +1200
commita83dfd5b983d5276a53a40713b2186bb8d78f7ec (patch)
tree12e18597a5610d29df051195dc11d48f770aa140 /ironic/tests/unit
parentc647408f8d4a15e7ee3f88e1929bc64fd68f3cec (diff)
downloadironic-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.py228
-rw-r--r--ironic/tests/unit/api/controllers/v1/test_utils.py5
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)