5
0

Adds book reference to API output. Improved overall stability. Adds more tests to check stability of code base.

This commit is contained in:
Llewellyn van der Merwe 2023-11-28 07:34:58 +02:00
parent 8901087217
commit 8e28a9f144
Signed by: Llewellyn
GPG Key ID: A9201372263741E7
8 changed files with 131 additions and 64 deletions

View File

@ -5,7 +5,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
setup(
name="getbible",
version="1.0.3",
version="1.1.0",
author="Llewellyn van der Merwe",
author_email="getbible@vdm.io",
description="A Python package to retrieving Bible references with ease.",

View File

@ -7,6 +7,7 @@ import time
from datetime import datetime, timedelta
from typing import Dict, Optional, Union
from getbible import GetBibleReference
from getbible import BookReference
class GetBible:
@ -26,6 +27,7 @@ class GetBible:
self.__books_cache = {}
self.__chapters_cache = {}
self.__start_cache_reset_thread()
# Pattern to check valid translations names
self.__pattern = re.compile(r'^[a-zA-Z0-9]{1,30}$')
# Determine if the repository path is a URL
self.__repo_path_url = self.__repo_path.startswith("http://") or self.__repo_path.startswith("https://")
@ -43,11 +45,11 @@ class GetBible:
references = reference.split(';')
for ref in references:
try:
reference = self.__get.ref(ref, abbreviation)
book_reference = self.__get.ref(ref, abbreviation)
except ValueError:
raise ValueError(f"Invalid reference '{ref}'.")
self.__set_verse(abbreviation, reference.book, reference.chapter, reference.verses, result)
self.__set_verse(abbreviation, book_reference, result)
return result
@ -127,18 +129,16 @@ class GetBible:
first_of_next_month = (now.replace(day=1) + timedelta(days=32)).replace(day=1)
return (first_of_next_month - now).total_seconds()
def __set_verse(self, abbreviation: str, book: int, chapter: int, verses: list, result: Dict) -> None:
def __set_verse(self, abbreviation: str, book_ref: BookReference, result: Dict) -> None:
"""
Set verse information into the result JSON.
:param abbreviation: Bible translation abbreviation.
:param book: The book of the Bible.
:param chapter: The chapter number.
:param verses: List of verse numbers.
:param book_ref: The book reference class.
:param result: The dictionary to store verse information.
"""
cache_key = f"{abbreviation}_{book}_{chapter}"
cache_key = f"{abbreviation}_{book_ref.book}_{book_ref.chapter}"
if cache_key not in self.__chapters_cache:
chapter_data = self.__retrieve_chapter_data(abbreviation, book, chapter)
chapter_data = self.__retrieve_chapter_data(abbreviation, book_ref.book, book_ref.chapter)
# Convert verses list to dictionary for faster lookup
verse_dict = {str(v["verse"]): v for v in chapter_data.get("verses", [])}
chapter_data["verses"] = verse_dict
@ -146,18 +146,22 @@ class GetBible:
else:
chapter_data = self.__chapters_cache[cache_key]
for verse in verses:
for verse in book_ref.verses:
verse_info = chapter_data["verses"].get(str(verse))
if not verse_info:
raise ValueError(f"Verse {verse} not found in book {book}, chapter {chapter}.")
raise ValueError(f"Verse {verse} not found in book {book_ref.book}, chapter {book_ref.chapter}.")
if cache_key in result:
existing_verses = {str(v["verse"]) for v in result[cache_key].get("verses", [])}
if str(verse) not in existing_verses:
result[cache_key]["verses"].append(verse_info)
existing_ref = result[cache_key].get("ref", [])
if str(book_ref.reference) not in existing_ref:
result[cache_key]["ref"].append(book_ref.reference)
else:
# Include all other relevant elements of your JSON structure
result[cache_key] = {key: chapter_data[key] for key in chapter_data if key != "verses"}
result[cache_key]["ref"] = [book_ref.reference]
result[cache_key]["verses"] = [verse_info]
def __check_translation(self, abbreviation: str) -> None:
@ -169,7 +173,7 @@ class GetBible:
"""
# Use valid_translation to check if the translation is available
if not self.valid_translation(abbreviation):
raise FileNotFoundError(f"Translation ({abbreviation}) not found in this API.")
raise FileNotFoundError(f"Translation ({abbreviation}) not found.")
def __generate_path(self, abbreviation: str, file_name: str) -> str:
"""
@ -210,7 +214,7 @@ class GetBible:
:param abbreviation: Bible translation abbreviation.
:param book: The book of the Bible.
:param chapter: The chapter number.
:param chapter: The chapter.
:return: Chapter data.
:raises FileNotFoundError: If the chapter data is not found.
"""
@ -218,5 +222,5 @@ class GetBible:
f"{chapter}.json")
chapter_data = self.__fetch_data(self.__generate_path(abbreviation, chapter_file))
if chapter_data is None:
raise FileNotFoundError(f"File {abbreviation}/{book}/{chapter}.json does not exist.")
raise FileNotFoundError(f"Chapter:{chapter} in book:{book} for {abbreviation} not found.")
return chapter_data

View File

@ -10,8 +10,8 @@ class GetBibleBookNumber:
Sets up the class by loading all translation tries from the data directory.
"""
self._tries = {}
self._data_path = os.path.join(os.path.dirname(__file__), 'data')
self.__tries = {}
self.__data_path = os.path.join(os.path.dirname(__file__), 'data')
self.__load_all_translations()
def __load_translation(self, filename: str) -> None:
@ -24,19 +24,33 @@ class GetBibleBookNumber:
trie = GetBibleReferenceTrie()
translation_code = filename.split('.')[0]
try:
trie.load(os.path.join(self._data_path, filename))
trie.load(os.path.join(self.__data_path, filename))
except IOError as e:
raise IOError(f"Error loading translation {translation_code}: {e}")
self._tries[translation_code] = trie
self.__tries[translation_code] = trie
def __load_all_translations(self) -> None:
"""
Load all translation tries from the data directory.
"""
for filename in os.listdir(self._data_path):
for filename in os.listdir(self.__data_path):
if filename.endswith('.json'):
self.__load_translation(filename)
def __valid_book_number(self, number: str) -> Optional[int]:
"""
Check if the number is a valid book number.
"""
try:
num = int(number)
if 1 <= num <= 83:
return num
else:
return None
except ValueError:
# Handle the case where the number cannot be converted to an integer
return None
def number(self, reference: str, translation_code: Optional[str] = None,
fallback_translations: Optional[List[str]] = None) -> Optional[int]:
"""
@ -47,27 +61,30 @@ class GetBibleBookNumber:
:param fallback_translations: A list of fallback translations to use if necessary.
:return: The book number as an integer if found, None otherwise.
"""
if not translation_code or translation_code not in self._tries:
if reference.isdigit():
return self.__valid_book_number(reference)
if not translation_code or translation_code not in self.__tries:
translation_code = 'kjv'
translation = self._tries.get(translation_code)
translation = self.__tries.get(translation_code)
result = translation.search(reference) if translation else None
if result and result.isdigit():
return int(result)
# If 'kjv' is not the original choice, try it next
if translation_code != 'kjv':
translation = self._tries.get('kjv')
translation = self.__tries.get('kjv')
result = translation.search(reference) if translation else None
if result and result.isdigit():
return int(result)
# Fallback to other translations
if fallback_translations is None:
fallback_translations = [code for code in self._tries if code != translation_code]
fallback_translations = [code for code in self.__tries if code != translation_code]
for code in fallback_translations:
translation = self._tries.get(code)
translation = self.__tries.get(code)
result = translation.search(reference) if translation else None
if result and result.isdigit():
return int(result)
@ -82,7 +99,7 @@ class GetBibleBookNumber:
:param filename: The name of the file to dump to.
:raises ValueError: If no data is available for the specified translation.
"""
if translation_code in self._tries:
self._tries[translation_code].dump(filename)
if translation_code in self.__tries:
self.__tries[translation_code].dump(filename)
else:
raise ValueError(f"No data available for translation: {translation_code}")

View File

@ -9,14 +9,15 @@ class BookReference:
book: int
chapter: int
verses: list
reference: str
class GetBibleReference:
def __init__(self):
self.__get_book = GetBibleBookNumber()
self.__pattern = re.compile(r'^[\w\s,:-]{1,50}$', re.UNICODE)
self.cache = {}
self.cache_limit = 5000
self.__cache = {}
self.__cache_limit = 5000
def ref(self, reference: str, translation_code: Optional[str] = None) -> BookReference:
"""
@ -30,12 +31,12 @@ class GetBibleReference:
sanitized_ref = self.__sanitize(reference)
if not sanitized_ref:
raise ValueError(f"Invalid reference '{reference}'.")
if sanitized_ref not in self.cache:
if sanitized_ref not in self.__cache:
book_ref = self.__book_reference(reference, translation_code)
if book_ref is None:
raise ValueError(f"Invalid reference '{reference}'.")
self.__manage_local_cache(sanitized_ref, book_ref)
return self.cache[sanitized_ref]
return self.__cache[sanitized_ref]
def valid(self, reference: str, translation_code: Optional[str] = None) -> bool:
"""
@ -48,10 +49,10 @@ class GetBibleReference:
sanitized_ref = self.__sanitize(reference)
if sanitized_ref is None:
return False
if sanitized_ref not in self.cache:
if sanitized_ref not in self.__cache:
book_ref = self.__book_reference(reference, translation_code)
self.__manage_local_cache(sanitized_ref, book_ref)
return self.cache[sanitized_ref] is not None
return self.__cache[sanitized_ref] is not None
def __sanitize(self, reference: str) -> Optional[str]:
"""
@ -80,7 +81,7 @@ class GetBibleReference:
return None
verses_arr = self.__get_verses_numbers(verses_portion)
chapter_number = self.__extract_chapter(book_chapter)
return BookReference(book=int(book_number), chapter=chapter_number, verses=verses_arr)
return BookReference(book=int(book_number), chapter=chapter_number, verses=verses_arr, reference=reference)
except Exception:
return None
@ -112,7 +113,7 @@ class GetBibleReference:
"""
if book_chapter.isdigit():
# If the entire string is numeric, return it as is
return book_chapter
return book_chapter.strip()
chapter_match = re.search(r'\d+$', book_chapter)
return book_chapter[:chapter_match.start()].strip() if chapter_match else book_chapter.strip()
@ -150,10 +151,7 @@ class GetBibleReference:
:param abbreviation: Translation abbreviation.
:return: Book number or None if not found.
"""
if book_name.isdigit():
return int(book_name)
book_number = self.__get_book.number(book_name, abbreviation)
return int(book_number) if book_number is not None else None
return self.__get_book.number(book_name, abbreviation)
def __manage_local_cache(self, key: str, value: Optional[BookReference]):
"""
@ -162,6 +160,6 @@ class GetBibleReference:
:param key: The key to insert into the cache.
:param value: The value to associate with the key.
"""
if len(self.cache) >= self.cache_limit:
self.cache.pop(next(iter(self.cache))) # Evict the oldest cache item
self.cache[key] = value
if len(self.__cache) >= self.__cache_limit:
self.__cache.pop(next(iter(self.__cache))) # Evict the oldest cache item
self.__cache[key] = value

View File

@ -11,8 +11,8 @@ class GetBibleReferenceTrie:
Sets up the Trie data structure for storing and searching book names.
"""
self.root = TrieNode()
self.space_removal_regex = re.compile(r'(\d)\s+(\w)', re.UNICODE)
self.__root = TrieNode()
self.__space_removal_regex = re.compile(r'(\d)\s+(\w)', re.UNICODE)
def __preprocess(self, name: str) -> str:
"""
@ -22,7 +22,7 @@ class GetBibleReferenceTrie:
:return: The processed name in lowercase.
"""
processed_name = name.replace('.', '')
processed_name = self.space_removal_regex.sub(r'\1\2', processed_name)
processed_name = self.__space_removal_regex.sub(r'\1\2', processed_name)
return processed_name.lower()
def __insert(self, book_number: str, names: [str]) -> None:
@ -34,7 +34,7 @@ class GetBibleReferenceTrie:
"""
for name in names:
processed_name = self.__preprocess(name)
node = self.root
node = self.__root
for char in processed_name:
node = node.children.setdefault(char, TrieNode())
node.book_number = book_number
@ -47,7 +47,7 @@ class GetBibleReferenceTrie:
:return: The book number if found, None otherwise.
"""
processed_name = self.__preprocess(book_name)
node = self.root
node = self.__root
for char in processed_name:
node = node.children.get(char)
if node is None:
@ -63,7 +63,7 @@ class GetBibleReferenceTrie:
:return: Dictionary representation of the Trie.
"""
if node is None:
node = self.root
node = self.__root
result = {}
if node.book_number is not None:

View File

@ -13,7 +13,7 @@ class TestGetBible(unittest.TestCase):
expected_result = {
"kjv_1_1": {"translation": "King James Version", "abbreviation": "kjv", "lang": "en", "language": "English",
"direction": "LTR", "encoding": "UTF-8", "book_nr": 1, "book_name": "Genesis", "chapter": 1,
"name": "Genesis 1", "verses": [{"chapter": 1, "verse": 2, "name": "Genesis 1:2",
"name": "Genesis 1", "ref": ["Gen 1:2-7"], "verses": [{"chapter": 1, "verse": 2, "name": "Genesis 1:2",
"text": "And the earth was without form and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters."},
{"chapter": 1, "verse": 3, "name": "Genesis 1:3",
"text": "And God said, Let there be light: and there was light."},
@ -33,7 +33,7 @@ class TestGetBible(unittest.TestCase):
"cns_1_1": {"translation": "NCV Simplified", "abbreviation": "cns", "lang": "zh-Hans",
"language": "Chinese",
"direction": "LTR", "encoding": "UTF-8", "book_nr": 1, "book_name": "\ufeff\u521b\u4e16\u8bb0",
"chapter": 1, "name": "\ufeff\u521b\u4e16\u8bb0 1", "verses": [
"chapter": 1, "name": "\ufeff\u521b\u4e16\u8bb0 1", "ref": ["创世记1:2-7"], "verses": [
{"chapter": 1, "verse": 2, "name": "\ufeff\u521b\u4e16\u8bb0 1:2",
"text": "\u5730\u662f\u7a7a\u865a\u6df7\u6c8c\uff1b\u6df1\u6e0a\u4e0a\u4e00\u7247\u9ed1\u6697\uff1b\u3000\u795e\u7684\u7075\u8fd0\u884c\u5728\u6c34\u9762\u4e0a\u3002 "},
{"chapter": 1, "verse": 3, "name": "\ufeff\u521b\u4e16\u8bb0 1:3",
@ -53,15 +53,15 @@ class TestGetBible(unittest.TestCase):
expected_result = {
"aov_1_1": {"translation": "Ou Vertaling", "abbreviation": "aov", "lang": "af", "language": "Afrikaans",
"direction": "LTR", "encoding": "UTF-8", "book_nr": 1, "book_name": "Genesis", "chapter": 1,
"name": "Genesis 1", "verses": [{"chapter": 1, "verse": 1, "name": "Genesis 1:1",
"name": "Genesis 1", "ref": ['Ge1:1'], "verses": [{"chapter": 1, "verse": 1, "name": "Genesis 1:1",
"text": "In die begin het God die hemel en die aarde geskape. "}]},
"aov_43_1": {"translation": "Ou Vertaling", "abbreviation": "aov", "lang": "af", "language": "Afrikaans",
"direction": "LTR", "encoding": "UTF-8", "book_nr": 43, "book_name": "Johannes", "chapter": 1,
"name": "Johannes 1", "verses": [{"chapter": 1, "verse": 1, "name": "Johannes 1:1",
"name": "Johannes 1", "ref": ['Jn1:1'], "verses": [{"chapter": 1, "verse": 1, "name": "Johannes 1:1",
"text": "In die begin was die Woord, en die Woord was by God, en die Woord was God. "}]},
"aov_62_1": {"translation": "Ou Vertaling", "abbreviation": "aov", "lang": "af", "language": "Afrikaans",
"direction": "LTR", "encoding": "UTF-8", "book_nr": 62, "book_name": "1 Johannes",
"chapter": 1, "name": "1 Johannes 1", "verses": [
"chapter": 1, "name": "1 Johannes 1", "ref": ["1Jn1:1"], "verses": [
{"chapter": 1, "verse": 1, "name": "1 Johannes 1:1",
"text": "Wat van die begin af was, wat ons gehoor het, wat ons met ons o\u00eb gesien het, wat ons aanskou het en ons hande getas het aangaande die Woord van die lewe \u2014 "}]}}
self.assertEqual(actual_result, expected_result, "Failed to find 'Ge1:1;Jn1:1;1Jn1:1' scripture.")
@ -79,6 +79,7 @@ class TestGetBible(unittest.TestCase):
'language': 'Hebrew',
'name': 'תְּהִלִּים 1',
'translation': 'Aleppo Codex',
'ref': ['Ps1:1', 'ps1:1-2'],
'verses': [{'chapter': 1,
'name': 'תְּהִלִּים 1:1',
'text': '\xa0\xa0אשרי האיש— \xa0\xa0 אשר לא הלך '
@ -100,6 +101,7 @@ class TestGetBible(unittest.TestCase):
'language': 'Hebrew',
'name': 'בְּרֵאשִׁית 1',
'translation': 'Aleppo Codex',
'ref': ['Ge1:1-3', 'Ge1:6-7,10'],
'verses': [{'chapter': 1,
'name': 'בְּרֵאשִׁית 1:1',
'text': 'בראשית ברא אלהים את השמים ואת הארץ ',
@ -134,6 +136,35 @@ class TestGetBible(unittest.TestCase):
self.assertEqual(actual_result, expected_result, "Failed to find 'Ge1:1-3;Ps1:1;ps1:1-2;Ge1:6-7,1' scripture.")
def test_invalid_reference_select_aleppo(self):
expected_exception = "Chapter:111 in book:1 for aleppo not found."
with self.assertRaises(FileNotFoundError) as actual:
self.getbible.select('Ge111', 'aleppo')
self.assertEqual(str(actual.exception), expected_exception)
def test_invalid_reference_select_kjv(self):
expected_exception = "Verse 111 not found in book 1, chapter 1."
with self.assertRaises(ValueError) as actual:
self.getbible.select('Ge 1:111', 'kjv')
self.assertEqual(str(actual.exception), expected_exception)
def test_invalid_double_dash_reference_select_kjv(self):
expected_exception = "Invalid reference 'Ge 1:1-7-11'."
with self.assertRaises(ValueError) as actual:
self.getbible.select('Ge 1:1-7-11', 'kjv')
self.assertEqual(str(actual.exception), expected_exception)
def test_invalid_verse_reference_select_kjv(self):
expected_exception = "Verse 32 not found in book 1, chapter 1."
with self.assertRaises(ValueError) as actual:
self.getbible.select('1 1:1-80', 'kjv')
self.assertEqual(str(actual.exception), expected_exception)
def test_invalid_book_reference_select_kjv(self):
expected_exception = "Invalid reference '112 1:1-80'."
with self.assertRaises(ValueError) as actual:
self.getbible.select('112 1:1-80', 'kjv')
self.assertEqual(str(actual.exception), expected_exception)
if __name__ == '__main__':
unittest.main()

View File

@ -47,6 +47,23 @@ class TestGetBibleBookNumber(unittest.TestCase):
actual_result = self.get_book.number('First John', 'kjv')
self.assertEqual(actual_result, expected_result, "Failed to find 'First John' in 'kjv' translation")
def test_valid_1_john(self):
expected_result = 62
actual_result = self.get_book.number('62', 'kjv')
self.assertEqual(actual_result, expected_result, "Failed to find '62' in 'kjv' translation")
def test_wrong_book_number(self):
actual_result = self.get_book.number('84', 'kjv')
self.assertIsNone(actual_result, "Failed to limit the books in 'kjv' translation to 1-83")
def test_wrong_book_number_zero(self):
actual_result = self.get_book.number('0', 'kjv')
self.assertIsNone(actual_result, "Failed to limit the books in 'kjv' translation to 1-83")
def test_wrong_book_number_minus(self):
actual_result = self.get_book.number('-19', 'kjv')
self.assertIsNone(actual_result, "Failed to limit the books in 'kjv' translation to 1-83")
def test_valid_mismatch_nospace_call(self):
expected_result = 62
actual_result = self.get_book.number('1Jn', 'aov')

View File

@ -8,57 +8,57 @@ class TestGetBibleReference(unittest.TestCase):
self.get = GetBibleReference()
def test_valid_reference(self):
expected_result = BookReference(book=1, chapter=1, verses=[2, 3, 4, 5, 6, 7])
expected_result = BookReference(book=1, chapter=1, verses=[2, 3, 4, 5, 6, 7], reference='Gen 1:2-7')
actual_result = self.get.ref('Gen 1:2-7', 'kjv')
self.assertEqual(actual_result, expected_result, "Failed to find 'Gen 1:2-7' book reference.")
def test_valid_reference_ch(self):
expected_result = BookReference(book=1, chapter=1, verses=[2, 3, 4, 5, 6, 7])
expected_result = BookReference(book=1, chapter=1, verses=[2, 3, 4, 5, 6, 7], reference='创世记1:2-7')
actual_result = self.get.ref('创世记1:2-7', 'cns')
self.assertEqual(actual_result, expected_result, "Failed to find '创世记1:2-7' book reference")
def test_valid_reference_missing_verse_ch(self):
expected_result = BookReference(book=1, chapter=1, verses=[2])
expected_result = BookReference(book=1, chapter=1, verses=[2], reference='创记 1:2-')
actual_result = self.get.ref('创记 1:2-', 'cus')
self.assertEqual(actual_result, expected_result, "Failed to find '创记 1:2-' book reference")
def test_valid_reference_missing_verse__ch(self):
expected_result = BookReference(book=1, chapter=1, verses=[5])
expected_result = BookReference(book=1, chapter=1, verses=[5], reference='创记 1:-5')
actual_result = self.get.ref('创记 1:-5', 'cus')
self.assertEqual(actual_result, expected_result, "Failed to find '创记 1:-5' book reference")
def test_valid_reference_ch_no_trans(self):
actual_result = self.get.ref('创世记')
expected_result = BookReference(book=1, chapter=1, verses=[1])
expected_result = BookReference(book=1, chapter=1, verses=[1], reference='创世记')
self.assertEqual(actual_result, expected_result, "Failed to find '创世记 1:1' book reference")
def test_valid_reference_ch_no__trans(self):
expected_result = BookReference(book=1, chapter=1, verses=[1])
expected_result = BookReference(book=1, chapter=1, verses=[1], reference='创记')
actual_result = self.get.ref('创记')
self.assertEqual(actual_result, expected_result, "Failed to find '创记 1:1' book reference")
def test_valid_1_john(self):
expected_result = BookReference(book=62, chapter=1, verses=[1])
expected_result = BookReference(book=62, chapter=1, verses=[1], reference='1 John')
actual_result = self.get.ref('1 John', 'kjv')
self.assertEqual(actual_result, expected_result, "Failed to find '1 John 1:1' book reference")
def test_valid_1_peter_ch(self):
actual_result = self.get.ref('彼得前书', 'cns')
expected_result = BookReference(book=60, chapter=1, verses=[1])
expected_result = BookReference(book=60, chapter=1, verses=[1], reference='彼得前书')
self.assertEqual(actual_result, expected_result, "Failed to find '彼得前书 1:1' book reference")
def test_valid_first_john(self):
expected_result = BookReference(book=62, chapter=3, verses=[16, 19, 20, 21])
expected_result = BookReference(book=62, chapter=3, verses=[16, 19, 20, 21], reference='First John 3:16,19-21')
actual_result = self.get.ref('First John 3:16,19-21', 'kjv')
self.assertEqual(actual_result, expected_result, "Failed to find 'First John 1:2-7' book reference.")
def test_valid_mismatch_nospace_call(self):
expected_result = BookReference(book=62, chapter=1, verses=[1])
expected_result = BookReference(book=62, chapter=1, verses=[1], reference='1Jn')
actual_result = self.get.ref('1Jn', 'aov')
self.assertEqual(actual_result, expected_result, "Failed to find '1Jn 1:1' book reference.")
def test_valid_mismatch_call(self):
expected_result = BookReference(book=62, chapter=5, verses=[1])
expected_result = BookReference(book=62, chapter=5, verses=[1], reference='1 John 5')
actual_result = self.get.ref('1 John 5', 'aov')
self.assertEqual(actual_result, expected_result, "Failed to find '1 John 5:1' book reference.")
@ -69,7 +69,7 @@ class TestGetBibleReference(unittest.TestCase):
self.assertEqual(str(actual.exception), expected_exception)
def test_nonexistent_translation(self):
expected_result = BookReference(book=1, chapter=1, verses=[1])
expected_result = BookReference(book=1, chapter=1, verses=[1], reference='Gen')
actual_result = self.get.ref('Gen', 'nonexistent')
self.assertEqual(actual_result, expected_result, "Failed to find 'Gen 1:1' book reference.")