Merge "Add unit tests for validators"
This commit is contained in:
commit
870682389a
@ -475,11 +475,12 @@ class error_handler(object):
|
||||
|
||||
|
||||
def get_schema_type(attr):
|
||||
if isinstance(attr, fields.IntegerField):
|
||||
if isinstance(attr, fields.IntegerField) or attr is fields.Integer:
|
||||
return 'integer'
|
||||
elif isinstance(attr, fields.FloatField):
|
||||
elif isinstance(attr, fields.FloatField) or attr is fields.Float:
|
||||
return 'number'
|
||||
elif isinstance(attr, fields.BooleanField):
|
||||
elif isinstance(attr, fields.FlexibleBooleanField) \
|
||||
or attr is fields.FlexibleBoolean:
|
||||
return 'boolean'
|
||||
elif isinstance(attr, glare_fields.List):
|
||||
return 'array'
|
||||
|
@ -923,10 +923,8 @@ class BaseArtifact(base.VersionedObject):
|
||||
schema['readOnly'] = True
|
||||
|
||||
if isinstance(field, glare_fields.Dict):
|
||||
element_type = (utils.get_schema_type(field.element_type)
|
||||
if hasattr(field, 'element_type')
|
||||
else 'string')
|
||||
|
||||
element_type = utils.get_schema_type(field.element_type)
|
||||
property_validators = schema.pop('propertyValidators', [])
|
||||
if field.element_type is glare_fields.BlobFieldType:
|
||||
schema['additionalProperties'] = output_blob_schema
|
||||
else:
|
||||
@ -938,16 +936,21 @@ class BaseArtifact(base.VersionedObject):
|
||||
'type': (element_type
|
||||
if key in required
|
||||
else [element_type, 'null'])}
|
||||
for val in property_validators:
|
||||
properties[key].update(val)
|
||||
schema['properties'] = properties
|
||||
schema['additionalProperties'] = False
|
||||
else:
|
||||
schema['additionalProperties'] = {'type': element_type}
|
||||
for val in property_validators:
|
||||
schema['additionalProperties'].update(val)
|
||||
|
||||
if field_type == 'array':
|
||||
if isinstance(field, glare_fields.List):
|
||||
items_validators = schema.pop('itemValidators', [])
|
||||
schema['items'] = {
|
||||
'type': (utils.get_schema_type(field.element_type)
|
||||
if hasattr(field, 'element_type')
|
||||
else 'string')}
|
||||
'type': utils.get_schema_type(field.element_type)}
|
||||
for val in items_validators:
|
||||
schema['items'].update(val)
|
||||
|
||||
if isinstance(field, glare_fields.BlobField):
|
||||
schema.update(output_blob_schema)
|
||||
|
@ -13,23 +13,27 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_versionedobjects import fields
|
||||
import abc
|
||||
import uuid
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_versionedobjects import fields
|
||||
import six
|
||||
|
||||
from glare.common import exception
|
||||
from glare.i18n import _
|
||||
from glare.objects.meta import fields as glare_fields
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Validator(object):
|
||||
"""Common interface for all validators."""
|
||||
|
||||
def validate(self, value):
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_allowed_types(self):
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def get_allowed_types():
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_type_allowed(self, field_type):
|
||||
@ -40,7 +44,7 @@ class Validator(object):
|
||||
for field in self.get_allowed_types()
|
||||
if hasattr(field, 'AUTO_TYPE'))
|
||||
if not issubclass(field_type, allowed_field_types):
|
||||
raise TypeError(
|
||||
raise exception.IncorrectArtifactType(
|
||||
_("%(type)s is not allowed for validator "
|
||||
"%(val)s. Allowed types are %(allowed)s.") % {
|
||||
"type": str(field_type),
|
||||
@ -50,23 +54,19 @@ class Validator(object):
|
||||
def to_jsonschema(self):
|
||||
return {}
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(self, value):
|
||||
try:
|
||||
self.validate(value)
|
||||
except ValueError:
|
||||
raise
|
||||
except TypeError as e:
|
||||
# we are raising all expected ex Type Errors as ValueErrors
|
||||
LOG.exception(e)
|
||||
raise ValueError(encodeutils.exception_to_unicode(e))
|
||||
raise NotImplemented
|
||||
|
||||
|
||||
class UUID(Validator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.StringField,
|
||||
|
||||
def validate(self, value):
|
||||
pass
|
||||
def __call__(self, value):
|
||||
uuid.UUID(value, version=4)
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'pattern': ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])'
|
||||
@ -74,26 +74,30 @@ class UUID(Validator):
|
||||
|
||||
|
||||
class AllowedValues(Validator):
|
||||
|
||||
def __init__(self, allowed_values):
|
||||
self.allowed_values = allowed_values
|
||||
|
||||
def get_allowed_types(self):
|
||||
return fields.StringField,
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.StringField, fields.IntegerField, fields.FloatField
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
if value not in self.allowed_values:
|
||||
raise ValueError(_("Value must be one of the following: %s") %
|
||||
', '.join(self.allowed_values))
|
||||
', '.join(map(str, self.allowed_values)))
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'enum': self.allowed_values}
|
||||
|
||||
|
||||
class Version(Validator):
|
||||
def get_allowed_types(self):
|
||||
return glare_fields.VersionField
|
||||
|
||||
def validate(self, value):
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.VersionField,
|
||||
|
||||
def __call__(self, value):
|
||||
pass
|
||||
|
||||
def to_jsonschema(self):
|
||||
@ -101,77 +105,87 @@ class Version(Validator):
|
||||
'+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/')}
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class SizeValidator(Validator):
|
||||
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
|
||||
|
||||
class MaxStrLen(SizeValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.StringField,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
l = len(value)
|
||||
if l > self.size:
|
||||
raise ValueError(
|
||||
_("String length must be less than %(size)s. "
|
||||
"Current size: %(cur)s") % {'size': self.size,
|
||||
'cur': l})
|
||||
_("String length must be less than %(size)d. "
|
||||
"Current length: %(cur)d") % {'size': self.size,
|
||||
'cur': l})
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'maxLength': self.size}
|
||||
|
||||
|
||||
class MinStrLen(SizeValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.StringField,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
l = len(value)
|
||||
if l < self.size:
|
||||
raise ValueError(
|
||||
_("String length must be more than %(size)s. "
|
||||
"Current size: %(cur)s") % {'size': self.size,
|
||||
'cur': l})
|
||||
_("String length must be less than %(size)d. "
|
||||
"Current length: %(cur)d") % {'size': self.size,
|
||||
'cur': l})
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'minLength': self.size}
|
||||
|
||||
|
||||
class ForbiddenChars(Validator):
|
||||
|
||||
def __init__(self, forbidden_chars):
|
||||
self.forbidden_chars = forbidden_chars
|
||||
|
||||
def get_allowed_types(self):
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.StringField,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for fc in self.forbidden_chars:
|
||||
if fc in value:
|
||||
raise ValueError(
|
||||
_("Forbidden character %(char) found in string "
|
||||
_("Forbidden character %(char)c found in string "
|
||||
"%(string)s")
|
||||
% {"char": fc, "string": value})
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'pattern': '^[^%s]+$' % ''.join(self.forbidden_chars)}
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class MaxSize(SizeValidator):
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
l = len(value)
|
||||
if l > self.size:
|
||||
raise ValueError(
|
||||
_("Number of items must be less than "
|
||||
"%(size)s. Current size: %(cur)s") %
|
||||
"%(size)d. Current size: %(cur)d") %
|
||||
{'size': self.size, 'cur': l})
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'maxItems': self.size}
|
||||
|
||||
|
||||
class MaxDictSize(MaxSize):
|
||||
|
||||
def get_allowed_types(self):
|
||||
return glare_fields.Dict
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'maxProperties': self.size}
|
||||
@ -179,20 +193,55 @@ class MaxDictSize(MaxSize):
|
||||
|
||||
class MaxListSize(MaxSize):
|
||||
|
||||
def get_allowed_types(self):
|
||||
return glare_fields.List
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.List,
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'maxItems': self.size}
|
||||
|
||||
|
||||
class MaxNumberSize(SizeValidator):
|
||||
def validate(self, value):
|
||||
if value > self.size:
|
||||
raise ValueError("Number is too big: %s. Max allowed number is "
|
||||
"%s" % (value, self.size))
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class MinSize(SizeValidator):
|
||||
|
||||
def get_allowed_types(self):
|
||||
def __call__(self, value):
|
||||
l = len(value)
|
||||
if l < self.size:
|
||||
raise ValueError(
|
||||
_("Number of items must be greater than "
|
||||
"%(size)d. Current size: %(cur)d") %
|
||||
{'size': self.size, 'cur': l})
|
||||
|
||||
|
||||
class MinDictSize(MinSize):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'minProperties': self.size}
|
||||
|
||||
|
||||
class MinListSize(MinSize):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.List,
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'minItems': self.size}
|
||||
|
||||
|
||||
class MaxNumberSize(SizeValidator):
|
||||
|
||||
def __call__(self, value):
|
||||
if value > self.size:
|
||||
raise ValueError("Number is too big: %d. Max allowed number is "
|
||||
"%d" % (value, self.size))
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.IntegerField, fields.FloatField
|
||||
|
||||
def to_jsonschema(self):
|
||||
@ -200,12 +249,14 @@ class MaxNumberSize(SizeValidator):
|
||||
|
||||
|
||||
class MinNumberSize(SizeValidator):
|
||||
def validate(self, value):
|
||||
if value < self.size:
|
||||
raise ValueError("Number is too small: %s. Min allowed number is "
|
||||
"%s" % (value, self.size))
|
||||
|
||||
def get_allowed_types(self):
|
||||
def __call__(self, value):
|
||||
if value < self.size:
|
||||
raise ValueError("Number is too small: %d. Min allowed number is "
|
||||
"%d" % (value, self.size))
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return fields.IntegerField, fields.FloatField
|
||||
|
||||
def to_jsonschema(self):
|
||||
@ -213,50 +264,34 @@ class MinNumberSize(SizeValidator):
|
||||
|
||||
|
||||
class Unique(Validator):
|
||||
|
||||
def __init__(self, convert_to_set=False):
|
||||
self.convert_to_set = convert_to_set
|
||||
|
||||
def get_allowed_types(self):
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.List,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
if self.convert_to_set:
|
||||
value[:] = list(set(value))
|
||||
elif len(value) != len(set(value)):
|
||||
raise ValueError(_("List items %s must be unique.") % value)
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'unique': True}
|
||||
|
||||
|
||||
class AllowedListValues(Validator):
|
||||
def __init__(self, allowed_values):
|
||||
self.allowed_items = allowed_values
|
||||
|
||||
def get_allowed_types(self):
|
||||
return glare_fields.List,
|
||||
|
||||
def validate(self, value):
|
||||
for item in value:
|
||||
if item not in self.allowed_items:
|
||||
raise ValueError(
|
||||
_("Value %(item)s is not allowed in list. "
|
||||
"Allowed list values: %(allowed)s") %
|
||||
{"item": item,
|
||||
"allowed": self.allowed_items})
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'enum': self.allowed_items}
|
||||
return {'uniqueItems': True}
|
||||
|
||||
|
||||
class AllowedDictKeys(Validator):
|
||||
|
||||
def __init__(self, allowed_keys):
|
||||
self.allowed_items = allowed_keys
|
||||
|
||||
def get_allowed_types(self):
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for item in value:
|
||||
if item not in self.allowed_items:
|
||||
raise ValueError(_("Key %(item)s is not allowed in dict. "
|
||||
@ -271,17 +306,19 @@ class AllowedDictKeys(Validator):
|
||||
|
||||
|
||||
class RequiredDictKeys(Validator):
|
||||
|
||||
def __init__(self, required_keys):
|
||||
self.required_items = required_keys
|
||||
|
||||
def get_allowed_types(self):
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for item in self.required_items:
|
||||
if item not in value:
|
||||
raise ValueError(_("Key \"%(item)s\" is required in property "
|
||||
"dictionary: %(value)s.") %
|
||||
raise ValueError(_("Key \"%(item)s\" is required in "
|
||||
"dictionary %(value)s.") %
|
||||
{"item": item,
|
||||
"value": ''.join(
|
||||
'{}:{}, '.format(key, val)
|
||||
@ -292,49 +329,69 @@ class RequiredDictKeys(Validator):
|
||||
|
||||
|
||||
class MaxDictKeyLen(SizeValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for key in value:
|
||||
if len(str(key)) > self.size:
|
||||
raise ValueError(_("Dict key length %(key)s must be less than "
|
||||
"%(size)s.") % {'key': key,
|
||||
"%(size)d.") % {'key': key,
|
||||
'size': self.size})
|
||||
|
||||
|
||||
class MinDictKeyLen(SizeValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for key in value:
|
||||
if len(str(key)) < self.size:
|
||||
raise ValueError(_("Dict key length %(key)s must be bigger "
|
||||
"than %(size)s.") % {'key': key,
|
||||
"than %(size)d.") % {'key': key,
|
||||
'size': self.size})
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ElementValidator(Validator):
|
||||
|
||||
def __init__(self, validators):
|
||||
self.validators = validators
|
||||
|
||||
|
||||
class ListElementValidator(ElementValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.List,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for v in value:
|
||||
for validator in self.validators:
|
||||
validator(v)
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'itemValidators': [
|
||||
val.to_jsonschema() for val in self.validators
|
||||
]}
|
||||
|
||||
|
||||
class DictElementValidator(ElementValidator):
|
||||
def get_allowed_types(self):
|
||||
|
||||
@staticmethod
|
||||
def get_allowed_types():
|
||||
return glare_fields.Dict,
|
||||
|
||||
def validate(self, value):
|
||||
def __call__(self, value):
|
||||
for v in value.values():
|
||||
for validator in self.validators:
|
||||
validator(v)
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'propertyValidators': [
|
||||
val.to_jsonschema() for val in self.validators
|
||||
]}
|
||||
|
@ -57,7 +57,9 @@ fixture_base_props = {
|
||||
u'readOnly': True,
|
||||
u'sortable': True,
|
||||
u'type': u'string'},
|
||||
u'metadata': {u'additionalProperties': {u'type': u'string'},
|
||||
u'metadata': {u'additionalProperties': {u'maxLength': 255,
|
||||
u'minLength': 1,
|
||||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'description': u'Key-value dict with useful information '
|
||||
u'about an artifact.',
|
||||
@ -102,12 +104,15 @@ fixture_base_props = {
|
||||
u'description': u'List of tags added to Artifact.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'minLength': 1,
|
||||
u'pattern': u'^[^,/]+$',
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'array', u'null'],
|
||||
u'unique': True},
|
||||
u'uniqueItems': True},
|
||||
u'updated_at': {
|
||||
u'description': u'Datetime when artifact has been updated last time.',
|
||||
u'filter_ops': [u'lt', u'gt'],
|
||||
@ -188,13 +193,13 @@ fixtures = {
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'Boolean',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'type': [u'boolean',
|
||||
u'null']},
|
||||
u'bool2': {u'default': False,
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'Boolean',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'type': [u'boolean',
|
||||
u'null']},
|
||||
u'link1': {u'filter_ops': [u'eq',
|
||||
u'neq'],
|
||||
@ -245,7 +250,7 @@ fixtures = {
|
||||
u'null']},
|
||||
u'dict_of_int': {
|
||||
u'additionalProperties': {
|
||||
u'type': u'string'},
|
||||
u'type': u'integer'},
|
||||
u'default': {},
|
||||
u'filter_ops': [u'eq', u'in'],
|
||||
u'glareType': u'IntegerDict',
|
||||
@ -270,8 +275,8 @@ fixtures = {
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'array', u'null']},
|
||||
u'dict_of_str': {
|
||||
u'additionalProperties': {
|
||||
u'type': u'string'},
|
||||
u'additionalProperties': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'filter_ops': [u'eq', u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
@ -284,15 +289,18 @@ fixtures = {
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 3,
|
||||
u'properties': {
|
||||
u'abc': {u'type': [u'string',
|
||||
u'null']},
|
||||
u'def': {u'type': [u'string',
|
||||
u'null']},
|
||||
u'ghi': {u'type': [u'string',
|
||||
u'null']},
|
||||
u'jkl': {u'type': [u'string',
|
||||
u'null']}},
|
||||
u'properties': {u'abc': {u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'def': {u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'ghi': {u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'jkl': {u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']}},
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'object',
|
||||
u'null']},
|
||||
@ -361,7 +369,7 @@ fixtures = {
|
||||
u'filter_ops': [u'eq', u'in'],
|
||||
u'glareType': u'IntegerList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'type': u'integer'},
|
||||
u'maxItems': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'array',
|
||||
@ -369,8 +377,8 @@ fixtures = {
|
||||
u'list_of_str': {u'default': [],
|
||||
u'filter_ops': [u'eq', u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'array',
|
||||
@ -378,13 +386,13 @@ fixtures = {
|
||||
u'list_validators': {u'default': [],
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'maxItems': 3,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'array',
|
||||
u'null'],
|
||||
u'unique': True},
|
||||
u'uniqueItems': True},
|
||||
u'small_blob': {u'additionalProperties': False,
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
@ -520,7 +528,8 @@ fixtures = {
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
u'type': [u'array',
|
||||
@ -533,11 +542,12 @@ fixtures = {
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'type': [u'array',
|
||||
u'null'],
|
||||
u'unique': True},
|
||||
u'uniqueItems': True},
|
||||
u'dependencies': {
|
||||
u'default': [],
|
||||
u'description': u'List of package dependencies for '
|
||||
@ -561,7 +571,8 @@ fixtures = {
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'inherits': {
|
||||
u'additionalProperties': {u'type': u'string'},
|
||||
u'additionalProperties': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
@ -575,7 +586,8 @@ fixtures = {
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'items': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
u'type': [u'array',
|
||||
@ -792,7 +804,8 @@ fixtures = {
|
||||
u'name': u'heat_templates',
|
||||
u'properties': generate_type_props({
|
||||
u'default_envs': {
|
||||
u'additionalProperties': {u'type': u'string'},
|
||||
u'additionalProperties': {u'maxLength': 255,
|
||||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'description': u'Default environments that can '
|
||||
u'be applied to the template if no '
|
||||
|
368
glare/tests/unit/test_validators.py
Normal file
368
glare/tests/unit/test_validators.py
Normal file
@ -0,0 +1,368 @@
|
||||
# Copyright 2017 - Nokia Networks
|
||||
#
|
||||
# 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_versionedobjects import fields
|
||||
|
||||
from glare.objects.meta import fields as glare_fields
|
||||
from glare.objects.meta import validators
|
||||
from glare.tests.unit import base
|
||||
|
||||
|
||||
class TestValidators(base.BaseTestArtifactAPI):
|
||||
|
||||
"""Class for testing field validators."""
|
||||
|
||||
def test_uuid(self):
|
||||
# test if applied string is uuid4
|
||||
validator = validators.UUID()
|
||||
|
||||
# valid string - no exception
|
||||
validator('167f8083-6bef-4f37-bf04-250343a2d53c')
|
||||
|
||||
# invalid string - ValueError
|
||||
self.assertRaises(ValueError, validator, 'INVALID')
|
||||
|
||||
# only strings can be applied as values
|
||||
self.assertEqual((fields.StringField,),
|
||||
validators.UUID.get_allowed_types())
|
||||
|
||||
self.assertEqual(
|
||||
{'pattern': ('^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])'
|
||||
'{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$')},
|
||||
validator.to_jsonschema())
|
||||
|
||||
def test_allowed_values(self):
|
||||
# test that field may have preoccupied values
|
||||
validator_s = validators.AllowedValues(['aaa', 'bbb'])
|
||||
validator_i = validators.AllowedValues([1, 2, 3])
|
||||
validator_f = validators.AllowedValues([1.0, 2.0, 3.0])
|
||||
|
||||
# allowed value - no exception
|
||||
validator_s('aaa')
|
||||
validator_s('bbb')
|
||||
validator_i(1)
|
||||
validator_i(3)
|
||||
validator_f(1.0)
|
||||
validator_f(3.0)
|
||||
|
||||
# not allowed value - value error
|
||||
self.assertRaises(ValueError, validator_s, 'a')
|
||||
self.assertRaises(ValueError, validator_i, 4)
|
||||
self.assertRaises(ValueError, validator_f, 4.0)
|
||||
|
||||
# only strings, integers and floats can be applied as values
|
||||
self.assertEqual(
|
||||
(fields.StringField, fields.IntegerField, fields.FloatField),
|
||||
validators.AllowedValues.get_allowed_types())
|
||||
|
||||
self.assertEqual({'enum': ['aaa', 'bbb']}, validator_s.to_jsonschema())
|
||||
self.assertEqual({'enum': [1, 2, 3]}, validator_i.to_jsonschema())
|
||||
self.assertEqual({'enum': [1.0, 2.0, 3.0]},
|
||||
validator_f.to_jsonschema())
|
||||
|
||||
def test_max_str_len(self):
|
||||
# test max allowed string length
|
||||
validator = validators.MaxStrLen(10)
|
||||
|
||||
# allowed length - no exception
|
||||
validator('a' * 10)
|
||||
validator('')
|
||||
|
||||
# too long string - value error
|
||||
self.assertRaises(ValueError, validator, 'a' * 11)
|
||||
|
||||
# only strings can be applied as values
|
||||
self.assertEqual((fields.StringField,),
|
||||
validators.MaxStrLen.get_allowed_types())
|
||||
|
||||
self.assertEqual({'maxLength': 10}, validator.to_jsonschema())
|
||||
|
||||
def test_min_str_len(self):
|
||||
# test min allowed string length
|
||||
validator = validators.MinStrLen(10)
|
||||
|
||||
# allowed length - no exception
|
||||
validator('a' * 10)
|
||||
|
||||
# too short string - value error
|
||||
self.assertRaises(ValueError, validator, 'a' * 9)
|
||||
self.assertRaises(ValueError, validator, '')
|
||||
|
||||
# only strings can be applied as values
|
||||
self.assertEqual((fields.StringField,),
|
||||
validators.MinStrLen.get_allowed_types())
|
||||
|
||||
self.assertEqual({'minLength': 10}, validator.to_jsonschema())
|
||||
|
||||
def test_forbidden_chars(self):
|
||||
# test that string has no forbidden chars
|
||||
validator = validators.ForbiddenChars(['a', '?'])
|
||||
|
||||
# allowed length - no exception
|
||||
validator('b' * 10)
|
||||
|
||||
# string contains forbidden chars - value error
|
||||
self.assertRaises(ValueError, validator, 'abc')
|
||||
self.assertRaises(ValueError, validator, '?')
|
||||
|
||||
# only strings can be applied as values
|
||||
self.assertEqual((fields.StringField,),
|
||||
validators.ForbiddenChars.get_allowed_types())
|
||||
|
||||
self.assertEqual({'pattern': '^[^a?]+$'}, validator.to_jsonschema())
|
||||
|
||||
def test_max_dict_size(self):
|
||||
# test max dict size
|
||||
validator = validators.MaxDictSize(3)
|
||||
|
||||
# allowed size - no exception
|
||||
validator({'a': 1, 'b': 2, 'c': 3})
|
||||
validator({})
|
||||
|
||||
# too big dictionary - value error
|
||||
self.assertRaises(ValueError, validator,
|
||||
{'a': 1, 'b': 2, 'c': 3, 'd': 4})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.MaxDictSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'maxProperties': 3}, validator.to_jsonschema())
|
||||
|
||||
def test_min_dict_size(self):
|
||||
# test min dict size
|
||||
validator = validators.MinDictSize(3)
|
||||
|
||||
# allowed size - no exception
|
||||
validator({'a': 1, 'b': 2, 'c': 3})
|
||||
|
||||
# too small dictionary - value error
|
||||
self.assertRaises(ValueError, validator,
|
||||
{'a': 1, 'b': 2})
|
||||
self.assertRaises(ValueError, validator, {})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.MinDictSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'minProperties': 3}, validator.to_jsonschema())
|
||||
|
||||
def test_max_list_size(self):
|
||||
# test max list size
|
||||
validator = validators.MaxListSize(3)
|
||||
|
||||
# allowed size - no exception
|
||||
validator(['a', 'b', 'c'])
|
||||
validator([])
|
||||
|
||||
# too big list - value error
|
||||
self.assertRaises(ValueError, validator,
|
||||
['a', 'b', 'c', 'd'])
|
||||
|
||||
# only lists can be applied as values
|
||||
self.assertEqual((glare_fields.List,),
|
||||
validators.MaxListSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'maxItems': 3}, validator.to_jsonschema())
|
||||
|
||||
def test_min_list_size(self):
|
||||
# test max list size
|
||||
validator = validators.MinListSize(3)
|
||||
|
||||
# allowed size - no exception
|
||||
validator(['a', 'b', 'c'])
|
||||
|
||||
# too small list - value error
|
||||
self.assertRaises(ValueError, validator, ['a', 'b'])
|
||||
self.assertRaises(ValueError, validator, [])
|
||||
|
||||
# only lists can be applied as values
|
||||
self.assertEqual((glare_fields.List,),
|
||||
validators.MinListSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'minItems': 3}, validator.to_jsonschema())
|
||||
|
||||
def test_max_number_size(self):
|
||||
# test max number size
|
||||
validator = validators.MaxNumberSize(10)
|
||||
|
||||
# allowed size - no exception
|
||||
validator(10)
|
||||
validator(0)
|
||||
validator(10.0)
|
||||
validator(0.0)
|
||||
|
||||
# too big number - value error
|
||||
self.assertRaises(ValueError, validator, 11)
|
||||
self.assertRaises(ValueError, validator, 10.1)
|
||||
|
||||
# only integers and floats can be applied as values
|
||||
self.assertEqual((fields.IntegerField, fields.FloatField),
|
||||
validators.MaxNumberSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'maximum': 10}, validator.to_jsonschema())
|
||||
|
||||
def test_min_number_size(self):
|
||||
# test min number size
|
||||
validator = validators.MinNumberSize(10)
|
||||
|
||||
# allowed size - no exception
|
||||
validator(10)
|
||||
validator(10.0)
|
||||
|
||||
# too small number - value error
|
||||
self.assertRaises(ValueError, validator, 9)
|
||||
self.assertRaises(ValueError, validator, 9.9)
|
||||
self.assertRaises(ValueError, validator, 0)
|
||||
self.assertRaises(ValueError, validator, 0)
|
||||
|
||||
# only integers and floats can be applied as values
|
||||
self.assertEqual((fields.IntegerField, fields.FloatField),
|
||||
validators.MinNumberSize.get_allowed_types())
|
||||
|
||||
self.assertEqual({'minimum': 10}, validator.to_jsonschema())
|
||||
|
||||
def test_unique(self):
|
||||
# test uniqueness of list elements
|
||||
|
||||
# validator raises exception in case of duplicates in the list
|
||||
validator = validators.Unique()
|
||||
# non strict validator removes duplicates without raising of ValueError
|
||||
validator_nonstrict = validators.Unique(convert_to_set=True)
|
||||
|
||||
# all elements unique - no exception
|
||||
validator(['a', 'b', 'c'])
|
||||
validator([])
|
||||
|
||||
# duplicates in the list - value error
|
||||
self.assertRaises(ValueError, validator, ['a', 'a', 'b'])
|
||||
|
||||
# non-strict validator converts list to set of elements
|
||||
l = ['a', 'a', 'b']
|
||||
validator_nonstrict(l)
|
||||
self.assertEqual({'a', 'b'}, set(l))
|
||||
|
||||
# only lists can be applied as values
|
||||
self.assertEqual((glare_fields.List,),
|
||||
validators.Unique.get_allowed_types())
|
||||
|
||||
self.assertEqual({'uniqueItems': True}, validator.to_jsonschema())
|
||||
|
||||
def test_allowed_dict_keys(self):
|
||||
# test that dictionary contains only allowed keys
|
||||
validator = validators.AllowedDictKeys(['aaa', 'bbb', 'ccc'])
|
||||
|
||||
# only allowed keys - no exception
|
||||
validator({'aaa': 5, 'bbb': 6})
|
||||
validator({})
|
||||
|
||||
# if dictionary has other keys - value error
|
||||
self.assertRaises(ValueError, validator, {'aaa': 5, 'a': 7, 'bbb': 6})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.AllowedDictKeys.get_allowed_types())
|
||||
|
||||
self.assertEqual({'properties': {'aaa': {}, 'bbb': {}, 'ccc': {}}},
|
||||
validator.to_jsonschema())
|
||||
|
||||
def test_required_dict_keys(self):
|
||||
# test that dictionary has required keys
|
||||
validator = validators.RequiredDictKeys(['aaa', 'bbb'])
|
||||
|
||||
# if dict has required keys - no exception
|
||||
validator({'aaa': 5, 'bbb': 6})
|
||||
validator({'aaa': 5, 'bbb': 6, 'ccc': 7})
|
||||
|
||||
# in other case - value error
|
||||
self.assertRaises(ValueError, validator, {'aaa': 5, 'a': 7})
|
||||
self.assertRaises(ValueError, validator, {})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.RequiredDictKeys.get_allowed_types())
|
||||
|
||||
self.assertEqual({'required': ['aaa', 'bbb']},
|
||||
validator.to_jsonschema())
|
||||
|
||||
def test_max_dict_key_len(self):
|
||||
# test max limit for dict key length
|
||||
validator = validators.MaxDictKeyLen(5)
|
||||
|
||||
# if key length less than the limit - no exception
|
||||
validator({'aaaaa': 5, 'bbbbb': 4})
|
||||
|
||||
# in other case - value error
|
||||
self.assertRaises(ValueError, validator, {'aaaaaa': 5, 'a': 7})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.MaxDictKeyLen.get_allowed_types())
|
||||
|
||||
def test_mix_dict_key_len(self):
|
||||
# test min limit for dict key length
|
||||
validator = validators.MinDictKeyLen(5)
|
||||
|
||||
# if key length bigger than the limit - no exception
|
||||
validator({'aaaaa': 5, 'bbbbb': 4})
|
||||
|
||||
# in other case - value error
|
||||
self.assertRaises(ValueError, validator, {'aaaaa': 5, 'a': 7})
|
||||
|
||||
# only dicts can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.MinDictKeyLen.get_allowed_types())
|
||||
|
||||
def test_allowed_list_values(self):
|
||||
# test that list contains only allowed values
|
||||
# AllowedValues validator will be applied to each element of the list
|
||||
validator = validators.ListElementValidator(
|
||||
[validators.AllowedValues(['aaa', 'bbb', 'ccc'])])
|
||||
|
||||
# only allowed values - no exception
|
||||
validator(['aaa', 'bbb'])
|
||||
validator([])
|
||||
|
||||
# if list has other values - value error
|
||||
self.assertRaises(ValueError, validator, ['aaa', 'a', 'bbb'])
|
||||
self.assertRaises(ValueError, validator, ['ccc', {'aaa': 'bbb'}])
|
||||
|
||||
# only lists can be applied as values
|
||||
self.assertEqual((glare_fields.List,),
|
||||
validators.ListElementValidator.get_allowed_types())
|
||||
|
||||
self.assertEqual({'itemValidators': [{'enum': ['aaa', 'bbb', 'ccc']}]},
|
||||
validator.to_jsonschema())
|
||||
|
||||
def test_allowed_dict_values(self):
|
||||
# test that dict contains only allowed values
|
||||
# AllowedValues validator will be applied to each element of the dict
|
||||
validator = validators.DictElementValidator(
|
||||
[validators.AllowedValues(['aaa', 'bbb', 'ccc'])])
|
||||
|
||||
# only allowed values - no exception
|
||||
validator({'a': 'aaa', 'b': 'bbb'})
|
||||
validator({})
|
||||
|
||||
# if dict has other values - value error
|
||||
self.assertRaises(ValueError, validator,
|
||||
{'a': 'aaa', 'b': 'bbb', 'c': 'c'})
|
||||
|
||||
# only dict can be applied as values
|
||||
self.assertEqual((glare_fields.Dict,),
|
||||
validators.DictElementValidator.get_allowed_types())
|
||||
|
||||
self.assertEqual(
|
||||
{'propertyValidators': [{'enum': ['aaa', 'bbb', 'ccc']}]},
|
||||
validator.to_jsonschema())
|
Loading…
Reference in New Issue
Block a user