mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Implement oneOf
, anyOf
, allOf
and not
with unit tests
This commit is contained in:
parent
c59d43330c
commit
625e0b6c06
2 changed files with 304 additions and 15 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):
|
||||
|
@ -724,6 +710,26 @@ class BaseConfigItem(BaseItem):
|
|||
if not isinstance(self.enum, list):
|
||||
self.enum = list(self.enum)
|
||||
|
||||
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):
|
||||
'''
|
||||
Render the configuration item as a restructured text string
|
||||
|
@ -1035,3 +1041,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()}
|
||||
|
|
|
@ -981,6 +981,218 @@ 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)
|
||||
|
||||
|
||||
@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_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