mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 19:50:59 +02:00
test: adapter field mapping + import layering tests
- 28 adapter tests (WebUI + PyQt): field mapping, events, provider, resolver - 15 import/layering tests: no PyQt/WebUI in app layer, all models importable
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
"""Import/layering tests for the conversion flow unification.
|
||||
|
||||
Verifies that:
|
||||
- Application layer does not import from PyQt or WebUI
|
||||
- Adapters import from application/domain (not the other way around)
|
||||
- All application models are importable without GUI/Flask side effects
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# ─── Application layer imports ─────────────────────────────────────
|
||||
|
||||
|
||||
class TestApplicationLayerImports:
|
||||
"""Verify application layer has no PyQt/WebUI imports."""
|
||||
|
||||
APPLICATION_DIR = Path(__file__).parent.parent / "abogen" / "application"
|
||||
|
||||
def _get_python_files(self):
|
||||
"""Get all Python files in the application directory."""
|
||||
return list(self.APPLICATION_DIR.glob("*.py"))
|
||||
|
||||
def test_no_pyqt_imports(self):
|
||||
"""Application layer must not import from abogen.pyqt."""
|
||||
import re
|
||||
|
||||
forbidden = re.compile(r"from\s+abogen\.pyqt|import\s+abogen\.pyqt")
|
||||
violations = []
|
||||
|
||||
for py_file in self._get_python_files():
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
if forbidden.search(content):
|
||||
violations.append(py_file.name)
|
||||
|
||||
assert not violations, f"Application files import from PyQt: {violations}"
|
||||
|
||||
def test_no_webui_imports(self):
|
||||
"""Application layer must not import from abogen.webui."""
|
||||
import re
|
||||
|
||||
forbidden = re.compile(r"from\s+abogen\.webui|import\s+abogen\.webui")
|
||||
violations = []
|
||||
|
||||
for py_file in self._get_python_files():
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
if forbidden.search(content):
|
||||
violations.append(py_file.name)
|
||||
|
||||
assert not violations, f"Application files import from WebUI: {violations}"
|
||||
|
||||
def test_no_flask_imports(self):
|
||||
"""Application layer must not import Flask."""
|
||||
import re
|
||||
|
||||
forbidden = re.compile(r"from\s+flask|import\s+flask")
|
||||
violations = []
|
||||
|
||||
for py_file in self._get_python_files():
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
if forbidden.search(content):
|
||||
violations.append(py_file.name)
|
||||
|
||||
assert not violations, f"Application files import Flask: {violations}"
|
||||
|
||||
def test_no_qthread_imports(self):
|
||||
"""Application layer must not import QThread."""
|
||||
import re
|
||||
|
||||
forbidden = re.compile(r"from\s+PyQt6|import\s+PyQt6")
|
||||
violations = []
|
||||
|
||||
for py_file in self._get_python_files():
|
||||
content = py_file.read_text(encoding="utf-8")
|
||||
if forbidden.search(content):
|
||||
violations.append(py_file.name)
|
||||
|
||||
assert not violations, f"Application files import PyQt6: {violations}"
|
||||
|
||||
|
||||
class TestApplicationModelsImportable:
|
||||
"""Verify application models are importable without side effects."""
|
||||
|
||||
def test_conversion_request_importable(self):
|
||||
from abogen.application.conversion_request import ConversionRequest
|
||||
|
||||
assert ConversionRequest is not None
|
||||
|
||||
def test_conversion_models_importable(self):
|
||||
from abogen.application.conversion_models import (
|
||||
ChapterPlan,
|
||||
ConversionPlan,
|
||||
IntroOutroSpec,
|
||||
OutputLayout,
|
||||
SegmentPlan,
|
||||
)
|
||||
|
||||
assert all(
|
||||
cls is not None
|
||||
for cls in [ChapterPlan, ConversionPlan, IntroOutroSpec, OutputLayout, SegmentPlan]
|
||||
)
|
||||
|
||||
def test_conversion_result_importable(self):
|
||||
from abogen.application.conversion_result import ConversionError, ConversionResult
|
||||
|
||||
assert ConversionResult is not None
|
||||
assert ConversionError is not None
|
||||
|
||||
def test_conversion_ports_importable(self):
|
||||
from abogen.application.conversion_ports import (
|
||||
AudioSink,
|
||||
ConversionEvents,
|
||||
PipelineProvider,
|
||||
ResolvedVoice,
|
||||
SubtitleWriter,
|
||||
VoiceResolver,
|
||||
)
|
||||
|
||||
assert all(
|
||||
cls is not None
|
||||
for cls in [
|
||||
AudioSink,
|
||||
ConversionEvents,
|
||||
PipelineProvider,
|
||||
ResolvedVoice,
|
||||
SubtitleWriter,
|
||||
VoiceResolver,
|
||||
]
|
||||
)
|
||||
|
||||
def test_output_layout_service_importable(self):
|
||||
from abogen.application.output_layout_service import (
|
||||
resolve_chapter_path,
|
||||
resolve_merged_path,
|
||||
resolve_output_layout,
|
||||
should_merge_output,
|
||||
)
|
||||
|
||||
assert all(
|
||||
fn is not None
|
||||
for fn in [resolve_chapter_path, resolve_merged_path, resolve_output_layout, should_merge_output]
|
||||
)
|
||||
|
||||
def test_conversion_planner_importable(self):
|
||||
from abogen.application.conversion_planner import build_conversion_plan
|
||||
|
||||
assert build_conversion_plan is not None
|
||||
|
||||
def test_conversion_executor_importable(self):
|
||||
from abogen.application.conversion_executor import execute_conversion
|
||||
|
||||
assert execute_conversion is not None
|
||||
|
||||
def test_conversion_service_importable(self):
|
||||
from abogen.application.conversion_service import run_conversion
|
||||
|
||||
assert run_conversion is not None
|
||||
|
||||
|
||||
class TestAdapterImports:
|
||||
"""Verify adapters import from application/domain correctly."""
|
||||
|
||||
def test_webui_adapter_imports_application(self):
|
||||
from abogen.webui.conversion_adapter import (
|
||||
WebJobEvents,
|
||||
WebPipelineProvider,
|
||||
WebVoiceResolver,
|
||||
build_conversion_request_from_job,
|
||||
)
|
||||
|
||||
assert all(
|
||||
cls is not None
|
||||
for cls in [
|
||||
WebJobEvents,
|
||||
WebPipelineProvider,
|
||||
WebVoiceResolver,
|
||||
build_conversion_request_from_job,
|
||||
]
|
||||
)
|
||||
|
||||
def test_pyqt_adapter_imports_application(self):
|
||||
from abogen.pyqt.conversion_adapter import (
|
||||
PyQtEvents,
|
||||
PyQtPipelineProvider,
|
||||
PyQtVoiceResolver,
|
||||
build_conversion_request_from_thread,
|
||||
)
|
||||
|
||||
assert all(
|
||||
cls is not None
|
||||
for cls in [
|
||||
PyQtEvents,
|
||||
PyQtPipelineProvider,
|
||||
PyQtVoiceResolver,
|
||||
build_conversion_request_from_thread,
|
||||
]
|
||||
)
|
||||
|
||||
def test_webui_adapter_does_not_import_pyqt(self):
|
||||
"""WebUI adapter must not import from PyQt."""
|
||||
import re
|
||||
|
||||
adapter_path = Path(__file__).parent.parent / "abogen" / "webui" / "conversion_adapter.py"
|
||||
content = adapter_path.read_text(encoding="utf-8")
|
||||
|
||||
forbidden = re.compile(r"from\s+abogen\.pyqt|import\s+abogen\.pyqt")
|
||||
assert not forbidden.search(content), "WebUI adapter imports from PyQt"
|
||||
|
||||
def test_pyqt_adapter_does_not_import_webui(self):
|
||||
"""PyQt adapter must not import from WebUI."""
|
||||
import re
|
||||
|
||||
adapter_path = Path(__file__).parent.parent / "abogen" / "pyqt" / "conversion_adapter.py"
|
||||
content = adapter_path.read_text(encoding="utf-8")
|
||||
|
||||
forbidden = re.compile(r"from\s+abogen\.webui|import\s+abogen\.webui")
|
||||
assert not forbidden.search(content), "PyQt adapter imports from WebUI"
|
||||
Reference in New Issue
Block a user