Merge pull request #25145 from s0undt3ch/features/raas-17-salt-cloud-2015.8

Implement `oneOf`, `anyOf`, `allOf` and `not` with unit tests
This commit is contained in:
Pedro Algarvio 2015-07-03 17:56:44 +01:00
commit 0cbf22d884
2 changed files with 294 additions and 16 deletions

View file

@ -652,21 +652,7 @@ class BaseItem(six.with_metaclass(BaseConfigItemMeta, object)):
'''
Return a serializable form of the config instance
'''
serialized = {'type': self.__type__}
for argname in self._attributes:
if argname == 'required':
# This is handled elsewhere
continue
argvalue = self._get_argname_value(argname)
if argvalue is not None:
if argvalue is Null:
argvalue = None
# None values are not meant to be included in the
# serialization, since this is not None...
if self.__serialize_attr_aliases__ and argname in self.__serialize_attr_aliases__:
argname = self.__serialize_attr_aliases__[argname]
serialized[argname] = argvalue
return serialized
raise NotImplementedError
class BaseConfigItem(BaseItem):
@ -689,8 +675,9 @@ class BaseConfigItem(BaseItem):
title = None
default = None
enum = None
enumNames = None
def __init__(self, title=None, description=None, default=None, enum=None, **kwargs):
def __init__(self, title=None, description=None, default=None, enum=None, enumNames=None, **kwargs):
'''
:param required:
If the configuration item is required. Defaults to ``False``.
@ -712,6 +699,8 @@ class BaseConfigItem(BaseItem):
self.default = default
if enum is not None:
self.enum = enum
if enumNames is not None:
self.enumNames = enumNames
super(BaseConfigItem, self).__init__(**kwargs)
def __validate_attributes__(self):
@ -723,6 +712,38 @@ class BaseConfigItem(BaseItem):
)
if not isinstance(self.enum, list):
self.enum = list(self.enum)
if self.enumNames is not None:
if not isinstance(self.enumNames, (list, tuple, set)):
raise RuntimeError(
'Only the \'list\', \'tuple\' and \'set\' python types can be used '
'to define \'enumNames\''
)
if len(self.enum) != len(self.enumNames):
raise RuntimeError(
'The size of \'enumNames\' must match the size of \'enum\''
)
if not isinstance(self.enumNames, list):
self.enumNames = list(self.enumNames)
def serialize(self):
'''
Return a serializable form of the config instance
'''
serialized = {'type': self.__type__}
for argname in self._attributes:
if argname == 'required':
# This is handled elsewhere
continue
argvalue = self._get_argname_value(argname)
if argvalue is not None:
if argvalue is Null:
argvalue = None
# None values are not meant to be included in the
# serialization, since this is not None...
if self.__serialize_attr_aliases__ and argname in self.__serialize_attr_aliases__:
argname = self.__serialize_attr_aliases__[argname]
serialized[argname] = argvalue
return serialized
def render_as_rst(self, name):
'''
@ -1035,3 +1056,74 @@ class ArrayConfig(BaseConfigItem):
for item in self.items:
items.append(item.serialize())
return items
class OneOfConfig(BaseItem):
__type__ = 'oneOf'
items = None
def __init__(self, items=None):
if items is not None:
self.items = items
super(OneOfConfig, self).__init__()
def __validate_attributes__(self):
if not self.items:
raise RuntimeError(
'The passed items must not be empty'
)
if not isinstance(self.items, (list, tuple)):
raise RuntimeError(
'The passed items must be passed as a list/tuple not '
'\'{0}\''.format(type(self.items))
)
for idx, item in enumerate(self.items):
if not isinstance(item, (Configuration, BaseItem)):
raise RuntimeError(
'The passed item at the {0} index must be of type '
'Configuration, BaseItem or BaseConfigItem, not '
'\'{1}\''.format(idx, type(item))
)
if not isinstance(self.items, list):
self.items = list(self.items)
def serialize(self):
return {self.__type__: [i.serialize() for i in self.items]}
class AnyOfConfig(OneOfConfig):
__type__ = 'anyOf'
class AllOfConfig(OneOfConfig):
__type__ = 'allOf'
class NotConfig(BaseItem):
__type__ = 'not'
item = None
def __init__(self, item=None):
if item is not None:
self.item = item
super(NotConfig, self).__init__()
def __validate_attributes__(self):
if not self.item:
raise RuntimeError(
'An item must be passed'
)
if not isinstance(self.item, (Configuration, BaseItem)):
raise RuntimeError(
'The passed item be of type Configuration, BaseItem or '
'BaseConfigItem, not \'{1}\''.format(type(self.item))
)
def serialize(self):
return {self.__type__: self.item.serialize()}

View file

@ -151,6 +151,24 @@ class ConfigTestCase(TestCase):
}
)
item = config.StringConfig(title='Foo',
description='Foo Item',
min_length=1,
max_length=3,
enum=('foo', 'bar'),
enumNames=('Foo', 'Bar'))
self.assertDictEqual(
item.serialize(), {
'type': 'string',
'title': item.title,
'description': item.description,
'minLength': item.min_length,
'maxLength': item.max_length,
'enum': ['foo', 'bar'],
'enumNames': ['Foo', 'Bar']
}
)
item = config.StringConfig(title='Foo',
description='Foo Item',
pattern=r'^([\w_-]+)$')
@ -981,6 +999,174 @@ class ConfigTestCase(TestCase):
format_checker=jsonschema.FormatChecker())
self.assertIn('is not one of', excinfo.exception.message)
def test_oneof_config(self):
item = config.OneOfConfig(
items=(config.StringConfig(title='Yes', enum=['yes']),
config.StringConfig(title='No', enum=['no']))
)
self.assertEqual(
item.serialize(), {
'oneOf': [i.serialize() for i in item.items]
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_oneof_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(
title='Hungry',
description='Are you hungry?',
items=config.OneOfConfig(
items=(config.StringConfig(title='Yes', enum=['yes']),
config.StringConfig(title='No', enum=['no']))
)
)
try:
jsonschema.validate({'item': ['no']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 2}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
def test_anyof_config(self):
item = config.AnyOfConfig(
items=(config.StringConfig(title='Yes', enum=['yes']),
config.StringConfig(title='No', enum=['no']))
)
self.assertEqual(
item.serialize(), {
'anyOf': [i.serialize() for i in item.items]
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_anyof_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(
title='Hungry',
description='Are you hungry?',
items=config.AnyOfConfig(
items=(config.StringConfig(title='Yes', enum=['yes']),
config.StringConfig(title='No', enum=['no']),
config.BooleanConfig())
)
)
try:
jsonschema.validate({'item': ['no']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': ['yes']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': [True]}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': [False]}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 2}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
def test_allof_config(self):
item = config.AllOfConfig(
items=(config.StringConfig(min_length=2),
config.StringConfig(max_length=3))
)
self.assertEqual(
item.serialize(), {
'allOf': [i.serialize() for i in item.items]
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_allof_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(
title='Hungry',
description='Are you hungry?',
items=config.AllOfConfig(
items=(config.StringConfig(min_length=2),
config.StringConfig(max_length=3))
)
)
try:
jsonschema.validate({'item': ['no']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': ['yes']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['maybe']}, TestConf.serialize())
self.assertIn('is too long', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': ['hmmmm']}, TestConf.serialize())
self.assertIn('is too long', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': 2}, TestConf.serialize())
self.assertIn('is not of type', excinfo.exception.message)
def test_not_config(self):
item = config.NotConfig(item=config.BooleanConfig())
self.assertEqual(
item.serialize(), {
'not': item.item.serialize()
}
)
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_not_config_validation(self):
class TestConf(config.Configuration):
item = config.ArrayConfig(
title='Hungry',
description='Are you hungry?',
items=config.NotConfig(item=config.BooleanConfig())
)
try:
jsonschema.validate({'item': ['no']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
try:
jsonschema.validate({'item': ['yes']}, TestConf.serialize())
except jsonschema.exceptions.ValidationError as exc:
self.fail('ValidationError raised: {0}'.format(exc))
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': [True]}, TestConf.serialize())
self.assertIn('is not allowed for', excinfo.exception.message)
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
jsonschema.validate({'item': [False]}, TestConf.serialize())
self.assertIn('is not allowed for', excinfo.exception.message)
if __name__ == '__main__':
from integration import run_tests