Add comprehensive tests for metric validators, constraint validators, and composite validators covering pass/fail cases and error handling.
179 lines
5.7 KiB
Python
179 lines
5.7 KiB
Python
"""Tests for composite validators."""
|
|
|
|
import pytest
|
|
|
|
from veritext.core.types import ValidationContext
|
|
from veritext.validators import all_of, any_of, bleu, contains, excludes, length
|
|
from veritext.validators.composite import AllOf, AnyOf
|
|
|
|
|
|
class TestAllOf:
|
|
def test_all_of_passes_when_all_checks_pass(self) -> None:
|
|
validator = AllOf(
|
|
checks=[
|
|
length(min_words=2),
|
|
contains(patterns=["hello"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello world", context)
|
|
|
|
assert result.passed is True
|
|
assert len(result.checks) == 2
|
|
assert all(c.passed for c in result.checks)
|
|
|
|
def test_all_of_fails_when_one_check_fails(self) -> None:
|
|
validator = AllOf(
|
|
checks=[
|
|
length(min_words=2),
|
|
contains(patterns=["goodbye"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello world", context)
|
|
|
|
assert result.passed is False
|
|
assert len(result.checks) == 2
|
|
assert len(result.failed_checks) == 1
|
|
|
|
def test_all_of_fails_when_all_checks_fail(self) -> None:
|
|
validator = AllOf(
|
|
checks=[
|
|
length(min_words=10),
|
|
contains(patterns=["goodbye"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello", context)
|
|
|
|
assert result.passed is False
|
|
assert len(result.failed_checks) == 2
|
|
|
|
def test_all_of_with_metric_validators(self) -> None:
|
|
validator = AllOf(
|
|
checks=[
|
|
bleu(min_score=0.5),
|
|
length(min_words=3),
|
|
]
|
|
)
|
|
context = ValidationContext(reference="the quick brown fox")
|
|
result = validator.check("the quick brown fox jumps", context)
|
|
|
|
assert result.passed is True
|
|
assert len(result.checks) == 2
|
|
|
|
def test_all_of_failure_summary(self) -> None:
|
|
validator = AllOf(
|
|
checks=[
|
|
length(min_words=10),
|
|
contains(patterns=["goodbye"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello", context)
|
|
|
|
summary = result.failure_summary
|
|
assert "failed" in summary.lower()
|
|
assert "length" in summary
|
|
assert "contains" in summary
|
|
|
|
def test_all_of_raises_on_empty_checks(self) -> None:
|
|
with pytest.raises(ValueError, match="cannot be empty"):
|
|
AllOf(checks=[])
|
|
|
|
def test_all_of_name_property(self) -> None:
|
|
validator = AllOf(checks=[length(min_chars=1)])
|
|
assert validator.name == "all_of"
|
|
|
|
def test_all_of_factory_function(self) -> None:
|
|
validator = all_of(checks=[length(min_chars=1)])
|
|
assert isinstance(validator, AllOf)
|
|
|
|
|
|
class TestAnyOf:
|
|
def test_any_of_passes_when_any_check_passes(self) -> None:
|
|
validator = AnyOf(
|
|
checks=[
|
|
length(min_words=10), # Will fail
|
|
contains(patterns=["hello"]), # Will pass
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello world", context)
|
|
|
|
assert result.passed is True
|
|
assert len(result.checks) == 2
|
|
# At least one check passed
|
|
assert any(c.passed for c in result.checks)
|
|
|
|
def test_any_of_passes_when_all_checks_pass(self) -> None:
|
|
validator = AnyOf(
|
|
checks=[
|
|
length(min_words=2),
|
|
contains(patterns=["hello"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello world", context)
|
|
|
|
assert result.passed is True
|
|
assert all(c.passed for c in result.checks)
|
|
|
|
def test_any_of_fails_when_all_checks_fail(self) -> None:
|
|
validator = AnyOf(
|
|
checks=[
|
|
length(min_words=10),
|
|
contains(patterns=["goodbye"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
result = validator.check("hello", context)
|
|
|
|
assert result.passed is False
|
|
assert not any(c.passed for c in result.checks)
|
|
|
|
def test_any_of_with_metric_validators(self) -> None:
|
|
validator = AnyOf(
|
|
checks=[
|
|
bleu(min_score=0.9), # Might fail
|
|
length(min_words=3), # Should pass
|
|
]
|
|
)
|
|
context = ValidationContext(reference="different text entirely")
|
|
result = validator.check("the quick brown fox jumps", context)
|
|
|
|
assert result.passed is True # Length check passes
|
|
|
|
def test_any_of_with_excludes(self) -> None:
|
|
validator = AnyOf(
|
|
checks=[
|
|
excludes(patterns=["error"]),
|
|
excludes(patterns=["warning"]),
|
|
]
|
|
)
|
|
context = ValidationContext()
|
|
|
|
# Should pass - neither pattern found
|
|
result = validator.check("All is well", context)
|
|
assert result.passed is True
|
|
|
|
# Should pass - one pattern found, other not
|
|
result = validator.check("This is an error", context)
|
|
assert result.passed is True
|
|
|
|
# Should fail - both patterns found
|
|
result = validator.check("error and warning", context)
|
|
assert result.passed is False
|
|
|
|
def test_any_of_raises_on_empty_checks(self) -> None:
|
|
with pytest.raises(ValueError, match="cannot be empty"):
|
|
AnyOf(checks=[])
|
|
|
|
def test_any_of_name_property(self) -> None:
|
|
validator = AnyOf(checks=[length(min_chars=1)])
|
|
assert validator.name == "any_of"
|
|
|
|
def test_any_of_factory_function(self) -> None:
|
|
validator = any_of(checks=[length(min_chars=1)])
|
|
assert isinstance(validator, AnyOf)
|