summaryrefslogtreecommitdiff
path: root/jsonschema-0.6/tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'jsonschema-0.6/tests.py')
-rw-r--r--jsonschema-0.6/tests.py796
1 files changed, 796 insertions, 0 deletions
diff --git a/jsonschema-0.6/tests.py b/jsonschema-0.6/tests.py
new file mode 100644
index 0000000..56b1481
--- /dev/null
+++ b/jsonschema-0.6/tests.py
@@ -0,0 +1,796 @@
+from __future__ import unicode_literals
+from decimal import Decimal
+from functools import wraps
+import sys
+import warnings
+
+if sys.version_info[:2] < (2, 7): # pragma: no cover
+ import unittest2 as unittest
+else:
+ import unittest
+
+
+from jsonschema import (
+ PY3, SchemaError, ValidationError, ErrorTree, Validator,
+ iteritems, validate
+)
+
+
+if PY3:
+ basestring = unicode = str
+
+
+class ParametrizedTestCase(type):
+ """
+ A (deliberately naive & specialized) parametrized test.
+
+ """
+
+ def __new__(cls, name, bases, attrs):
+ attr = {}
+
+ for k, v in iteritems(attrs):
+ parameters = getattr(v, "_parameters", None)
+
+ if parameters is not None:
+ for parameter in parameters:
+ parametrized_name, args = parameter[0], parameter[1:]
+ fn = partial(v, *args)
+
+ names = ["test", k]
+ if parametrized_name:
+ names.append(parametrized_name)
+
+ fn_name = "_".join(names)
+ if not PY3:
+ fn_name = fn_name.encode('utf8')
+ fn.__name__ = fn_name
+ attr[fn.__name__] = fn
+ else:
+ attr[k] = v
+
+ if not PY3:
+ name = name.encode('utf8')
+
+ return super(ParametrizedTestCase, cls).__new__(cls, name, bases, attr)
+
+
+# Inscrutable way to create metaclasses in a Python 2/3 compatible way
+# See: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/
+ParameterizedTestCase = ParametrizedTestCase(
+ 'ParameterizedTestCase', (object,), {}
+)
+
+
+def parametrized(*runs):
+ def parametrized_test(fn):
+ fn._parameters = runs
+ return fn
+ return parametrized_test
+
+
+def partial(fn, *args, **kwargs):
+ """
+ ``functools.partial`` for methods (suitable for binding).
+
+ """
+
+ @wraps(fn)
+ def _partial(self):
+ return fn(self, *args, **kwargs)
+ return _partial
+
+
+def validation_test(schema=(), initkwargs=(), **kwschema):
+ schema = dict(schema, **kwschema)
+ initkwargs = dict(initkwargs)
+
+ def _validation_test(self, expected, instance):
+ if expected == "valid":
+ validate(instance, schema, **initkwargs)
+ elif expected == "invalid":
+ with self.assertRaises(ValidationError):
+ validate(instance, schema, **initkwargs)
+ else: # pragma: no cover
+ raise ValueError("You spelled something wrong.")
+
+ return _validation_test
+
+
+class TestValidate(ParameterizedTestCase, unittest.TestCase):
+ integer = parametrized(
+ ("integer", "valid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="integer"))
+
+ number = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="number"))
+
+ _string = [
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("unicode", "valid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ ]
+
+ if not PY3:
+ # The JSON module in Python2 does not always produce unicode objects :/
+ _string.append(("bytestring", "valid", b"foo"))
+
+ string = parametrized(*_string)(validation_test(type="string"))
+
+ object = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "valid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="object"))
+
+ array = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "valid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type="array"))
+
+ boolean = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("true", "valid", True),
+ ("false", "valid", False),
+ ("null", "invalid", None),
+ )(validation_test(type="boolean"))
+
+ null = parametrized(
+ ("integer", "invalid", 1),
+ ("number", "invalid", 1.1),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "valid", None),
+ )(validation_test(type="null"))
+
+ any = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("string", "valid", "foo"),
+ ("object", "valid", {}),
+ ("array", "valid", []),
+ ("boolean", "valid", True),
+ ("null", "valid", None),
+ )(validation_test(type="any"))
+
+ multiple_types = parametrized(
+ ("integer", "valid", 1),
+ ("string", "valid", "foo"),
+ ("number", "invalid", 1.1),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type=["integer", "string"]))
+
+ multiple_types_schema = parametrized(
+ ("match", "valid", [1, 2]),
+ ("other_match", "valid", {"foo" : "bar"}),
+ ("number", "invalid", 1.1),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(type=["array", {"type" : "object"}]))
+
+ multiple_types_subschema = parametrized(
+ ("integer", "valid", 1),
+ ("object_right_type", "valid", {"foo" : None}),
+ ("object_wrong_type", "invalid", {"foo" : 1}),
+ ("object_another_wrong_type", "invalid", {"foo" : 1.1}),
+ )(validation_test(
+ type=["integer", {"properties" : {"foo" : {"type" : "null"}}}]
+ ))
+
+ def test_multiple_types_nonobject(self):
+ """
+ Regression test for issue #18.
+
+ """
+ validate(
+ [1, 2, 3],
+ {"type" : [{"type" : ["string"]}, {"type" : ["array", "null"]}]}
+ )
+
+ properties = parametrized(
+ ("", "valid", {"foo" : 1, "bar" : "baz"}),
+ ("extra_property", "valid",
+ {"foo" : 1, "bar" : "baz", "quux" : 42}),
+ ("invalid_type", "invalid", {"foo" : 1, "bar" : []}),
+ )(validation_test(
+ {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+ ))
+
+ patternProperties = parametrized(
+ ("single_match", "valid", {"foo" : 1}),
+ ("multiple_match", "valid", {"foo" : 1, "fah" : 2, "bar" : "baz"}),
+ ("single_mismatch", "invalid", {"foo" : "bar"}),
+ ("multiple_mismatch", "invalid", {"foo" : 1, "fah" : "bar"}),
+ )(validation_test(patternProperties={"f.*" : {"type" : "integer"}}))
+
+ multiple_patternProperties = parametrized(
+ ("match", "valid", {"a" : 21}),
+ ("other_match", "valid", {"aaaa" : 18}),
+ ("multiple_match", "valid", {"a" : 21, "aaaa" : 18}),
+ ("mismatch", "invalid", {"aaa" : "bar"}),
+ ("other_mismatch", "invalid", {"aaaa" : 31}),
+ ("multiple_mismatch", "invalid", {"aaa" : "foo", "aaaa" : 32}),
+ )(validation_test(patternProperties={
+ "a*" : {"type" : "integer"},
+ "aaa*" : {"maximum" : 20},
+ }
+ ))
+
+ def test_additionalProperties_allowed_by_default(self):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+ validate({"foo" : 1, "bar" : "baz", "quux" : False}, schema)
+
+ @parametrized(
+ ("", False),
+ ("schema", {"type" : "boolean"}),
+ )
+ def additionalProperties(self, aP):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ },
+
+ "additionalProperties" : aP,
+ }
+
+ with self.assertRaises(ValidationError):
+ validate({"foo" : 1, "bar" : "baz", "quux" : "boom"}, schema)
+
+ def test_additionalProperties_ignores_nonobjects(self):
+ validate(None, {"additionalProperties" : False})
+
+ @parametrized(
+ ("single_extra", {"foo" : 2}, ["'foo' was unexpected)"]),
+ ("multiple_extras",
+ dict.fromkeys(["foo", "bar", "quux"]),
+ ["'bar'", "'foo'", "'quux'", "were unexpected)"],
+ ),
+ )
+ def additionalProperties_errorMessage(self, instance, errs):
+ schema = {"additionalProperties" : False}
+
+ with self.assertRaises(ValidationError) as error:
+ validate(instance, schema)
+
+ self.assertTrue(all(err in unicode(error.exception) for err in errs))
+
+ items = parametrized(
+ ("", "valid", [1, 2, 3]),
+ ("wrong_type", "invalid", [1, "x"]),
+ )(validation_test(items={"type" : "integer"}))
+
+ items_tuple_typing = parametrized(
+ ("", "valid", [1, "foo"]),
+ ("wrong_type", "invalid", ["foo", 1])
+ )(validation_test(items=[{"type" : "integer"}, {"type" : "string"}]))
+
+ def test_additionalItems_allowed_by_default(self):
+ validate(
+ [1, "foo", False],
+ {"items" : [{"type" : "integer"}, {"type" : "string"}]}
+ )
+
+ additionalItems = parametrized(
+ ("no_additional", "valid", [1, "foo"]),
+ ("additional", "invalid", [1, "foo", False]),
+ )(validation_test({
+ "items" : [{"type" : "integer"}, {"type" : "string"}],
+ "additionalItems" : False,
+ }))
+
+ additionalItems_schema = parametrized(
+ ("match", "valid", [1, "foo", 3]),
+ ("mismatch", "invalid", [1, "foo", "bar"]),
+ )(validation_test({
+ "items" : [{"type" : "integer"}, {"type" : "string"}],
+ "additionalItems" : {"type" : "integer"},
+ }))
+
+ def test_additionalItems_ignores_nonarrays(self):
+ validate(None, {"additionalItems" : False})
+
+ @parametrized(
+ ("single_extra", [2], "(2 was unexpected)"),
+ ("multiple_extras", [1, 2, 3], "(1, 2, 3 were unexpected)"),
+ )
+ def additionalItems_errorMessage(self, instance, err):
+ schema = {"additionalItems" : False}
+ self.assertRaisesRegexp(
+ ValidationError, err, validate, instance, schema
+ )
+
+ @parametrized(
+ ("false_by_default", "valid", {}, {}),
+ ("false_explicit", "valid", {"required" : False}, {}),
+ ("one", "valid", {"required" : True}, {}),
+ ("other", "invalid", {}, {"required" : True}),
+ ("both", "invalid", {"required" : True}, {"required" : True}),
+ )
+ def required(self, expect, foo, bar):
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "number"},
+ "bar" : {"type" : "string"},
+ }
+ }
+
+ schema["properties"]["foo"].update(foo)
+ schema["properties"]["bar"].update(bar)
+
+ test = validation_test(schema)
+ test(self, expect, {"foo" : 1})
+
+ dependencies = parametrized(
+ ("neither", "valid", {}),
+ ("nondependant", "valid", {"foo" : 1}),
+ ("with_dependency", "valid", {"foo" : 1, "bar" : 2}),
+ ("missing_dependency", "invalid", {"bar" : 2}),
+ )(validation_test(dependencies={"bar": "foo"}))
+
+ multiple_dependencies = parametrized(
+ ("neither", "valid", {}),
+ ("nondependants", "valid", {"foo" : 1, "bar" : 2}),
+ ("with_dependencies", "valid", {"foo" : 1, "bar" : 2, "quux" : 3}),
+ ("missing_dependency", "invalid", {"foo" : 1, "quux" : 2}),
+ ("missing_other_dependency", "invalid", {"bar" : 1, "quux" : 2}),
+ ("missing_both_dependencies", "invalid", {"quux" : 1}),
+ )(validation_test(
+ dependencies={"quux" : ["foo", "bar"]}
+ ))
+
+ multiple_dependencies_subschema = parametrized(
+ ("", "valid", {"foo" : 1, "bar" : 2}),
+ ("wrong_type", "invalid", {"foo" : "quux", "bar" : 2}),
+ ("wrong_type_other", "invalid", {"foo" : 2, "bar" : "quux"}),
+ ("wrong_type_both", "invalid", {"foo" : "quux", "bar" : "quux"}),
+ )(validation_test(dependencies={
+ "bar" : {
+ "properties" : {
+ "foo" : {"type" : "integer"},
+ "bar" : {"type" : "integer"},
+ }}}))
+
+ def test_dependencies_error_message_has_single_element_not_list(self):
+ with self.assertRaises(ValidationError) as e:
+ validate({"bar" : 2}, {"dependencies" : {"bar" : "foo"}})
+ self.assertNotIn("'foo']", e.exception.message)
+ self.assertIn("'foo'", e.exception.message)
+
+ @parametrized(
+ ("", "valid", {}, 2.6),
+ ("fail", "invalid", {}, .6),
+ ("exclusiveMinimum", "valid", {"exclusiveMinimum" : True}, 1.2),
+ ("exclusiveMinimum_fail", "invalid",
+ {"exclusiveMinimum" : True}, 1.1),
+ )
+ def minimum(self, expect, eM, instance):
+ eM["minimum"] = 1.1
+ test = validation_test(eM)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("", "valid", {}, 2.6),
+ ("fail", "invalid", {}, 3.5),
+ ("exclusiveMaximum", "valid", {"exclusiveMaximum" : True}, 2.2),
+ ("exclusiveMaximum_fail", "invalid",
+ {"exclusiveMaximum" : True}, 3.0),
+ )
+ def maximum(self, expect, eM, instance):
+ eM["maximum"] = 3.0
+ test = validation_test(eM)
+ test(self, expect, instance)
+
+ minItems = parametrized(
+ ("exact", "valid", [1]),
+ ("longer", "valid", [1, 2]),
+ ("too_short", "invalid", []),
+ ("ignores_strings", "valid", "a"),
+ )(validation_test(minItems=1))
+
+ maxItems = parametrized(
+ ("exact", "valid", [1, 2]),
+ ("shorter", "valid", [1]),
+ ("empty", "valid", []),
+ ("too_long", "invalid", [1, 2, 3]),
+ ("ignores_strings", "valid", "aaaa"),
+ )(validation_test(maxItems=2))
+
+ uniqueItems = parametrized(
+ ("unique", "valid", [1, 2]),
+ ("not_unique", "invalid", [1, 1]),
+ ("object_unique", "valid", [{"foo" : "bar"}, {"foo" : "baz"}]),
+ ("object_not_unique", "invalid", [{"foo" : "bar"}, {"foo" : "bar"}]),
+ ("array_unique", "valid", [["foo"], ["bar"]]),
+ ("array_not_unique", "invalid", [["foo"], ["foo"]]),
+ ("nested", "valid", [
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ {"foo" : {"bar" : {"baz" : "spam"}}},
+ ]),
+ ("nested_not_unique", "invalid", [
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ {"foo" : {"bar" : {"baz" : "quux"}}},
+ ])
+ )(validation_test(uniqueItems=True))
+
+ pattern = parametrized(
+ ("match", "valid", "aaa"),
+ ("mismatch", "invalid", "ab"),
+ ("ignores_other_stuff", "valid", True),
+ )(validation_test(pattern="^a*$"))
+
+ minLength = parametrized(
+ ("", "valid", "foo"),
+ ("too_short", "invalid", "f"),
+ ("ignores_arrays", "valid", [1]),
+ )(validation_test(minLength=2))
+
+ maxLength = parametrized(
+ ("", "valid", "f"),
+ ("too_long", "invalid", "foo"),
+ ("ignores_arrays", "valid", [1, 2, 3]),
+ )(validation_test(maxLength=2))
+
+ @parametrized(
+ ("integer", "valid", 1, [1, 2, 3]),
+ ("integer_fail", "invalid", 6, [1, 2, 3]),
+ ("string", "valid", "foo", ["foo", "bar"]),
+ ("string_fail", "invalid", "quux", ["foo", "bar"]),
+ ("bool", "valid", True, [True]),
+ ("bool_fail", "invalid", False, [True]),
+ ("object", "valid", {"foo" : "bar"}, [{"foo" : "bar"}]),
+ ("object_fail", "invalid", {"foo" : "bar"}, [{"foo" : "quux"}]),
+ )
+ def enum(self, expect, instance, enum):
+ test = validation_test(enum=enum)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("int_by_int", "valid", 10, 2),
+ ("int_by_int_fail", "invalid", 7, 2),
+ ("number_by_number", "valid", 3.3, 1.1),
+ ("number_by_number_fail", "invalid", 3.5, 1.1),
+ ("number_by_number_small", "valid", .0075, .0001),
+ ("number_by_number_small_fail", "invalid", .00751, .0001),
+ ("number_by_number_again", "valid", 1.09, .01),
+ ("number_by_number_again_2", "valid", 1.89, .01),
+ )
+ def divisibleBy(self, expect, instance, dB):
+ test = validation_test(divisibleBy=dB)
+ test(self, expect, instance)
+
+ disallow = parametrized(
+ ("", "valid", "foo"),
+ ("disallowed", "invalid", 1),
+ )(validation_test(disallow="integer"))
+
+ multiple_disallow = parametrized(
+ ("", "valid", "foo"),
+ ("mismatch", "invalid", 1),
+ ("other_mismatch", "invalid", True),
+ )(validation_test(disallow=["integer", "boolean"]))
+
+ multiple_disallow_subschema = parametrized(
+ ("match", "valid", 1),
+ ("other_match", "valid", {"foo" : 1}),
+ ("mismatch", "invalid", "foo"),
+ ("other_mismatch", "invalid", {"foo" : "bar"}),
+ )(validation_test(
+ disallow=[
+ "string",
+ {"type" : "object", "properties" : {"foo" : {"type" : "string"}}},
+ ]
+ ))
+
+ @parametrized(
+ ("", "valid", {"foo" : "baz", "bar" : 2}),
+ ("mismatch_extends", "invalid", {"foo" : "baz"}),
+ ("mismatch_extended", "invalid", {"bar" : 2}),
+ ("wrong_type", "invalid", {"foo" : "baz", "bar" : "quux"}),
+ )
+ def extends(self, expect, instance):
+ schema = {
+ "properties" : {"bar" : {"type" : "integer", "required" : True}},
+ "extends" : {
+ "properties" : {
+ "foo" : {"type" : "string", "required" : True},
+ }
+ },
+ }
+
+ test = validation_test(**schema)
+ test(self, expect, instance)
+
+ @parametrized(
+ ("", "valid", {"foo" : "quux", "bar" : 2, "baz" : None}),
+ ("mismatch_first_extends", "invalid", {"bar" : 2, "baz" : None}),
+ ("mismatch_second_extends", "invalid", {"foo" : "quux", "bar" : 2}),
+ ("mismatch_both", "invalid", {"bar" : 2}),
+ )
+ def multiple_extends(self, expect, instance):
+ schema = {
+ "properties" : {"bar" : {"type" : "integer", "required" : True}},
+ "extends" : [
+ {
+ "properties" : {
+ "foo" : {"type" : "string", "required" : True},
+ }
+ },
+ {
+ "properties" : {
+ "baz" : {"type" : "null", "required" : True},
+ }
+ },
+ ],
+ }
+
+ test = validation_test(**schema)
+ test(self, expect, instance)
+
+ extends_simple_types = parametrized(
+ ("", "valid", 25),
+ ("mismatch_extends", "invalid", 35)
+ )(validation_test(minimum=20, extends={"maximum" : 30}))
+
+ def test_iter_errors(self):
+ instance = [1, 2]
+ schema = {
+ "disallow" : "array",
+ "enum" : [["a", "b", "c"], ["d", "e", "f"]],
+ "minItems" : 3
+ }
+
+ if PY3:
+ errors = sorted([
+ "'array' is disallowed for [1, 2]",
+ "[1, 2] is too short",
+ "[1, 2] is not one of [['a', 'b', 'c'], ['d', 'e', 'f']]",
+ ])
+ else:
+ errors = sorted([
+ "u'array' is disallowed for [1, 2]",
+ "[1, 2] is too short",
+ "[1, 2] is not one of [[u'a', u'b', u'c'], [u'd', u'e', u'f']]",
+ ])
+
+ self.assertEqual(
+ sorted(str(e) for e in Validator().iter_errors(instance, schema)),
+ errors,
+ )
+
+ def test_unknown_type_error(self):
+ with self.assertRaises(SchemaError):
+ validate(1, {"type" : "foo"}, unknown_type="error")
+
+ def test_unknown_type_warn(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ validate(1, {"type" : "foo"}, unknown_type="warn")
+ self.assertEqual(len(w), 1)
+
+ def test_unknown_type_skip(self):
+ validate(1, {"type" : "foo"}, unknown_type="skip")
+
+ def test_unknown_property_error(self):
+ with self.assertRaises(SchemaError):
+ validate(1, {"foo" : "bar"}, unknown_property="error")
+
+ def test_unknown_property_warn(self):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ validate(1, {"foo" : "bar"}, unknown_property="warn")
+ self.assertEqual(len(w), 1)
+
+ def test_unknown_property_skip(self):
+ validate(
+ 1,
+ {"foo" : "foo", "type" : "integer"},
+ unknown_property="skip"
+ )
+
+ decimal = parametrized(
+ ("integer", "valid", 1),
+ ("number", "valid", 1.1),
+ ("decimal", "valid", Decimal(1) / Decimal(8)),
+ ("string", "invalid", "foo"),
+ ("object", "invalid", {}),
+ ("array", "invalid", []),
+ ("boolean", "invalid", True),
+ ("null", "invalid", None),
+ )(validation_test(
+ initkwargs={"types" : {"number" : (int, float, Decimal)}},
+ type="number")
+ )
+
+ # TODO: we're in need of more meta schema tests
+ def test_invalid_properties(self):
+ with self.assertRaises(SchemaError):
+ validate({}, {"properties": {"test": True}})
+
+ def test_minItems_invalid_string(self):
+ with self.assertRaises(SchemaError):
+ validate([1], {"minItems" : "1"}) # needs to be an integer
+
+ def test_iter_errors_multiple_failures_one_validator(self):
+ instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"}
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "string"},
+ "bar" : {"minItems" : 2},
+ "baz" : {"maximum" : 10, "enum" : [2, 4, 6, 8]},
+ }
+ }
+
+ errors = list(Validator().iter_errors(instance, schema))
+ self.assertEqual(len(errors), 4)
+
+
+class TestValidationErrorDetails(unittest.TestCase):
+ # TODO: These really need unit tests for each individual validator, rather
+ # than just these higher level tests.
+ def test_single_nesting(self):
+ instance = {"foo" : 2, "bar" : [1], "baz" : 15, "quux" : "spam"}
+ schema = {
+ "properties" : {
+ "foo" : {"type" : "string"},
+ "bar" : {"minItems" : 2},
+ "baz" : {"maximum" : 10, "enum" : [2, 4, 6, 8]},
+ }
+ }
+
+ errors = Validator().iter_errors(instance, schema)
+ e1, e2, e3, e4 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, ["bar"])
+ self.assertEqual(e2.path, ["baz"])
+ self.assertEqual(e3.path, ["baz"])
+ self.assertEqual(e4.path, ["foo"])
+
+ self.assertEqual(e1.validator, "minItems")
+ self.assertEqual(e2.validator, "enum")
+ self.assertEqual(e3.validator, "maximum")
+ self.assertEqual(e4.validator, "type")
+
+ def test_multiple_nesting(self):
+ instance = [1, {"foo" : 2, "bar" : {"baz" : [1]}}, "quux"]
+ schema = {
+ "type" : "string",
+ "items" : {
+ "type" : ["string", "object"],
+ "properties" : {
+ "foo" : {"enum" : [1, 3]},
+ "bar" : {
+ "type" : "array",
+ "properties" : {
+ "bar" : {"required" : True},
+ "baz" : {"minItems" : 2},
+ }
+ }
+ }
+ }
+ }
+
+ errors = Validator().iter_errors(instance, schema)
+ e1, e2, e3, e4, e5, e6 = sorted_errors(errors)
+
+ self.assertEqual(e1.path, [])
+ self.assertEqual(e2.path, [0])
+ self.assertEqual(e3.path, ["bar", 1])
+ self.assertEqual(e4.path, ["bar", "bar", 1])
+ self.assertEqual(e5.path, ["baz", "bar", 1])
+ self.assertEqual(e6.path, ["foo", 1])
+
+ self.assertEqual(e1.validator, "type")
+ self.assertEqual(e2.validator, "type")
+ self.assertEqual(e3.validator, "type")
+ self.assertEqual(e4.validator, "required")
+ self.assertEqual(e5.validator, "minItems")
+ self.assertEqual(e6.validator, "enum")
+
+
+class TestErrorTree(unittest.TestCase):
+ def test_tree(self):
+ instance = [1, {"foo" : 2, "bar" : {"baz" : [1]}}, "quux"]
+ schema = {
+ "type" : "string",
+ "items" : {
+ "type" : ["string", "object"],
+ "properties" : {
+ "foo" : {"enum" : [1, 3]},
+ "bar" : {
+ "type" : "array",
+ "properties" : {
+ "bar" : {"required" : True},
+ "baz" : {"minItems" : 2},
+ }
+ }
+ }
+ }
+ }
+
+ errors = sorted_errors(Validator().iter_errors(instance, schema))
+ e1, e2, e3, e4, e5, e6 = errors
+ tree = ErrorTree(errors)
+
+ self.assertEqual(len(tree), 6)
+
+ self.assertIn(0, tree)
+ self.assertIn(1, tree)
+ self.assertIn("bar", tree[1])
+ self.assertIn("foo", tree[1])
+ self.assertIn("baz", tree[1]["bar"])
+
+ self.assertEqual(tree.errors["type"], e1)
+ self.assertEqual(tree[0].errors["type"], e2)
+ self.assertEqual(tree[1]["bar"].errors["type"], e3)
+ self.assertEqual(tree[1]["bar"]["bar"].errors["required"], e4)
+ self.assertEqual(tree[1]["bar"]["baz"].errors["minItems"], e5)
+ self.assertEqual(tree[1]["foo"].errors["enum"], e6)
+
+
+class TestIgnorePropertiesForIrrelevantTypes(unittest.TestCase):
+ def test_minimum(self):
+ validate("x", {"type": ["string", "number"], "minimum": 10})
+
+ def test_maximum(self):
+ validate("x", {"type": ["string", "number"], "maximum": 10})
+
+ def test_properties(self):
+ validate(1, {"type": ["integer", "object"], "properties": {"x": {}}})
+
+ def test_items(self):
+ validate(
+ 1, {"type": ["integer", "array"], "items": {"type": "string"}}
+ )
+
+ def test_divisibleBy(self):
+ validate("x", {"type": ["integer", "string"], "divisibleBy": 10})
+
+
+def sorted_errors(errors):
+ return sorted(errors, key=lambda e : [str(err) for err in e.path])