import io
import zipfile
from unittest.mock import patch
import pytest
from nodes.epub_loader import OmniVoiceEpubLoader
# Capture the real ZipFile class BEFORE any patching so epub_opener can use it
# without hitting the patch and causing infinite recursion.
_RealZipFile = zipfile.ZipFile
_CONTAINER_XML = """
Hello world
"), ("Chapter One", "Body here
")]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): text, chapter_list = OmniVoiceEpubLoader().load_epub('/fake.epub', 1, 2) assert "Hello world" in text assert "Body here" in text assert "---" in text assert len(chapter_list.strip().splitlines()) == 2 def test_chapter_range_single(): epub = make_fake_epub([("One", "First
"), ("Two", "Second
"), ("Three", "Third
")]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): text, _ = OmniVoiceEpubLoader().load_epub('/fake.epub', 2, 2) assert "Second" in text assert "First" not in text assert "Third" not in text def test_chapter_list_contains_all(): epub = make_fake_epub([("A", ""), ("B", ""), ("C", "")]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): _, chapter_list = OmniVoiceEpubLoader().load_epub('/fake.epub', 2, 2) lines = chapter_list.strip().splitlines() assert len(lines) == 3 assert lines[0].startswith("1.") assert lines[2].startswith("3.") def test_range_clamping_high(): epub = make_fake_epub([("A", "aaa
"), ("B", "bbb
")]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): text, _ = OmniVoiceEpubLoader().load_epub('/fake.epub', 1, 99) assert "aaa" in text and "bbb" in text def test_range_clamping_end_below_start(): epub = make_fake_epub([("A", "aaa
"), ("B", "bbb
")]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): text, _ = OmniVoiceEpubLoader().load_epub('/fake.epub', 2, 1) assert "bbb" in text assert "aaa" not in text def test_missing_title_fallback(): buf = io.BytesIO() with zipfile.ZipFile(buf, 'w') as z: z.writestr('mimetype', 'application/epub+zip') z.writestr('META-INF/container.xml', _CONTAINER_XML) z.writestr('OEBPS/content.opf', """No title here
') buf.seek(0) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(buf.read())): _, chapter_list = OmniVoiceEpubLoader().load_epub('/fake.epub', 1, 1) assert "1. Chapter 1" in chapter_list def test_script_style_stripped(): epub = make_fake_epub([("Test", 'clean
')]) with patch('nodes.epub_loader.zipfile.ZipFile', side_effect=epub_opener(epub)): text, _ = OmniVoiceEpubLoader().load_epub('/fake.epub', 1, 1) assert "alert" not in text assert "color" not in text assert "clean" in text