ironic/ironic/tests/unit/api/test_args.py

507 lines
16 KiB
Python

# Copyright 2020 Red Hat, 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.
import datetime
import decimal
import io
from webob import multidict
from ironic.api import args
from ironic.api.controllers.v1 import types
from ironic.api import functions
from ironic.api import types as atypes
from ironic.common import exception
from ironic.tests import base as test_base
class Obj(atypes.Base):
id = atypes.wsattr(int, mandatory=True)
name = str
readonly_field = atypes.wsattr(str, readonly=True)
default_field = atypes.wsattr(str, default='foo')
unset_me = str
class NestedObj(atypes.Base):
o = Obj
class TestArgs(test_base.TestCase):
def test_fromjson_array(self):
atype = atypes.ArrayType(int)
self.assertEqual(
[0, 1, 1234, None],
args.fromjson_array(atype, [0, '1', '1_234', None])
)
self.assertRaises(ValueError, args.fromjson_array,
atype, ['one', 'two', 'three'])
self.assertRaises(ValueError, args.fromjson_array,
atype, 'one')
def test_fromjson_dict(self):
dtype = atypes.DictType(str, int)
self.assertEqual({
'zero': 0,
'one': 1,
'etc': 1234,
'none': None
}, args.fromjson_dict(dtype, {
'zero': 0,
'one': '1',
'etc': '1_234',
'none': None
}))
self.assertRaises(ValueError, args.fromjson_dict,
dtype, [])
self.assertRaises(ValueError, args.fromjson_dict,
dtype, {'one': 'one'})
def test_fromjson_bool(self):
for b in (1, 2, True, 'true', 't', 'yes', 'y', 'on', '1'):
self.assertTrue(args.fromjson_bool(b))
for b in (0, False, 'false', 'f', 'no', 'n', 'off', '0'):
self.assertFalse(args.fromjson_bool(b))
for b in ('yup', 'yeet', 'NOPE', 3.14):
self.assertRaises(ValueError, args.fromjson_bool, b)
def test_fromjson(self):
# parse None
self.assertIsNone(args.fromjson(None, None))
# parse array
atype = atypes.ArrayType(int)
self.assertEqual(
[0, 1, 1234, None],
args.fromjson(atype, [0, '1', '1_234', None])
)
# parse dict
dtype = atypes.DictType(str, int)
self.assertEqual({
'zero': 0,
'one': 1,
'etc': 1234,
'none': None
}, args.fromjson(dtype, {
'zero': 0,
'one': '1',
'etc': '1_234',
'none': None
}))
# parse bytes
self.assertEqual(
b'asdf',
args.fromjson(bytes, b'asdf')
)
self.assertEqual(
b'asdf',
args.fromjson(bytes, 'asdf')
)
self.assertEqual(
b'33',
args.fromjson(bytes, 33)
)
self.assertEqual(
b'3.14',
args.fromjson(bytes, 3.14)
)
# parse str
self.assertEqual(
'asdf',
args.fromjson(str, b'asdf')
)
self.assertEqual(
'asdf',
args.fromjson(str, 'asdf')
)
# parse int/float
self.assertEqual(
3,
args.fromjson(int, '3')
)
self.assertEqual(
3,
args.fromjson(int, 3)
)
self.assertEqual(
3.14,
args.fromjson(float, 3.14)
)
# parse bool
self.assertFalse(args.fromjson(bool, 'no'))
self.assertTrue(args.fromjson(bool, 'yes'))
# parse decimal
self.assertEqual(
decimal.Decimal(3.14),
args.fromjson(decimal.Decimal, 3.14)
)
# parse datetime
expected = datetime.datetime(2015, 8, 13, 11, 38, 9, 496475)
self.assertEqual(
expected,
args.fromjson(datetime.datetime, '2015-08-13T11:38:09.496475')
)
# parse complex
n = args.fromjson(NestedObj, {'o': {
'id': 1234,
'name': 'an object'
}})
self.assertIsInstance(n.o, Obj)
self.assertEqual(1234, n.o.id)
self.assertEqual('an object', n.o.name)
self.assertEqual('foo', n.o.default_field)
# parse usertype
self.assertEqual(
['0', '1', '2', 'three'],
args.fromjson(types.listtype, '0,1, 2, three')
)
def test_fromjson_complex(self):
n = args.fromjson_complex(NestedObj, {'o': {
'id': 1234,
'name': 'an object'
}})
self.assertIsInstance(n.o, Obj)
self.assertEqual(1234, n.o.id)
self.assertEqual('an object', n.o.name)
self.assertEqual('foo', n.o.default_field)
e = self.assertRaises(exception.UnknownAttribute,
args.fromjson_complex,
Obj, {'ooo': {}})
self.assertEqual({'ooo'}, e.attributes)
e = self.assertRaises(exception.InvalidInput, args.fromjson_complex,
Obj,
{'name': 'an object'})
self.assertEqual('id', e.fieldname)
self.assertEqual('Mandatory field missing.', e.msg)
e = self.assertRaises(exception.InvalidInput, args.fromjson_complex,
Obj,
{'id': 1234, 'readonly_field': 'foo'})
self.assertEqual('readonly_field', e.fieldname)
self.assertEqual('Cannot set read only field.', e.msg)
def test_parse(self):
# source as bytes
s = b'{"o": {"id": 1234, "name": "an object"}}'
# test bodyarg=True
n = args.parse(s, {"o": NestedObj}, True)['o']
self.assertEqual(1234, n.o.id)
self.assertEqual('an object', n.o.name)
# source as file
s = io.StringIO('{"o": {"id": 1234, "name": "an object"}}')
# test bodyarg=False
n = args.parse(s, {"o": Obj}, False)['o']
self.assertEqual(1234, n.id)
self.assertEqual('an object', n.name)
# fromjson ValueError
s = '{"o": ["id", "name"]}'
self.assertRaises(exception.InvalidInput, args.parse,
s, {"o": atypes.DictType(str, str)}, False)
s = '["id", "name"]'
self.assertRaises(exception.InvalidInput, args.parse,
s, {"o": atypes.DictType(str, str)}, True)
# fromjson UnknownAttribute
s = '{"o": {"foo": "bar", "id": 1234, "name": "an object"}}'
self.assertRaises(exception.UnknownAttribute, args.parse,
s, {"o": NestedObj}, True)
self.assertRaises(exception.UnknownAttribute, args.parse,
s, {"o": Obj}, False)
# invalid json
s = '{Sunn O)))}'
self.assertRaises(exception.ClientSideError, args.parse,
s, {"o": Obj}, False)
# extra args
s = '{"foo": "bar", "o": {"id": 1234, "name": "an object"}}'
self.assertRaises(exception.UnknownArgument, args.parse,
s, {"o": Obj}, False)
def test_from_param(self):
# datetime param
expected = datetime.datetime(2015, 8, 13, 11, 38, 9, 496475)
self.assertEqual(
expected,
args.from_param(datetime.datetime, '2015-08-13T11:38:09.496475')
)
self.assertIsNone(args.from_param(datetime.datetime, None))
# usertype param
self.assertEqual(
['0', '1', '2', 'three'],
args.from_param(types.listtype, '0,1, 2, three')
)
# array param
atype = atypes.ArrayType(int)
self.assertEqual(
[0, 1, 1234, None],
args.from_param(atype, [0, '1', '1_234', None])
)
self.assertIsNone(args.from_param(atype, None))
# string param
self.assertEqual('foo', args.from_param(str, 'foo'))
self.assertIsNone(args.from_param(str, None))
# string param with from_params
hit_paths = set()
params = multidict.MultiDict(
foo='bar',
)
self.assertEqual(
'bar',
args.from_params(str, params, 'foo', hit_paths)
)
self.assertEqual({'foo'}, hit_paths)
def test_array_from_params(self):
hit_paths = set()
datatype = atypes.ArrayType(str)
params = multidict.MultiDict(
foo='bar',
one='two'
)
self.assertEqual(
['bar'],
args.from_params(datatype, params, 'foo', hit_paths)
)
self.assertEqual({'foo'}, hit_paths)
self.assertEqual(
['two'],
args.array_from_params(datatype, params, 'one', hit_paths)
)
self.assertEqual({'foo', 'one'}, hit_paths)
def test_usertype_from_params(self):
hit_paths = set()
datatype = types.listtype
params = multidict.MultiDict(
foo='0,1, 2, three',
)
self.assertEqual(
['0', '1', '2', 'three'],
args.usertype_from_params(datatype, params, 'foo', hit_paths)
)
self.assertEqual(
['0', '1', '2', 'three'],
args.from_params(datatype, params, 'foo', hit_paths)
)
self.assertEqual(
atypes.Unset,
args.usertype_from_params(datatype, params, 'bar', hit_paths)
)
def test_args_from_args(self):
fromargs = ['one', 2, [0, '1', '2_34']]
fromkwargs = {'foo': '1, 2, 3'}
@functions.signature(str, str, int, atypes.ArrayType(int),
types.listtype)
def myfunc(self, first, second, third, foo):
pass
funcdef = functions.FunctionDefinition.get(myfunc)
newargs, newkwargs = args.args_from_args(funcdef, fromargs, fromkwargs)
self.assertEqual(['one', 2, [0, 1, 234]], newargs)
self.assertEqual({'foo': ['1', '2', '3']}, newkwargs)
def test_args_from_params(self):
@functions.signature(str, str, int, atypes.ArrayType(int),
types.listtype)
def myfunc(self, first, second, third, foo):
pass
funcdef = functions.FunctionDefinition.get(myfunc)
params = multidict.MultiDict(
foo='0,1, 2, three',
third='1',
second='2'
)
self.assertEqual(
([], {'foo': ['0', '1', '2', 'three'], 'second': 2, 'third': [1]}),
args.args_from_params(funcdef, params)
)
# unexpected param
params = multidict.MultiDict(bar='baz')
self.assertRaises(exception.UnknownArgument, args.args_from_params,
funcdef, params)
# no params plus a body
params = multidict.MultiDict(__body__='')
self.assertEqual(
([], {}),
args.args_from_params(funcdef, params)
)
def test_args_from_body(self):
@functions.signature(str, body=NestedObj)
def myfunc(self, nested):
pass
funcdef = functions.FunctionDefinition.get(myfunc)
mimetype = 'application/json'
body = b'{"o": {"id": 1234, "name": "an object"}}'
newargs, newkwargs = args.args_from_body(funcdef, body, mimetype)
self.assertEqual(1234, newkwargs['nested'].o.id)
self.assertEqual('an object', newkwargs['nested'].o.name)
self.assertEqual(
((), {}),
args.args_from_body(funcdef, None, mimetype)
)
self.assertRaises(exception.ClientSideError, args.args_from_body,
funcdef, body, 'application/x-corba')
self.assertEqual(
((), {}),
args.args_from_body(funcdef, body,
'application/x-www-form-urlencoded')
)
def test_combine_args(self):
@functions.signature(str, str, int)
def myfunc(self, first, second,):
pass
funcdef = functions.FunctionDefinition.get(myfunc)
# empty
self.assertEqual(
([], {}),
args.combine_args(
funcdef, (
([], {}),
([], {}),
)
)
)
# combine kwargs
self.assertEqual(
([], {'first': 'one', 'second': 'two'}),
args.combine_args(
funcdef, (
([], {}),
([], {'first': 'one', 'second': 'two'}),
)
)
)
# combine mixed args
self.assertEqual(
([], {'first': 'one', 'second': 'two'}),
args.combine_args(
funcdef, (
(['one'], {}),
([], {'second': 'two'}),
)
)
)
# override kwargs
self.assertEqual(
([], {'first': 'two'}),
args.combine_args(
funcdef, (
([], {'first': 'one'}),
([], {'first': 'two'}),
),
allow_override=True
)
)
# override args
self.assertEqual(
([], {'first': 'two', 'second': 'three'}),
args.combine_args(
funcdef, (
(['one', 'three'], {}),
(['two'], {}),
),
allow_override=True
)
)
# can't override args
self.assertRaises(exception.ClientSideError, args.combine_args,
funcdef,
((['one'], {}), (['two'], {})))
# can't override kwargs
self.assertRaises(exception.ClientSideError, args.combine_args,
funcdef,
(([], {'first': 'one'}), ([], {'first': 'two'})))
def test_get_args(self):
@functions.signature(str, str, int, atypes.ArrayType(int),
types.listtype, body=NestedObj)
def myfunc(self, first, second, third, foo, nested):
pass
funcdef = functions.FunctionDefinition.get(myfunc)
params = multidict.MultiDict(
foo='0,1, 2, three',
second='2'
)
mimetype = 'application/json'
body = b'{"o": {"id": 1234, "name": "an object"}}'
fromargs = ['one']
fromkwargs = {'third': '1'}
newargs, newkwargs = args.get_args(funcdef, fromargs, fromkwargs,
params, body, mimetype)
self.assertEqual([], newargs)
n = newkwargs.pop('nested')
self.assertEqual({
'first': 'one',
'foo': ['0', '1', '2', 'three'],
'second': 2,
'third': [1]},
newkwargs
)
self.assertEqual(1234, n.o.id)
self.assertEqual('an object', n.o.name)
# check_arguments missing mandatory argument 'second'
params = multidict.MultiDict(
foo='0,1, 2, three',
)
self.assertRaises(exception.MissingArgument, args.get_args,
funcdef, fromargs, fromkwargs,
params, body, mimetype)