"""Tests for input validation utilities.""" import pytest from src.exceptions import InvalidReferenceError, VerseNotFoundError from src.utils.validation import ( ALL_BOOKS, BOOK_ALIASES, NT_BOOKS, OT_BOOKS, is_valid_book, normalize_book_name, parse_reference_string, sanitize_book_name_for_path, validate_chapter, validate_reference, validate_verse, ) class TestNormalizeBookName: """Tests for normalize_book_name.""" def test_canonical_name_lowercase(self): """Test canonical name passes through lowercase.""" assert normalize_book_name("john") == "john" assert normalize_book_name("genesis") == "genesis" def test_canonical_name_mixed_case(self): """Test canonical name normalizes case.""" assert normalize_book_name("John") == "john" assert normalize_book_name("GENESIS") == "genesis" assert normalize_book_name("Matthew") == "matthew" def test_alias_resolution(self): """Test alias names are resolved.""" assert normalize_book_name("jn") == "john" assert normalize_book_name("gen") == "genesis" assert normalize_book_name("matt") == "matthew" assert normalize_book_name("rev") == "revelation" def test_numbered_books(self): """Test numbered books.""" assert normalize_book_name("1john") == "1john" assert normalize_book_name("1jn") == "1john" assert normalize_book_name("2corinthians") == "2corinthians" assert normalize_book_name("2cor") == "2corinthians" def test_removes_separators(self): """Test separators are removed.""" assert normalize_book_name("song of solomon") == "songofsolomon" assert normalize_book_name("song-of-solomon") == "songofsolomon" assert normalize_book_name("song_of_solomon") == "songofsolomon" def test_unknown_book_raises(self): """Test unknown book raises InvalidReferenceError.""" with pytest.raises(InvalidReferenceError) as exc_info: normalize_book_name("notabook") assert "Unknown book name" in str(exc_info.value) def test_whitespace_handling(self): """Test whitespace is handled.""" assert normalize_book_name(" john ") == "john" assert normalize_book_name("1 john") == "1john" class TestValidateChapter: """Tests for validate_chapter.""" def test_valid_chapters(self): """Test valid chapter numbers pass.""" validate_chapter("john", 1) validate_chapter("john", 21) validate_chapter("genesis", 50) def test_zero_chapter_raises(self): """Test chapter 0 raises error.""" with pytest.raises(InvalidReferenceError) as exc_info: validate_chapter("john", 0) assert "must be positive" in str(exc_info.value) def test_negative_chapter_raises(self): """Test negative chapter raises error.""" with pytest.raises(InvalidReferenceError): validate_chapter("john", -1) def test_chapter_exceeds_max_raises(self): """Test chapter exceeding max raises error.""" with pytest.raises(InvalidReferenceError) as exc_info: validate_chapter("john", 100) assert "only has" in str(exc_info.value) class TestValidateVerse: """Tests for validate_verse.""" def test_valid_verses(self): """Test valid verse numbers pass.""" validate_verse("john", 1, 1) validate_verse("john", 1, 51) validate_verse("psalms", 119, 176) def test_zero_verse_raises(self): """Test verse 0 raises error.""" with pytest.raises(InvalidReferenceError) as exc_info: validate_verse("john", 1, 0) assert "must be positive" in str(exc_info.value) def test_negative_verse_raises(self): """Test negative verse raises error.""" with pytest.raises(InvalidReferenceError): validate_verse("john", 1, -1) def test_implausibly_high_verse_raises(self): """Test implausibly high verse raises error.""" with pytest.raises(InvalidReferenceError) as exc_info: validate_verse("john", 1, 500) assert "implausibly high" in str(exc_info.value) class TestValidateReference: """Tests for validate_reference.""" def test_valid_reference(self): """Test valid reference returns canonical book name.""" result = validate_reference("jn", 1, 1) assert result == "john" def test_normalizes_book(self): """Test book name is normalized.""" result = validate_reference("Matt", 1, 1) assert result == "matthew" def test_invalid_book_raises(self): """Test invalid book raises error.""" with pytest.raises(InvalidReferenceError): validate_reference("invalid", 1, 1) def test_invalid_chapter_raises(self): """Test invalid chapter raises error.""" with pytest.raises(InvalidReferenceError): validate_reference("john", 0, 1) def test_invalid_verse_raises(self): """Test invalid verse raises error.""" with pytest.raises(InvalidReferenceError): validate_reference("john", 1, 0) class TestParseReferenceString: """Tests for parse_reference_string.""" def test_standard_format(self): """Test standard 'Book chapter:verse' format.""" book, chapter, verse = parse_reference_string("John 3:16") assert book == "john" assert chapter == 3 assert verse == 16 def test_lowercase_format(self): """Test lowercase format.""" book, chapter, verse = parse_reference_string("john 3:16") assert book == "john" assert chapter == 3 assert verse == 16 def test_alias_format(self): """Test alias format.""" book, chapter, verse = parse_reference_string("jn 3:16") assert book == "john" assert chapter == 3 assert verse == 16 def test_numbered_book_format(self): """Test numbered book format.""" book, chapter, verse = parse_reference_string("1 John 1:1") assert book == "1john" assert chapter == 1 assert verse == 1 def test_colon_separated_format(self): """Test 'book:chapter:verse' format.""" book, chapter, verse = parse_reference_string("john:3:16") assert book == "john" assert chapter == 3 assert verse == 16 def test_invalid_format_raises(self): """Test invalid format raises error.""" with pytest.raises(InvalidReferenceError) as exc_info: parse_reference_string("invalid") assert "Expected format" in str(exc_info.value) def test_whitespace_handling(self): """Test whitespace is handled.""" book, chapter, verse = parse_reference_string(" John 3:16 ") assert book == "john" class TestSanitizeBookNameForPath: """Tests for sanitize_book_name_for_path.""" def test_valid_book_passes(self): """Test valid book name passes.""" result = sanitize_book_name_for_path("john") assert result == "john" def test_alias_is_normalized(self): """Test alias is normalized.""" result = sanitize_book_name_for_path("jn") assert result == "john" def test_invalid_book_raises(self): """Test invalid book raises error.""" with pytest.raises(InvalidReferenceError): sanitize_book_name_for_path("notabook") class TestIsValidBook: """Tests for is_valid_book.""" def test_valid_books_return_true(self): """Test valid books return True.""" assert is_valid_book("john") is True assert is_valid_book("jn") is True assert is_valid_book("Matthew") is True assert is_valid_book("1john") is True def test_invalid_books_return_false(self): """Test invalid books return False.""" assert is_valid_book("notabook") is False assert is_valid_book("xyz") is False assert is_valid_book("") is False class TestBookLists: """Tests for book list constants.""" def test_nt_books_count(self): """Test NT has 27 books.""" assert len(NT_BOOKS) == 27 def test_ot_books_count(self): """Test OT has 39 books.""" assert len(OT_BOOKS) == 39 def test_all_books_combined(self): """Test ALL_BOOKS is OT + NT.""" assert len(ALL_BOOKS) == 66 assert ALL_BOOKS == OT_BOOKS + NT_BOOKS def test_no_duplicates(self): """Test no duplicate book names.""" assert len(set(ALL_BOOKS)) == len(ALL_BOOKS) def test_john_in_nt(self): """Test John is in NT books.""" assert "john" in NT_BOOKS def test_genesis_in_ot(self): """Test Genesis is in OT books.""" assert "genesis" in OT_BOOKS class TestBookAliases: """Tests for book aliases.""" def test_common_aliases_exist(self): """Test common aliases exist.""" assert "jn" in BOOK_ALIASES assert "gen" in BOOK_ALIASES assert "matt" in BOOK_ALIASES assert "rev" in BOOK_ALIASES def test_aliases_map_to_valid_books(self): """Test all aliases map to valid books.""" for alias, book in BOOK_ALIASES.items(): assert book in ALL_BOOKS, f"Alias '{alias}' maps to invalid book '{book}'"