ironic/ironic/tests/unit/common/test_args.py

619 lines
18 KiB
Python

# 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 oslo_utils import uuidutils
from ironic.common import args
from ironic.common import exception
from ironic.tests import base
class ArgsDecorated(object):
@args.validate(one=args.string,
two=args.boolean,
three=args.uuid,
four=args.uuid_or_name)
def method(self, one, two, three, four):
return one, two, three, four
@args.validate(one=args.string)
def needs_string(self, one):
return one
@args.validate(one=args.boolean)
def needs_boolean(self, one):
return one
@args.validate(one=args.uuid)
def needs_uuid(self, one):
return one
@args.validate(one=args.name)
def needs_name(self, one):
return one
@args.validate(one=args.uuid_or_name)
def needs_uuid_or_name(self, one):
return one
@args.validate(one=args.string_list)
def needs_string_list(self, one):
return one
@args.validate(one=args.integer)
def needs_integer(self, one):
return one
@args.validate(one=args.mac_address)
def needs_mac_address(self, one):
return one
@args.validate(one=args.schema({
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'count': {'type': 'integer', 'minimum': 0},
},
'additionalProperties': False,
'required': ['name'],
}
}))
def needs_schema(self, one):
return one
@args.validate(one=args.string, two=args.string, the_rest=args.schema({
'type': 'object',
'properties': {
'three': {'type': 'string'},
'four': {'type': 'string', 'maxLength': 4},
'five': {'type': 'string'},
},
'additionalProperties': False,
'required': ['three']
}))
def needs_schema_kwargs(self, one, two, **the_rest):
return one, two, the_rest
@args.validate(one=args.string, two=args.string, the_rest=args.schema({
'type': 'array',
'items': {'type': 'string'}
}))
def needs_schema_args(self, one, two=None, *the_rest):
return one, two, the_rest
@args.validate(one=args.string, two=args.string, args=args.schema({
'type': 'array',
'items': {'type': 'string'}
}), kwargs=args.schema({
'type': 'object',
'properties': {
'four': {'type': 'string'},
},
}))
def needs_schema_mixed(self, one, two=None, *args, **kwargs):
return one, two, args, kwargs
@args.validate(one=args.string)
def needs_mixed_unvalidated(self, one, two=None, *args, **kwargs):
return one, two, args, kwargs
@args.validate(body=args.patch)
def patch(self, body):
return body
class BaseTest(base.TestCase):
def setUp(self):
super(BaseTest, self).setUp()
self.decorated = ArgsDecorated()
class ValidateDecoratorTest(BaseTest):
def test_decorated_args(self):
uuid = uuidutils.generate_uuid()
self.assertEqual((
'a',
True,
uuid,
'a_name',
), self.decorated.method(
'a',
True,
uuid,
'a_name',
))
def test_decorated_kwargs(self):
uuid = uuidutils.generate_uuid()
self.assertEqual((
'a',
True,
uuid,
'a_name',
), self.decorated.method(
one='a',
two=True,
three=uuid,
four='a_name',
))
def test_decorated_args_kwargs(self):
uuid = uuidutils.generate_uuid()
self.assertEqual((
'a',
True,
uuid,
'a_name',
), self.decorated.method(
'a',
True,
uuid,
four='a_name',
))
def test_decorated_function(self):
@args.validate(one=args.string,
two=args.boolean,
three=args.uuid,
four=args.uuid_or_name)
def func(one, two, three, four):
return one, two, three, four
uuid = uuidutils.generate_uuid()
self.assertEqual((
'a',
True,
uuid,
'a_name',
), func(
'a',
'yes',
uuid,
four='a_name',
))
def test_unexpected_args(self):
uuid = uuidutils.generate_uuid()
e = self.assertRaises(
exception.InvalidParameterValue,
self.decorated.method,
one='a',
two=True,
three=uuid,
four='a_name',
five='5',
six=6
)
self.assertIn('Unexpected arguments: ', str(e))
self.assertIn('five', str(e))
self.assertIn('six', str(e))
def test_string(self):
self.assertEqual('foo', self.decorated.needs_string('foo'))
self.assertIsNone(self.decorated.needs_string(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_string, 123)
def test_boolean(self):
self.assertTrue(self.decorated.needs_boolean('yes'))
self.assertTrue(self.decorated.needs_boolean('true'))
self.assertTrue(self.decorated.needs_boolean(True))
self.assertFalse(self.decorated.needs_boolean('no'))
self.assertFalse(self.decorated.needs_boolean('false'))
self.assertFalse(self.decorated.needs_boolean(False))
self.assertIsNone(self.decorated.needs_boolean(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_boolean,
'yeah nah yeah nah')
def test_uuid(self):
uuid = uuidutils.generate_uuid()
self.assertEqual(uuid, self.decorated.needs_uuid(uuid))
self.assertIsNone(self.decorated.needs_uuid(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_uuid, uuid + 'XXX')
def test_name(self):
self.assertEqual('foo', self.decorated.needs_name('foo'))
self.assertIsNone(self.decorated.needs_name(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_name, 'I am a name')
def test_uuid_or_name(self):
uuid = uuidutils.generate_uuid()
self.assertEqual(uuid, self.decorated.needs_uuid_or_name(uuid))
self.assertEqual('foo', self.decorated.needs_uuid_or_name('foo'))
self.assertIsNone(self.decorated.needs_uuid_or_name(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_uuid_or_name,
'I am a name')
def test_string_list(self):
self.assertEqual([
'foo', 'bar', 'baz'
], self.decorated.needs_string_list('foo, bar ,bAZ'))
self.assertIsNone(self.decorated.needs_name(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_name, True)
def test_integer(self):
self.assertEqual(123, self.decorated.needs_integer(123))
self.assertIsNone(self.decorated.needs_integer(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_integer,
'more than a number')
def test_mac_address(self):
self.assertEqual('02:ce:20:50:68:6f',
self.decorated.needs_mac_address('02:cE:20:50:68:6F'))
self.assertIsNone(self.decorated.needs_mac_address(None))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_mac_address,
'big:mac')
def test_mixed_unvalidated(self):
# valid
self.assertEqual((
'one', 'two', ('three', 'four', 'five'), {}
), self.decorated.needs_mixed_unvalidated(
'one', 'two', 'three', 'four', 'five',
))
self.assertEqual((
'one', 'two', ('three',), {'four': 'four', 'five': 'five'}
), self.decorated.needs_mixed_unvalidated(
'one', 'two', 'three', four='four', five='five',
))
self.assertEqual((
'one', 'two', (), {}
), self.decorated.needs_mixed_unvalidated(
'one', 'two',
))
self.assertEqual((
'one', None, (), {}
), self.decorated.needs_mixed_unvalidated(
'one',
))
# wrong type in one
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_mixed_unvalidated, 1)
def test_mandatory(self):
@args.validate(foo=args.string)
def doit(foo):
return foo
@args.validate(foo=args.string)
def doit_maybe(foo='baz'):
return foo
# valid
self.assertEqual('bar', doit('bar'))
# invalid, argument not provided
self.assertRaises(exception.InvalidParameterValue, doit)
# valid, not mandatory
self.assertEqual('baz', doit_maybe())
def test_or(self):
@args.validate(foo=args.or_valid(
args.string,
args.integer,
args.boolean
))
def doit(foo):
return foo
# valid
self.assertEqual('bar', doit('bar'))
self.assertEqual(1, doit(1))
self.assertEqual(True, doit(True))
# invalid, wrong type
self.assertRaises(exception.InvalidParameterValue, doit, {})
def test_and(self):
@args.validate(foo=args.and_valid(
args.string,
args.name
))
def doit(foo):
return foo
# valid
self.assertEqual('bar', doit('bar'))
# invalid, not a string
self.assertRaises(exception.InvalidParameterValue, doit, 2)
# invalid, not a name
self.assertRaises(exception.InvalidParameterValue, doit, 'not a name')
class ValidateSchemaTest(BaseTest):
def test_schema(self):
valid = [
{'name': 'zero'},
{'name': 'one', 'count': 1},
{'name': 'two', 'count': 2}
]
invalid_count = [
{'name': 'neg', 'count': -1},
{'name': 'one', 'count': 1},
{'name': 'two', 'count': 2}
]
invalid_root = {}
self.assertEqual(valid, self.decorated.needs_schema(valid))
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema,
invalid_count)
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema,
invalid_root)
def test_schema_needs_kwargs(self):
# valid
self.assertEqual((
'one', 'two', {
'three': 'three',
'four': 'four',
'five': 'five',
}
), self.decorated.needs_schema_kwargs(
one='one',
two='two',
three='three',
four='four',
five='five',
))
self.assertEqual((
'one', 'two', {
'three': 'three',
}
), self.decorated.needs_schema_kwargs(
one='one',
two='two',
three='three',
))
self.assertEqual((
'one', 'two', {}
), self.decorated.needs_schema_kwargs(
one='one',
two='two',
))
# missing mandatory 'three'
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema_kwargs,
one='one', two='two', four='four', five='five')
# 'four' value exceeds length
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema_kwargs,
one='one', two='two', three='three',
four='beforefore', five='five')
def test_schema_needs_args(self):
# valid
self.assertEqual((
'one', 'two', ('three', 'four', 'five')
), self.decorated.needs_schema_args(
'one', 'two', 'three', 'four', 'five',
))
self.assertEqual((
'one', 'two', ('three',)
), self.decorated.needs_schema_args(
'one', 'two', 'three',
))
self.assertEqual((
'one', 'two', ()
), self.decorated.needs_schema_args(
'one', 'two',
))
self.assertEqual((
'one', None, ()
), self.decorated.needs_schema_args(
'one',
))
# failed, non string *the_rest value
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema_args,
'one', 'two', 'three', 4, False)
def test_schema_needs_mixed(self):
# valid
self.assertEqual((
'one', 'two', ('three', 'four', 'five'), {}
), self.decorated.needs_schema_mixed(
'one', 'two', 'three', 'four', 'five',
))
self.assertEqual((
'one', 'two', ('three', ), {'four': 'four'}
), self.decorated.needs_schema_mixed(
'one', 'two', 'three', four='four',
))
self.assertEqual((
'one', 'two', (), {'four': 'four'}
), self.decorated.needs_schema_mixed(
'one', 'two', four='four',
))
self.assertEqual((
'one', None, (), {}
), self.decorated.needs_schema_mixed(
'one',
))
# wrong type in *args
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema_mixed,
'one', 'two', 3, four='four')
# wrong type in *kwargs
self.assertRaises(exception.InvalidParameterValue,
self.decorated.needs_schema_mixed,
'one', 'two', 'three', four=4)
class ValidatePatchSchemaTest(BaseTest):
def test_patch(self):
data = [{
'path': '/foo',
'op': 'replace',
'value': 'bar'
}, {
'path': '/foo/bar',
'op': 'add',
'value': True
}, {
'path': '/foo/bar/baz',
'op': 'remove',
'value': 123
}]
self.assertEqual(
data,
self.decorated.patch(data)
)
def assertValidationFailed(self, data, error_snippets=None):
e = self.assertRaises(exception.InvalidParameterValue,
self.decorated.patch, data)
if error_snippets:
for s in error_snippets:
self.assertIn(s, str(e))
def test_patch_validation_failed(self):
self.assertValidationFailed(
{},
["Schema error for body:",
"{} is not of type 'array'"])
self.assertValidationFailed(
[{
'path': '/foo/bar/baz',
'op': 'fribble',
'value': 123
}],
["Schema error for body:",
"'fribble' is not one of ['add', 'replace', 'remove']"])
self.assertValidationFailed(
[{
'path': '/',
'op': 'add',
'value': 123
}],
["Schema error for body:",
"'/' does not match"])
self.assertValidationFailed(
[{
'path': 'foo/',
'op': 'add',
'value': 123
}],
["Schema error for body:",
"'foo/' does not match"])
self.assertValidationFailed(
[{
'path': '/foo bar',
'op': 'add',
'value': 123
}],
["Schema error for body:",
"'/foo bar' does not match"])
class ValidateDictTest(BaseTest):
def test_dict_valid(self):
uuid = uuidutils.generate_uuid()
@args.validate(foo=args.dict_valid(
bar=args.uuid
))
def doit(foo):
return foo
# validate passes
doit(foo={'bar': uuid})
# tolerate other keys
doit(foo={'bar': uuid, 'baz': 'baz'})
# key missing
doit({})
# value fails validation
e = self.assertRaises(exception.InvalidParameterValue,
doit, {'bar': uuid + 'XXX'})
self.assertIn('Expected UUID for bar:', str(e))
# not a dict
e = self.assertRaises(exception.InvalidParameterValue,
doit, 'asdf')
self.assertIn("Expected types <class 'dict'> for foo: asdf", str(e))
def test_dict_valid_colon_key_name(self):
uuid = uuidutils.generate_uuid()
@args.validate(foo=args.dict_valid(**{
'bar:baz': args.uuid
}
))
def doit(foo):
return foo
# validate passes
doit(foo={'bar:baz': uuid})
# value fails validation
e = self.assertRaises(exception.InvalidParameterValue,
doit, {'bar:baz': uuid + 'XXX'})
self.assertIn('Expected UUID for bar:', str(e))
class ValidateTypesTest(BaseTest):
def test_types(self):
@args.validate(foo=args.types(type(None), dict, str))
def doit(foo):
return foo
# valid None
self.assertIsNone(doit(None))
# valid dict
self.assertEqual({'foo': 'bar'}, doit({'foo': 'bar'}))
# valid string
self.assertEqual('foo', doit('foo'))
# invalid integer
e = self.assertRaises(exception.InvalidParameterValue,
doit, 123)
self.assertIn("Expected types "
"<class 'NoneType'>, <class 'dict'>, <class 'str'> "
"for foo: 123", str(e))