Files
abogen/tests/test_conversion_request.py
T
Artem Akymenko e1e49e8a0f test: regression tests for conversion flow unification
Three new test files describing expected behavior before refactoring:
- test_conversion_planner.py: chapter parsing, voice markers, TTSContext, intro/outro, output paths, subtitles (30 tests)
- test_conversion_request.py: settings, context building, chapter selection, cancellation/logging protocols (15 tests)
- test_conversion_executor.py: synthesize_text, process_and_write_subtitles, full pipeline with fake backend/sink (12 tests)

All 1310 tests pass (1253 existing + 57 new).
2026-07-21 11:16:40 +03:00

195 lines
6.8 KiB
Python

"""Regression tests for ConversionRequest building.
These tests verify that both WebUI and PyQt adapters can produce
a valid ConversionRequest from their respective Job/thread state.
They serve as a specification for the adapter code that will be
created in Phase 2/3 of the refactor.
Currently these tests verify the EXISTING behavior by testing the
domain functions that the adapters will call. After the adapters
are created, these tests should be updated to test the adapters
directly.
"""
import pytest
from pathlib import Path
from unittest.mock import MagicMock
from abogen.domain.normalization import TTSContext
from abogen.domain.settings_core import settings_defaults
from abogen.domain.split_pattern import get_split_pattern
class TestConversionRequestBasics:
"""Verify that basic request parameters can be derived from settings."""
def test_settings_defaults_exist(self):
defaults = settings_defaults()
assert isinstance(defaults, dict)
assert "output_format" in defaults
assert "subtitle_format" in defaults
assert "save_mode" in defaults
assert "use_gpu" in defaults
assert "silence_between_chapters" in defaults
assert "merge_chapters_at_end" in defaults
def test_split_pattern_computation(self):
pattern = get_split_pattern("a", "Disabled")
assert isinstance(pattern, str)
assert len(pattern) > 0
def test_split_pattern_varies_by_subtitle_mode(self):
pattern_disabled = get_split_pattern("a", "Disabled")
pattern_sentence = get_split_pattern("a", "Sentence")
# Different modes should produce different patterns
assert isinstance(pattern_disabled, str)
assert isinstance(pattern_sentence, str)
class TestTTSContextBuilding:
"""Verify TTSContext can be built from settings parameters."""
def test_build_context_from_params(self):
ctx = TTSContext(
split_pattern=r"(?<=[.!?\-])\s+",
pronunciation_rules=None,
heteronym_rules=None,
normalization_overrides=None,
)
assert ctx.split_pattern
assert ctx.normalize("Hello world.") is not None
def test_build_context_with_compiled_rules(self):
from abogen.domain.pronunciation import compile_pronunciation_rules
rules = compile_pronunciation_rules([{"pattern": "test", "replacement": "Test"}])
ctx = TTSContext(
split_pattern=r"\n+",
pronunciation_rules=rules,
)
result = ctx.normalize("test text")
assert isinstance(result, str)
def test_usage_counter_tracking(self):
ctx = TTSContext()
ctx.usage_counter["token1"] = 0
ctx.normalize("Some text with token1")
# Usage counter should be passed through (may or may not increment
# depending on whether the token matches)
assert isinstance(ctx.usage_counter, dict)
class TestOutputDirectoryResolution:
"""Verify output directory can be resolved from parameters."""
def test_resolve_output_directory(self, tmp_path):
from abogen.domain.output_paths import resolve_output_directory
result = resolve_output_directory(
save_mode="Save next to input file",
stored_path=tmp_path / "test.txt",
output_folder=None,
desktop_dir=tmp_path,
user_output_path=None,
user_cache_outputs=tmp_path,
)
assert result is not None
assert isinstance(result, Path)
def test_resolve_output_with_explicit_folder(self, tmp_path):
from abogen.domain.output_paths import resolve_output_directory
custom_dir = tmp_path / "custom_output"
custom_dir.mkdir()
result = resolve_output_directory(
save_mode="Save to custom folder",
stored_path=tmp_path / "test.txt",
output_folder=str(custom_dir),
desktop_dir=tmp_path,
user_output_path=None,
user_cache_outputs=tmp_path,
)
assert result is not None
class TestChapterSelection:
"""Verify chapter selection logic works with various inputs."""
def test_auto_select_relevant_chapters(self):
from abogen.domain.file_type import auto_select_relevant_chapters
from abogen.text_extractor import ExtractedChapter
chapters = [
ExtractedChapter(title="Chapter 1", text="A" * 500),
ExtractedChapter(title="Chapter 2", text="B" * 50),
ExtractedChapter(title="Chapter 3", text="C" * 600),
]
result = auto_select_relevant_chapters(chapters, "txt")
# Should filter out short chapters
assert len(result.kept) >= 1
assert isinstance(result.skipped, list)
def test_auto_select_all_long_chapters(self):
from abogen.domain.file_type import auto_select_relevant_chapters
from abogen.text_extractor import ExtractedChapter
chapters = [
ExtractedChapter(title="Chapter 1", text="A" * 500),
ExtractedChapter(title="Chapter 2", text="B" * 500),
]
result = auto_select_relevant_chapters(chapters, "txt")
assert len(result.kept) == 2
assert len(result.skipped) == 0
def test_metadata_merge(self):
from abogen.domain.metadata_merge import merge_metadata
base = {"title": "Original Title", "author": "Author A"}
overrides = {"title": "New Title"}
result = merge_metadata(base, overrides)
assert result["title"] == "New Title"
assert result["author"] == "Author A"
class TestCancellationProtocol:
"""Verify cancellation mechanism can be implemented as a callback."""
def test_cancellation_flag_check(self):
class FakeJob:
def __init__(self):
self.cancel_requested = False
job = FakeJob()
check = lambda: job.cancel_requested
assert check() is False
job.cancel_requested = True
assert check() is True
def test_cancellation_exception_pattern(self):
"""WebUI uses exception-based cancellation."""
class JobCancelled(Exception):
pass
def canceller():
raise JobCancelled()
with pytest.raises(JobCancelled):
canceller()
class TestLoggingProtocol:
"""Verify logging can be abstracted as a callback."""
def test_log_callback(self):
logs = []
def log_fn(msg, level="info"):
logs.append((msg, level))
log_fn("Test message", "info")
assert len(logs) == 1
assert logs[0] == ("Test message", "info")
def test_progress_callback(self):
progress_calls = []
def progress_fn(processed, total, etr):
progress_calls.append((processed, total, etr))
progress_fn(100, 1000, "0:05:00")
assert len(progress_calls) == 1
assert progress_calls[0] == (100, 1000, "0:05:00")