# 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 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(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 " ", , " "for foo: 123", str(e))