# 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)