diff options
Diffstat (limited to 'python/voluptuous/tests.md')
-rw-r--r-- | python/voluptuous/tests.md | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/python/voluptuous/tests.md b/python/voluptuous/tests.md new file mode 100644 index 000000000..18f6fbafa --- /dev/null +++ b/python/voluptuous/tests.md @@ -0,0 +1,268 @@ +Error reporting should be accurate: + + >>> from voluptuous import * + >>> schema = Schema(['one', {'two': 'three', 'four': ['five'], + ... 'six': {'seven': 'eight'}}]) + >>> schema(['one']) + ['one'] + >>> schema([{'two': 'three'}]) + [{'two': 'three'}] + +It should show the exact index and container type, in this case a list +value: + + >>> try: + ... schema(['one', 'two']) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) == 'expected a dictionary @ data[1]' + True + +It should also be accurate for nested values: + + >>> try: + ... schema([{'two': 'nine'}]) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "not a valid value for dictionary value @ data[0]['two']" + + >>> try: + ... schema([{'four': ['nine']}]) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "not a valid value @ data[0]['four'][0]" + + >>> try: + ... schema([{'six': {'seven': 'nine'}}]) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "not a valid value for dictionary value @ data[0]['six']['seven']" + +Errors should be reported depth-first: + + >>> validate = Schema({'one': {'two': 'three', 'four': 'five'}}) + >>> try: + ... validate({'one': {'four': 'six'}}) + ... except Invalid as e: + ... print(e) + ... print(e.path) + not a valid value for dictionary value @ data['one']['four'] + ['one', 'four'] + +Voluptuous supports validation when extra fields are present in the +data: + + >>> schema = Schema({'one': 1, Extra: object}) + >>> schema({'two': 'two', 'one': 1}) == {'two': 'two', 'one': 1} + True + >>> schema = Schema({'one': 1}) + >>> try: + ... schema({'two': 2}) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "extra keys not allowed @ data['two']" + +dict, list, and tuple should be available as type validators: + + >>> Schema(dict)({'a': 1, 'b': 2}) == {'a': 1, 'b': 2} + True + >>> Schema(list)([1,2,3]) + [1, 2, 3] + >>> Schema(tuple)((1,2,3)) + (1, 2, 3) + +Validation should return instances of the right types when the types are +subclasses of dict or list: + + >>> class Dict(dict): + ... pass + >>> + >>> d = Schema(dict)(Dict(a=1, b=2)) + >>> d == {'a': 1, 'b': 2} + True + >>> type(d) is Dict + True + >>> class List(list): + ... pass + >>> + >>> l = Schema(list)(List([1,2,3])) + >>> l + [1, 2, 3] + >>> type(l) is List + True + +Multiple errors are reported: + + >>> schema = Schema({'one': 1, 'two': 2}) + >>> try: + ... schema({'one': 2, 'two': 3, 'three': 4}) + ... except MultipleInvalid as e: + ... errors = sorted(e.errors, key=lambda k: str(k)) + ... print([str(i) for i in errors]) # doctest: +NORMALIZE_WHITESPACE + ["extra keys not allowed @ data['three']", + "not a valid value for dictionary value @ data['one']", + "not a valid value for dictionary value @ data['two']"] + >>> schema = Schema([[1], [2], [3]]) + >>> try: + ... schema([1, 2, 3]) + ... except MultipleInvalid as e: + ... print([str(i) for i in e.errors]) # doctest: +NORMALIZE_WHITESPACE + ['expected a list @ data[0]', + 'expected a list @ data[1]', + 'expected a list @ data[2]'] + +Required fields in dictionary which are invalid should not have required : + + >>> from voluptuous import * + >>> schema = Schema({'one': {'two': 3}}, required=True) + >>> try: + ... schema({'one': {'two': 2}}) + ... except MultipleInvalid as e: + ... errors = e.errors + >>> 'required' in ' '.join([x.msg for x in errors]) + False + +Multiple errors for nested fields in dicts and objects: + +> \>\>\> from collections import namedtuple \>\>\> validate = Schema({ +> ... 'anobject': Object({ ... 'strfield': str, ... 'intfield': int ... +> }) ... }) \>\>\> try: ... SomeObj = namedtuple('SomeObj', ('strfield', +> 'intfield')) ... validate({'anobject': SomeObj(strfield=123, +> intfield='one')}) ... except MultipleInvalid as e: ... +> print(sorted(str(i) for i in e.errors)) \# doctest: +> +NORMALIZE\_WHITESPACE ["expected int for object value @ +> data['anobject']['intfield']", "expected str for object value @ +> data['anobject']['strfield']"] + +Custom classes validate as schemas: + + >>> class Thing(object): + ... pass + >>> schema = Schema(Thing) + >>> t = schema(Thing()) + >>> type(t) is Thing + True + +Classes with custom metaclasses should validate as schemas: + + >>> class MyMeta(type): + ... pass + >>> class Thing(object): + ... __metaclass__ = MyMeta + >>> schema = Schema(Thing) + >>> t = schema(Thing()) + >>> type(t) is Thing + True + +Schemas built with All() should give the same error as the original +validator (Issue \#26): + + >>> schema = Schema({ + ... Required('items'): All([{ + ... Required('foo'): str + ... }]) + ... }) + + >>> try: + ... schema({'items': [{}]}) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "required key not provided @ data['items'][0]['foo']" + +Validator should return same instance of the same type for object: + + >>> class Structure(object): + ... def __init__(self, q=None): + ... self.q = q + ... def __repr__(self): + ... return '{0.__name__}(q={1.q!r})'.format(type(self), self) + ... + >>> schema = Schema(Object({'q': 'one'}, cls=Structure)) + >>> type(schema(Structure(q='one'))) is Structure + True + +Object validator should treat cls argument as optional. In this case it +shouldn't check object type: + + >>> from collections import namedtuple + >>> NamedTuple = namedtuple('NamedTuple', ('q',)) + >>> schema = Schema(Object({'q': 'one'})) + >>> named = NamedTuple(q='one') + >>> schema(named) == named + True + >>> schema(named) + NamedTuple(q='one') + +If cls argument passed to object validator we should check object type: + + >>> schema = Schema(Object({'q': 'one'}, cls=Structure)) + >>> schema(NamedTuple(q='one')) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + MultipleInvalid: expected a <class 'Structure'> + >>> schema = Schema(Object({'q': 'one'}, cls=NamedTuple)) + >>> schema(NamedTuple(q='one')) + NamedTuple(q='one') + +Ensure that objects with \_\_slots\_\_ supported properly: + + >>> class SlotsStructure(Structure): + ... __slots__ = ['q'] + ... + >>> schema = Schema(Object({'q': 'one'})) + >>> schema(SlotsStructure(q='one')) + SlotsStructure(q='one') + >>> class DictStructure(object): + ... __slots__ = ['q', '__dict__'] + ... def __init__(self, q=None, page=None): + ... self.q = q + ... self.page = page + ... def __repr__(self): + ... return '{0.__name__}(q={1.q!r}, page={1.page!r})'.format(type(self), self) + ... + >>> structure = DictStructure(q='one') + >>> structure.page = 1 + >>> try: + ... schema(structure) + ... raise AssertionError('MultipleInvalid not raised') + ... except MultipleInvalid as e: + ... exc = e + >>> str(exc) + "extra keys not allowed @ data['page']" + + >>> schema = Schema(Object({'q': 'one', Extra: object})) + >>> schema(structure) + DictStructure(q='one', page=1) + +Ensure that objects can be used with other validators: + + >>> schema = Schema({'meta': Object({'q': 'one'})}) + >>> schema({'meta': Structure(q='one')}) + {'meta': Structure(q='one')} + +Ensure that subclasses of Invalid of are raised as is. + + >>> class SpecialInvalid(Invalid): + ... pass + ... + >>> def custom_validator(value): + ... raise SpecialInvalid('boom') + ... + >>> schema = Schema({'thing': custom_validator}) + >>> try: + ... schema({'thing': 'not an int'}) + ... except MultipleInvalid as e: + ... exc = e + >>> exc.errors[0].__class__.__name__ + 'SpecialInvalid' |