def test_expected_handling(self): item = Invalid('foo', 'FOO') self.assertEqual("Invalid('foo', 'FOO')", repr(item)) # QUESTION: How should kwds be handled if keys match item or expected? with self.assertRaises(TypeError): item = Invalid('foo', 'FOO', required='bar')
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_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_accepted_invalid(self): differences = [Invalid('X'), Invalid('Y'), Extra('Z')] with self.assertRaises(ValidationError) as cm: with AcceptedDifferences(Invalid): # <- Apply acceptance! 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_repr(self): item = Invalid('foo') self.assertEqual("Invalid('foo')", repr(item)) item = Invalid(None) self.assertEqual("Invalid(None)", repr(item)) item = Invalid(2) self.assertEqual("Invalid(2)", repr(item))
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 _getdiff() function returns differences for objects that are KNOWN TO BE DIFFERENT--it does not test for differences itself. """ diff = _getdiff('a', 'a') self.assertEqual(diff, Invalid('a', 'a')) diff = _getdiff(None, None) self.assertEqual(diff, Invalid(None, None))
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_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 setUp(self): ntup = namedtuple('ntup', ('cls', 'args', 'scope')) self.acceptances = [ ntup(cls=AcceptedDifferences, args=(Invalid('A'),), scope=set(['element'])), ntup(cls=AcceptedDifferences, args=([Invalid('A')],), scope=set(['group'])), ntup(cls=AcceptedDifferences, args=({'X': [Invalid('A')]},), scope=set(['group'])), ntup(cls=AcceptedDifferences, args=([Invalid('A')], None, 'whole'), scope=set(['whole'])), ntup(cls=AcceptedKeys, args=(lambda args: True,), scope=set(['element'])), ntup(cls=AcceptedArgs, args=(lambda *args: True,), scope=set(['element'])), ntup(cls=AcceptedTolerance, args=(10,), scope=set(['element'])), ntup(cls=AcceptedPercent, args=(0.05,), scope=set(['element'])), ntup(cls=AcceptedFuzzy, args=tuple(), scope=set(['element'])), ntup(cls=AcceptedCount, args=(4,), scope=set(['whole'])), ]
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')) # Show expected has no value with Missing or Extra differences. diff = _make_difference(NOVALUE, 6, show_expected=False) self.assertEqual(diff, Missing(6)) diff = _make_difference(6, NOVALUE, show_expected=False) self.assertEqual(diff, Extra(6))
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_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_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_is_common(self): """If requirement is common it should be omitted from Invalid difference (but not from Deviation differences). """ diff = _getdiff('a', 6, is_common=True) self.assertEqual(diff, Invalid('a')) diff = _getdiff(_NOTFOUND, 6, is_common=True) self.assertEqual(diff, Deviation(-6, 6))
def test_predicate_regex(self): data = {'A': 'Alpha', 'B': ['Beta', 'Gamma']} requirement = Query.from_object({'A': r'^[A-Z]', 'B': r'^[A-Z]'}) validate.regex(data, requirement) with self.assertRaises(ValidationError) as cm: data = {'A': 'Alpha', 'B': ['Beta', 'gamma'], 'C': ('b', 2)} requirement = Query.from_object({ 'A': r'^[A-Z]', 'B': r'^[A-Z]', 'C': r'\d' }) validate.regex(data, requirement) actual = cm.exception.differences expected = { 'B': [Invalid('gamma')], 'C': Invalid(('b', 2), expected=r'\d'), } self.assertEqual(actual, expected)
def test_keywords(self): """Keywords should be passed to diff objet.""" diff = _getdiff(5, 6, col1='AAA') self.assertEqual(diff, Deviation(-1, 6, col1='AAA')) diff = _getdiff('a', 6, col1='AAA') self.assertEqual(diff, Invalid('a', 6, col1='AAA')) diff = _getdiff(_NOTFOUND, 6, col1='AAA') self.assertEqual(diff, Deviation(-6, 6, col1='AAA'))
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 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_fuzzy_method(self): data = {'A': 'aaa', 'B': 'bbx'} requirement = Query.from_object({'A': 'aaa', 'B': 'bbb'}) validate.fuzzy(data, requirement) with self.assertRaises(ValidationError) as cm: data = {'A': 'axx', 'B': 'bbx'} requirement = Query.from_object({'A': 'aaa', 'B': 'bbb'}) validate.fuzzy(data, requirement) actual = cm.exception.differences expected = {'A': Invalid('axx', expected='aaa')} self.assertEqual(actual, expected)
def test_failing(self): with self.assertRaises(ValidationError) as cm: with AcceptedFuzzy(cutoff=0.7): raise ValidationError(self.differences) remaining = cm.exception.differences self.assertEqual(remaining, [Invalid('bbyy', 'bbbb')]) with self.assertRaises(ValidationError) as cm: with AcceptedFuzzy(cutoff=0.8): raise ValidationError(self.differences) remaining = cm.exception.differences self.assertEqual(remaining, self.differences, msg='none accepted')
def test_percent_empty_value_handling(self): # Test empty deviation cases--should pass without error. with AcceptedPercent(0): # <- Accepts empty deviations only. raise ValidationError([ Invalid(None, 0), Invalid('', 0), ]) # Test diffs that can not be accepted as percentages. with self.assertRaises(ValidationError) as cm: with AcceptedPercent(2.00): # <- Accepts +/- 200%. raise ValidationError([ Invalid(None, 0), # 0% Invalid(0, None), # 0% Deviation(+2, 0), # Can not be accepted by percent. Invalid(+2, None), # Can not be accepted by percent. Deviation(float('nan'), 16), # Not a number. ]) actual = cm.exception.differences expected = [ Deviation(+2, 0), # Can not be accepted by percent. Invalid(2, None), # Can not be accepted by percent. Deviation(float('nan'), 16), # Not a number. ] self.assertEqual(actual, expected)
def test_required_vs_mapping_failing(self): with self.assertRaises(ValidationError) as cm: data = { 'a': ('abc', 1.0), 'b': ('xyz', 2) } # Mapping of base-elements. requirement = ('abc', int) validate(data, requirement) differences = cm.exception.differences self.assertEqual(differences, { 'a': Invalid(('abc', 1.0)), 'b': Invalid(('xyz', 2)) }) with self.assertRaises(ValidationError) as cm: data = {'a': [1, 2.0], 'b': [3.0, 4]} # Mapping of containers. validate(data, int) differences = cm.exception.differences self.assertEqual(differences, { 'a': [Invalid(2.0)], 'b': [Invalid(3.0)] })
def test_predicate_method(self): data = {'A': 'aaa', 'B': [1, 2, 3], 'C': ('a', 1)} requirement = Query.from_object({ 'A': set(['aaa', 'bbb']), 'B': int, 'C': ('a', 1) }) validate.predicate(data, requirement) with self.assertRaises(ValidationError) as cm: data = {'A': 'aaa', 'B': [1, 2, 3.5], 'C': ('b', 2)} requirement = Query.from_object({ 'A': set(['aaa', 'bbb']), 'B': int, 'C': ('a', 1) }) validate.predicate(data, requirement) actual = cm.exception.differences expected = { 'B': [Invalid(3.5)], 'C': Invalid(('b', 2), expected=('a', 1)), } self.assertEqual(actual, expected)
def test_object_vs_object(self): """Non-numeric comparisons return Invalid type.""" diff = _make_difference('a', 'b') self.assertEqual(diff, Invalid('a', 'b')) diff = _make_difference(5, 'b') self.assertEqual(diff, Invalid(5, 'b')) diff = _make_difference('a', 6) self.assertEqual(diff, Invalid('a', 6)) diff = _make_difference(float('nan'), 6) self.assertEqual(diff, Deviation(float('nan'), 6)) diff = _make_difference(5, float('nan')) self.assertEqual(diff, Deviation(float('nan'), float('nan'))) fn = lambda x: True diff = _make_difference('a', fn) self.assertEqual(diff, Invalid('a', fn)) regex = re.compile('^test$') diff = _make_difference('a', regex) self.assertEqual(diff, Invalid('a', re.compile('^test$')))
def test_required_sequence(self): """When *required* is a sequence, should compare predicates by position. """ with self.assertRaises(ValidationError) as cm: data = ['a', 2, 'x', 3] required = ['a', 2, 'c', 4] self.assertValid(data, required) error = cm.exception expected = [ Invalid('x', expected='c'), Deviation(-1, 4), ] self.assertEqual(error.differences, expected) self.assertEqual(error.args[1], 'does not match required sequence')