def test_mapping_vs_mapping_failing(self): with self.assertRaises(ValidationError) as cm: data = { 'a': ('abc', 1.0), 'b': ('xyz', 2.0) } # Mapping of base-elements. requirement = { 'a': ('abc', int), 'b': ('abc', float) } # Mapping of requirements. validate(data, requirement) actual = cm.exception.differences expected = { 'a': Invalid(('abc', 1.0), ('abc', int)), 'b': Invalid(('xyz', 2.0), ('abc', float)), } self.assertEqual(actual, expected) with self.assertRaises(ValidationError) as cm: data = { 'a': [('abc', 1.0), ('abc', 2)], 'b': [('abc', 1.0), ('xyz', 2.0)] } # Mapping of containers. requirement = { 'a': ('abc', int), 'b': ('abc', float) } # Mapping of requirements. validate(data, requirement) actual = cm.exception.differences expected = { 'a': [Invalid(('abc', 1.0))], 'b': [Invalid(('xyz', 2.0))], } self.assertEqual(actual, expected)
def test_get_deviation_expected_extra_or_single_arg_invalid(self): """Extra and single-argument Invalid differences should be treated the same. """ func = AcceptedTolerance._get_deviation_expected self.assertEqual(func(Extra(2)), (2, 0)) self.assertEqual(func(Invalid(2)), (2, 0)) self.assertEqual(func(Extra(-2)), (-2, 0)) self.assertEqual(func(Invalid(-2)), (-2, 0)) self.assertEqual(func(Extra(0)), (0, 0)) self.assertEqual(func(Invalid(0)), (0, 0)) with self.assertRaises(TypeError): func(Extra((1, 2))) with self.assertRaises(TypeError): func(Invalid((1, 2))) with self.assertRaises(TypeError): func(Extra('abc')) with self.assertRaises(TypeError): func(Invalid('abc'))
def test_custom_difference(self): """When a predicate function returns a difference object, it should be used in place of an auto-generated one. """ pred = lambda x: Invalid('custom') result = _require_predicate('A', pred, False) self.assertEqual(result, Invalid('custom'))
def test_allowed_invalid(self): differences = [Invalid('X'), Invalid('Y'), Extra('Z')] with self.assertRaises(ValidationError) as cm: with allowed_invalid(): # <- Apply allowance! raise ValidationError(differences) remaining_diffs = cm.exception.differences self.assertEqual(list(remaining_diffs), [Extra('Z')])
def test_invalid(self): diff = Invalid('foo') with self.assertRaises(AttributeError): diff.expected = 'bar' with self.assertRaises(AttributeError): diff.new_attribute = 'baz'
def test_duplicate_false(self): """Should return an error for every non-match (incl. duplicates).""" data = iter(['a1', 'b2', 'XX', 'XX', 'XX']) # <- Multiple XX's. result = _require_predicate_from_iterable(data, self.regex) self.assertEqual( list(result), [Invalid('XX'), Invalid('XX'), Invalid('XX')])
def test_hashable(self): """Differences with hashable *args should be hashable.""" # Following should all pass without error. hash(Missing('foo')) hash(Extra('bar')) hash(Invalid('baz')) hash(Invalid('baz', 'qux')) hash(Deviation(-1, 10))
def test_duplicate_false(self): """Should return an error for every false result (incl. duplicates).""" data = ['10', '20', 'XX', 'XX', 'XX'] # <- Multiple XX's. result = _require_predicate_from_iterable(data, self.isdigit) self.assertEqual( list(result), [Invalid('XX'), Invalid('XX'), Invalid('XX')])
def test_repr(self): diff = Invalid('foo') self.assertEqual(repr(diff), "Invalid('foo')") diff = Invalid('foo', 'bar') self.assertEqual(repr(diff), "Invalid('foo', expected='bar')") diff = Invalid('foo', None) self.assertEqual(repr(diff), "Invalid('foo', expected=None)")
def test_same(self): """The _make_difference() function returns differences for objects that are KNOWN TO BE DIFFERENT--it does not test for differences itself. """ diff = _make_difference('a', 'a') self.assertEqual(diff, Invalid('a', 'a')) diff = _make_difference(None, None) self.assertEqual(diff, Invalid(None, None))
def test_mixed_differences(self): data = ['aaa', 'xxx', 'ddd', 'eee', 'ggg'] requirement = ['aaa', 'bbb', 'ccc', 'ddd', 'fff'] actual = _require_sequence(data, requirement) expected = { (1, 2): [Invalid('xxx', expected='bbb'), Missing('ccc')], (3, 5): [Invalid('eee', expected='fff'), Extra('ggg')], } self.assertEqual(actual, expected)
def test_show_expected(self): """If requirement is common it should be omitted from Invalid difference (but not from Deviation differences). """ diff = _make_difference('a', 6, show_expected=True) self.assertEqual(diff, Invalid('a', expected=6)) diff = _make_difference('a', 6, show_expected=False) self.assertEqual(diff, Invalid('a')) diff = _make_difference(NOVALUE, 6, show_expected=False) self.assertEqual(diff, Deviation(-6, 6))
def test_invalid_deviation_single_arg_percent(self): # Check error percent. with self.assertRaises(ValidationError) as cm: with AcceptedPercent(2.0): # <- Accepts +/- 200%. raise ValidationError([ Invalid( -1), # <- Rejected: Can not be accepted by percent. Invalid(0), # <- ACCEPTED! Invalid(2), # <- Rejected: Can not be accepted by percent. ]) remaining = cm.exception.differences self.assertEqual(remaining, [Invalid(-1), Invalid(2)])
def test_scope(self): with self.assertRaises(ValidationError) as cm: with AcceptedCount(2, scope='group'): # <- Accepts 2 per group. raise ValidationError({ 'foo': [Extra('xxx'), Extra('yyy')], 'bar': [Missing('xxx'), Missing('yyy')], 'baz': [Invalid('xxx'), Invalid('yyy'), Invalid('zzz')], }) remaining = cm.exception.differences self.assertEqual(remaining, {'baz': Invalid('zzz')})
def test_numeric_matching(self): """When checking sequence order, numeric differences should not be converted into Deviation objects. """ data = [1, 100, 4, 200, 300] requirement = [1, 2, 3, 4, 5] actual = _require_sequence(data, requirement) expected = { (1, 2): [Invalid(100, expected=2), Missing(3)], (3, 5): [Invalid(200, expected=5), Extra(300)], } self.assertEqual(actual, expected)
def test_some_differences(self): # Sequence order. data = {'a': ['x', 'y']} result = _apply_mapping_requirement(data, {'a': ['x', 'z']}) result = dict(result) self.assertTrue(len(result) == 1) self.assertEqual(result, {'a': {(1, 2): [Invalid('y', 'z')]}}) # Set membership. data = {'a': ['x', 'x'], 'b': ['x', 'y', 'z']} result = _apply_mapping_requirement(data, { 'a': set(['x', 'y']), 'b': set(['x', 'y']) }) expected = {'a': [Missing('y')], 'b': [Extra('z')]} self.assertEqual(dict(result), expected) # Equality of single values. data = {'a': 'x', 'b': 10} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9}) expected = {'a': Invalid('x', expected='j'), 'b': Deviation(+1, 9)} self.assertEqual(dict(result), expected) # Equality of multiple values. data = {'a': ['x', 'j'], 'b': [10, 9]} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9}) expected = {'a': [Invalid('x')], 'b': [Deviation(+1, 9)]} self.assertEqual(dict(result), expected) # Equality of single tuples. data = {'a': (1, 'x'), 'b': (9, 10)} result = _apply_mapping_requirement(data, {'a': (1, 'j'), 'b': (9, 9)}) expected = { 'a': Invalid((1, 'x'), expected=(1, 'j')), 'b': Invalid((9, 10), expected=(9, 9)) } self.assertEqual(dict(result), expected) # Equality of multiple tuples. data = {'a': [(1, 'j'), (1, 'x')], 'b': [(9, 9), (9, 10)]} result = _apply_mapping_requirement(data, {'a': (1, 'j'), 'b': (9, 9)}) expected = {'a': [Invalid((1, 'x'))], 'b': [Invalid((9, 10))]} self.assertEqual(dict(result), expected) # Equality of multiple values, missing key with single item. data = {'a': ['x', 'j'], 'b': [10, 9]} result = _apply_mapping_requirement(data, {'a': 'j', 'b': 9, 'c': 'z'}) expected = { 'a': [Invalid('x')], 'b': [Deviation(+1, 9)], 'c': Missing('z') } self.assertEqual(dict(result), expected) # Missing key, set membership. data = {'a': 'x'} result = _apply_mapping_requirement(data, {'a': 'x', 'b': set(['z'])}) expected = {'b': [Missing('z')]} self.assertEqual(dict(result), expected)
def test_unhashable_contents(self): """The hash behavior of differences should act like tuples do. When a difference's contents are unhashable, the difference itself becomes unhashable too. """ with self.assertRaises(TypeError): hash(Missing(['foo'])) with self.assertRaises(TypeError): hash(Extra(['bar'])) with self.assertRaises(TypeError): hash(Invalid(['baz'])) with self.assertRaises(TypeError): hash(Invalid('baz', ['qux']))
def test_nonnumeric_but_compatible(self): with self.assertRaises(ValidationError) as cm: with AcceptedTolerance( datetime.timedelta(hours=2)): # <- Accepts +/- 2 hours. raise ValidationError([ Invalid(datetime.datetime(1989, 2, 24, hour=10, minute=30), datetime.datetime(1989, 2, 24, hour=11, minute=30)), Invalid(datetime.datetime(1989, 2, 24, hour=15, minute=10), datetime.datetime(1989, 2, 24, hour=11, minute=30)) ]) remaining = cm.exception.differences self.assertEqual(remaining, [ Invalid(datetime.datetime(1989, 2, 24, 15, 10), expected=datetime.datetime(1989, 2, 24, 11, 30)) ])
def test_required_sequence(self): """When *required* is a sequence, _compare_sequence() should be called. """ with self.assertRaises(ValidationError) as cm: data = ['a', 2, 'x', 3] required = ['a', 2, 'c', 4] self.assertValid(data, required) error = cm.exception expected = { (2, 4): [Invalid('x', expected='c'), Invalid(3, expected=4)], } self.assertEqual(error.differences, expected) self.assertEqual(error.args[1], 'does not match sequence order')
def test_unhashable(self): """Uses "deep hashing" to attempt to sort unhashable types.""" first = [{'a': 1}, {'b': 2}, {'c': 3}] second = [{'a': 1}, {'b': 2}, {'c': 3}] error = _require_sequence(first, second) self.assertIsNone(error) # No difference, returns None. data = [{'a': 1}, {'x': 0}, {'d': 4}, {'y': 5}, {'g': 7}] requirement = [{'a': 1}, {'b': 2}, {'c': 3}, {'d': 4}, {'f': 6}] actual = _require_sequence(data, requirement) expected = { (1, 2): [Invalid({'x': 0}, expected={'b': 2}), Missing({'c': 3})], (3, 5): [Invalid({'y': 5}, expected={'f': 6}), Extra({'g': 7})], } self.assertEqual(actual, expected)
def test_non_deviation_diffs(self): diffs = [Missing('foo'), Extra('bar'), Invalid('baz')] with self.assertRaises(ValidationError) as cm: with AcceptedPercent(0.05): raise ValidationError(diffs) uncaught_diffs = cm.exception.differences self.assertEqual(diffs, uncaught_diffs)
def test_required_mapping(self): with self.assertRaises(ValidationError) as cm: data = {'AAA': 'a', 'BBB': 'x'} required = {'AAA': 'a', 'BBB': 'b', 'CCC': 'c'} self.assertValid(data, required) differences = cm.exception.differences self.assertEqual(differences, {'BBB': Invalid('x', 'b'), 'CCC': Missing('c')})
def test_invalid(self): data = ['aaa', 'xxx', 'ccc'] requirement = ['aaa', 'bbb', 'ccc'] actual = _require_sequence(data, requirement) expected = { (1, 2): [Invalid('xxx', 'bbb')], } self.assertEqual(actual, expected)
def test_some_false(self): data = ['x', 'x', 'y'] result = _require_predicate_from_iterable(data, 'x') self.assertEqual(list(result), [Invalid('y')]) data = [2, 2, 7] result = _require_predicate_from_iterable(data, 2) self.assertEqual(list(result), [Deviation(+5, 2)])
def test_repr_with_callables(self): def myfunc(x): return True class MyClass(object): pass diff = Invalid('foo', myfunc) self.assertEqual(repr(diff), "Invalid('foo', expected=myfunc)") diff = Invalid('foo', MyClass) self.assertEqual(repr(diff), "Invalid('foo', expected=MyClass)") diff = Invalid(myfunc, 'bar') self.assertEqual(repr(diff), "Invalid(myfunc, expected='bar')") diff = Invalid(MyClass, 'bar') self.assertEqual(repr(diff), "Invalid(MyClass, expected='bar')")
def test_invalid_different_lengths(self): data = ['aaa', 'xxx', 'ddd'] requirement = ['aaa', 'bbb', 'ccc', 'ddd'] actual = _require_sequence(data, requirement) expected = { (1, 2): [Invalid('xxx', 'bbb'), Missing('ccc')], } self.assertEqual(actual, expected) data = ['aaa', 'xxx', 'yyy', 'ccc'] requirement = ['aaa', 'bbb', 'ccc'] actual = _require_sequence(data, requirement) expected = { (1, 3): [Invalid('xxx', 'bbb'), Extra('yyy')], } self.assertEqual(actual, expected)
def test_returned_error(self): """When a difference is returned, it is used in place of Invalid.""" def func(x): if x == 'c': return Invalid("Letter 'c' is no good!") return True data = ['a', 'b', 'c'] result = _require_predicate_from_iterable(data, func) self.assertEqual(list(result), [Invalid("Letter 'c' is no good!")])
def test_required_vs_data_failing(self): """Apply single requirement to BaseElement or non-mapping container of data. """ with self.assertRaises(ValidationError) as cm: data = ('abc', 1.0) # A single base element. requirement = ('abc', int) validate(data, requirement) differences = cm.exception.differences self.assertEqual(differences, [Invalid(('abc', 1.0))]) with self.assertRaises(ValidationError) as cm: data = [('abc', 1.0), ('xyz', 2)] # Non-mapping container of base elements. requirement = ('abc', int) validate(data, requirement) differences = cm.exception.differences self.assertEqual( differences, [Invalid( ('abc', 1.0)), Invalid(('xyz', 2))])
def test_required_other(self): """When *required* is a string or other object, _compare_other() should be called. """ with self.assertRaises(ValidationError) as cm: required = lambda x: x.isupper() data = ['AAA', 'BBB', 'ccc', 'DDD'] self.assertValid(data, required) differences = cm.exception.differences self.assertEqual(differences, [Invalid('ccc')])
def setUp(self): ntup = namedtuple('ntup', ('cls', 'args', 'priority')) self.acceptances = [ ntup(cls=AcceptedFuzzy, args=tuple(), priority=4), ntup(cls=AcceptedPercent, args=(0.05, ), priority=4), ntup(cls=AcceptedKeys, args=(lambda args: True, ), priority=4), ntup(cls=AcceptedArgs, args=(lambda *args: True, ), priority=4), ntup(cls=AcceptedCount, args=(4, ), priority=256), ntup(cls=AcceptedDifferences, args=(Invalid('A'), ), priority=4), ntup(cls=AcceptedDifferences, args=([Invalid('A')], ), priority=32), ntup(cls=AcceptedDifferences, args=({ 'X': [Invalid('A')] }, ), priority=32), ntup(cls=AcceptedDifferences, args=([Invalid('A')], None, 'whole'), priority=256), ]