mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
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:
commit
0cbf22d884
2 changed files with 294 additions and 16 deletions
|
@ -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()}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue