"""Tests for validation checks.""" import pytest from src.context.glossary import GlossaryManager from src.models.verse import GlossaryTerm, TranslationOutput, VerseReference from src.validation.checks import TranslationValidator, ValidationReport, ValidationResult @pytest.fixture def glossary_terms() -> list[GlossaryTerm]: """Create test glossary terms.""" return [ GlossaryTerm( original="λόγος", transliteration="logos", japanese="言葉", english="word", ), ] class TestValidationResult: """Tests for ValidationResult.""" def test_create_result(self) -> None: """Test creating a validation result.""" result = ValidationResult( passed=True, check_name="test_check", message="All good", ) assert result.passed is True assert result.check_name == "test_check" def test_result_with_details(self) -> None: """Test result with details.""" result = ValidationResult( passed=False, check_name="length", message="Too short", details={"length": 5, "min": 10}, ) assert result.details["length"] == 5 class TestValidationReport: """Tests for ValidationReport.""" def test_empty_report(self) -> None: """Test empty report.""" report = ValidationReport() assert report.all_passed is True assert report.failed_checks == [] def test_add_result(self) -> None: """Test adding results.""" report = ValidationReport() report.add_result( ValidationResult(passed=True, check_name="check1", message="OK") ) assert len(report.results) == 1 def test_all_passed_true(self) -> None: """Test all_passed returns True when all pass.""" report = ValidationReport() report.add_result( ValidationResult(passed=True, check_name="check1", message="OK") ) report.add_result( ValidationResult(passed=True, check_name="check2", message="OK") ) assert report.all_passed is True def test_all_passed_false(self) -> None: """Test all_passed returns False when any fail.""" report = ValidationReport() report.add_result( ValidationResult(passed=True, check_name="check1", message="OK") ) report.add_result( ValidationResult(passed=False, check_name="check2", message="Failed") ) assert report.all_passed is False def test_failed_checks(self) -> None: """Test getting failed checks.""" report = ValidationReport() report.add_result( ValidationResult(passed=True, check_name="check1", message="OK") ) report.add_result( ValidationResult(passed=False, check_name="check2", message="Failed") ) failed = report.failed_checks assert len(failed) == 1 assert failed[0].check_name == "check2" class TestTranslationValidator: """Tests for TranslationValidator.""" def test_init_defaults(self) -> None: """Test default initialization.""" validator = TranslationValidator() assert validator.min_length == 5 assert validator.max_length == 2000 def test_init_custom_params(self) -> None: """Test custom parameters.""" validator = TranslationValidator(min_length=10, max_length=100) assert validator.min_length == 10 assert validator.max_length == 100 def test_validate_valid_translation(self) -> None: """Test validating a valid translation.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="初めに言葉があった。言葉は神と共にあった。", model="test", ) report = validator.validate(output) assert report.all_passed is True def test_check_not_empty_fails(self) -> None: """Test not_empty check fails for empty text.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput(reference=ref, text="", model="test") report = validator.validate(output) assert not report.all_passed failed_names = [r.check_name for r in report.failed_checks] assert "not_empty" in failed_names def test_check_not_empty_whitespace_only(self) -> None: """Test not_empty check fails for whitespace-only text.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput(reference=ref, text=" \n\t ", model="test") report = validator.validate(output) failed_names = [r.check_name for r in report.failed_checks] assert "not_empty" in failed_names def test_check_length_too_short(self) -> None: """Test length check fails for short text.""" validator = TranslationValidator(min_length=10) ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput(reference=ref, text="短い", model="test") report = validator.validate(output) failed_names = [r.check_name for r in report.failed_checks] assert "length" in failed_names def test_check_length_too_long(self) -> None: """Test length check fails for long text.""" validator = TranslationValidator(max_length=10) ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="これはとても長い文章です。", model="test" ) report = validator.validate(output) failed_names = [r.check_name for r in report.failed_checks] assert "length" in failed_names def test_check_no_english_only_fails(self) -> None: """Test no_english_only check fails for English-only text.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="In the beginning was the Word", model="test" ) report = validator.validate(output) failed_names = [r.check_name for r in report.failed_checks] assert "no_english_only" in failed_names def test_check_has_japanese_low_ratio(self) -> None: """Test has_japanese check fails for low Japanese ratio.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) # Mostly English with just a few Japanese output = TranslationOutput( reference=ref, text="This is English text あ", model="test" ) report = validator.validate(output) failed_names = [r.check_name for r in report.failed_checks] assert "has_japanese" in failed_names def test_check_glossary_consistency_no_glossary(self) -> None: """Test glossary check passes without glossary.""" validator = TranslationValidator() ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="初めに言葉があった。", model="test", glossary_terms_applied=["λόγος"], ) report = validator.validate(output) # Should pass since no glossary to check against glossary_results = [ r for r in report.results if r.check_name == "glossary_consistency" ] assert len(glossary_results) == 0 # No glossary configured def test_check_glossary_consistency_with_glossary( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test glossary check with glossary.""" glossary = GlossaryManager(glossary_terms) validator = TranslationValidator(glossary=glossary) ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="初めに言葉があった。", # Contains 言葉 model="test", glossary_terms_applied=["λόγος"], ) report = validator.validate(output) glossary_results = [ r for r in report.results if r.check_name == "glossary_consistency" ] assert len(glossary_results) == 1 assert glossary_results[0].passed is True def test_check_glossary_consistency_missing_term( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test glossary check fails for missing term.""" glossary = GlossaryManager(glossary_terms) validator = TranslationValidator(glossary=glossary) ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="初めにワードがあった。", # Missing 言葉, using ワード instead model="test", glossary_terms_applied=["λόγος"], ) report = validator.validate(output) glossary_results = [ r for r in report.results if r.check_name == "glossary_consistency" ] assert len(glossary_results) == 1 assert glossary_results[0].passed is False def test_check_glossary_consistency_no_terms_applied( self, glossary_terms: list[GlossaryTerm] ) -> None: """Test glossary check passes when no terms expected.""" glossary = GlossaryManager(glossary_terms) validator = TranslationValidator(glossary=glossary) ref = VerseReference(book="john", chapter=1, verse=1) output = TranslationOutput( reference=ref, text="初めにあった。", model="test", glossary_terms_applied=[], # No terms expected ) report = validator.validate(output) glossary_results = [ r for r in report.results if r.check_name == "glossary_consistency" ] assert len(glossary_results) == 1 assert glossary_results[0].passed is True