|
Packit |
534379 |
from jsonschema import Draft4Validator, exceptions
|
|
Packit |
534379 |
from jsonschema.tests.compat import mock, unittest
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
class TestBestMatch(unittest.TestCase):
|
|
Packit |
534379 |
def best_match(self, errors):
|
|
Packit |
534379 |
errors = list(errors)
|
|
Packit |
534379 |
best = exceptions.best_match(errors)
|
|
Packit |
534379 |
reversed_best = exceptions.best_match(reversed(errors))
|
|
Packit |
534379 |
self.assertEqual(
|
|
Packit |
534379 |
best,
|
|
Packit |
534379 |
reversed_best,
|
|
Packit |
534379 |
msg="Didn't return a consistent best match!\n"
|
|
Packit |
534379 |
"Got: {0}\n\nThen: {1}".format(best, reversed_best),
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
return best
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_shallower_errors_are_better_matches(self):
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"foo": {
|
|
Packit |
534379 |
"minProperties": 2,
|
|
Packit |
534379 |
"properties": {"bar": {"type": "object"}},
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({"foo": {"bar": []}}))
|
|
Packit |
534379 |
self.assertEqual(best.validator, "minProperties")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_oneOf_and_anyOf_are_weak_matches(self):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
A property you *must* match is probably better than one you have to
|
|
Packit |
534379 |
match a part of.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"minProperties": 2,
|
|
Packit |
534379 |
"anyOf": [{"type": "string"}, {"type": "number"}],
|
|
Packit |
534379 |
"oneOf": [{"type": "string"}, {"type": "number"}],
|
|
Packit |
534379 |
}
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({}))
|
|
Packit |
534379 |
self.assertEqual(best.validator, "minProperties")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_if_the_most_relevant_error_is_anyOf_it_is_traversed(self):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
If the most relevant error is an anyOf, then we traverse its context
|
|
Packit |
534379 |
and select the otherwise *least* relevant error, since in this case
|
|
Packit |
534379 |
that means the most specific, deep, error inside the instance.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
I.e. since only one of the schemas must match, we look for the most
|
|
Packit |
534379 |
relevant one.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"foo": {
|
|
Packit |
534379 |
"anyOf": [
|
|
Packit |
534379 |
{"type": "string"},
|
|
Packit |
534379 |
{"properties": {"bar": {"type": "array"}}},
|
|
Packit |
534379 |
],
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
|
|
Packit |
534379 |
self.assertEqual(best.validator_value, "array")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_if_the_most_relevant_error_is_oneOf_it_is_traversed(self):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
If the most relevant error is an oneOf, then we traverse its context
|
|
Packit |
534379 |
and select the otherwise *least* relevant error, since in this case
|
|
Packit |
534379 |
that means the most specific, deep, error inside the instance.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
I.e. since only one of the schemas must match, we look for the most
|
|
Packit |
534379 |
relevant one.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"foo": {
|
|
Packit |
534379 |
"oneOf": [
|
|
Packit |
534379 |
{"type": "string"},
|
|
Packit |
534379 |
{"properties": {"bar": {"type": "array"}}},
|
|
Packit |
534379 |
],
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
|
|
Packit |
534379 |
self.assertEqual(best.validator_value, "array")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_if_the_most_relevant_error_is_allOf_it_is_traversed(self):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
Now, if the error is allOf, we traverse but select the *most* relevant
|
|
Packit |
534379 |
error from the context, because all schemas here must match anyways.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"foo": {
|
|
Packit |
534379 |
"allOf": [
|
|
Packit |
534379 |
{"type": "string"},
|
|
Packit |
534379 |
{"properties": {"bar": {"type": "array"}}},
|
|
Packit |
534379 |
],
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
|
|
Packit |
534379 |
self.assertEqual(best.validator_value, "string")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_nested_context_for_oneOf(self):
|
|
Packit |
534379 |
validator = Draft4Validator(
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"foo": {
|
|
Packit |
534379 |
"oneOf": [
|
|
Packit |
534379 |
{"type": "string"},
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"oneOf": [
|
|
Packit |
534379 |
{"type": "string"},
|
|
Packit |
534379 |
{
|
|
Packit |
534379 |
"properties": {
|
|
Packit |
534379 |
"bar": {"type": "array"}
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
],
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
],
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
},
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
best = self.best_match(validator.iter_errors({"foo": {"bar": 12}}))
|
|
Packit |
534379 |
self.assertEqual(best.validator_value, "array")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_one_error(self):
|
|
Packit |
534379 |
validator = Draft4Validator({"minProperties": 2})
|
|
Packit |
534379 |
error, = validator.iter_errors({})
|
|
Packit |
534379 |
self.assertEqual(
|
|
Packit |
534379 |
exceptions.best_match(validator.iter_errors({})).validator,
|
|
Packit |
534379 |
"minProperties",
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_no_errors(self):
|
|
Packit |
534379 |
validator = Draft4Validator({})
|
|
Packit |
534379 |
self.assertIsNone(exceptions.best_match(validator.iter_errors({})))
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
class TestByRelevance(unittest.TestCase):
|
|
Packit |
534379 |
def test_short_paths_are_better_matches(self):
|
|
Packit |
534379 |
shallow = exceptions.ValidationError("Oh no!", path=["baz"])
|
|
Packit |
534379 |
deep = exceptions.ValidationError("Oh yes!", path=["foo", "bar"])
|
|
Packit |
534379 |
match = max([shallow, deep], key=exceptions.by_relevance())
|
|
Packit |
534379 |
self.assertIs(match, shallow)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
match = max([deep, shallow], key=exceptions.by_relevance())
|
|
Packit |
534379 |
self.assertIs(match, shallow)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_global_errors_are_even_better_matches(self):
|
|
Packit |
534379 |
shallow = exceptions.ValidationError("Oh no!", path=[])
|
|
Packit |
534379 |
deep = exceptions.ValidationError("Oh yes!", path=["foo"])
|
|
Packit |
534379 |
|
|
Packit |
534379 |
errors = sorted([shallow, deep], key=exceptions.by_relevance())
|
|
Packit |
534379 |
self.assertEqual(
|
|
Packit |
534379 |
[list(error.path) for error in errors],
|
|
Packit |
534379 |
[["foo"], []],
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
errors = sorted([deep, shallow], key=exceptions.by_relevance())
|
|
Packit |
534379 |
self.assertEqual(
|
|
Packit |
534379 |
[list(error.path) for error in errors],
|
|
Packit |
534379 |
[["foo"], []],
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_weak_validators_are_lower_priority(self):
|
|
Packit |
534379 |
weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
|
|
Packit |
534379 |
normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
best_match = exceptions.by_relevance(weak="a")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
match = max([weak, normal], key=best_match)
|
|
Packit |
534379 |
self.assertIs(match, normal)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
match = max([normal, weak], key=best_match)
|
|
Packit |
534379 |
self.assertIs(match, normal)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_strong_validators_are_higher_priority(self):
|
|
Packit |
534379 |
weak = exceptions.ValidationError("Oh no!", path=[], validator="a")
|
|
Packit |
534379 |
normal = exceptions.ValidationError("Oh yes!", path=[], validator="b")
|
|
Packit |
534379 |
strong = exceptions.ValidationError("Oh fine!", path=[], validator="c")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
best_match = exceptions.by_relevance(weak="a", strong="c")
|
|
Packit |
534379 |
|
|
Packit |
534379 |
match = max([weak, normal, strong], key=best_match)
|
|
Packit |
534379 |
self.assertIs(match, strong)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
match = max([strong, normal, weak], key=best_match)
|
|
Packit |
534379 |
self.assertIs(match, strong)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
|
|
Packit |
534379 |
class TestErrorTree(unittest.TestCase):
|
|
Packit |
534379 |
def test_it_knows_how_many_total_errors_it_contains(self):
|
|
Packit |
534379 |
errors = [mock.MagicMock() for _ in range(8)]
|
|
Packit |
534379 |
tree = exceptions.ErrorTree(errors)
|
|
Packit |
534379 |
self.assertEqual(tree.total_errors, 8)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_it_contains_an_item_if_the_item_had_an_error(self):
|
|
Packit |
534379 |
errors = [exceptions.ValidationError("a message", path=["bar"])]
|
|
Packit |
534379 |
tree = exceptions.ErrorTree(errors)
|
|
Packit |
534379 |
self.assertIn("bar", tree)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_it_does_not_contain_an_item_if_the_item_had_no_error(self):
|
|
Packit |
534379 |
errors = [exceptions.ValidationError("a message", path=["bar"])]
|
|
Packit |
534379 |
tree = exceptions.ErrorTree(errors)
|
|
Packit |
534379 |
self.assertNotIn("foo", tree)
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_validators_that_failed_appear_in_errors_dict(self):
|
|
Packit |
534379 |
error = exceptions.ValidationError("a message", validator="foo")
|
|
Packit |
534379 |
tree = exceptions.ErrorTree([error])
|
|
Packit |
534379 |
self.assertEqual(tree.errors, {"foo": error})
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_it_creates_a_child_tree_for_each_nested_path(self):
|
|
Packit |
534379 |
errors = [
|
|
Packit |
534379 |
exceptions.ValidationError("a bar message", path=["bar"]),
|
|
Packit |
534379 |
exceptions.ValidationError("a bar -> 0 message", path=["bar", 0]),
|
|
Packit |
534379 |
]
|
|
Packit |
534379 |
tree = exceptions.ErrorTree(errors)
|
|
Packit |
534379 |
self.assertIn(0, tree["bar"])
|
|
Packit |
534379 |
self.assertNotIn(1, tree["bar"])
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_children_have_their_errors_dicts_built(self):
|
|
Packit |
534379 |
e1, e2 = (
|
|
Packit |
534379 |
exceptions.ValidationError("1", validator="foo", path=["bar", 0]),
|
|
Packit |
534379 |
exceptions.ValidationError("2", validator="quux", path=["bar", 0]),
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
tree = exceptions.ErrorTree([e1, e2])
|
|
Packit |
534379 |
self.assertEqual(tree["bar"][0].errors, {"foo": e1, "quux": e2})
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_it_does_not_contain_subtrees_that_are_not_in_the_instance(self):
|
|
Packit |
534379 |
error = exceptions.ValidationError("123", validator="foo", instance=[])
|
|
Packit |
534379 |
tree = exceptions.ErrorTree([error])
|
|
Packit |
534379 |
|
|
Packit |
534379 |
with self.assertRaises(IndexError):
|
|
Packit |
534379 |
tree[0]
|
|
Packit |
534379 |
|
|
Packit |
534379 |
def test_if_its_in_the_tree_anyhow_it_does_not_raise_an_error(self):
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
If a validator is dumb (like:validator:`required` in draft 3) and
|
|
Packit |
534379 |
refers to a path that isn't in the instance, the tree still properly
|
|
Packit |
534379 |
returns a subtree for that path.
|
|
Packit |
534379 |
|
|
Packit |
534379 |
"""
|
|
Packit |
534379 |
|
|
Packit |
534379 |
error = exceptions.ValidationError(
|
|
Packit |
534379 |
"a message", validator="foo", instance={}, path=["foo"],
|
|
Packit |
534379 |
)
|
|
Packit |
534379 |
tree = exceptions.ErrorTree([error])
|
|
Packit |
534379 |
self.assertIsInstance(tree["foo"], exceptions.ErrorTree)
|