Files
abogen/tests/contracts/conftest.py
T
Artem Akymenko c094b94704 feat(tts-plugin): complete Plugin Architecture refactor
- Normalize Pipeline public API: create_pipeline(plugin_id, *, lang_code, device)
- EngineConfig: add lang_code field per Architecture Amendment #1
- Kokoro plugin reads config.lang_code (fixes functional regression)
- Static voice catalog in PluginManifest.voices (None = dynamic/VoiceLister)
- get_voices() reads from manifest without creating Engine
- Remove dead kwargs (sample_rate, auto_download, total_steps) from SuperTonic
- Clean up unused imports and dead code in engine implementations
- Fix test expectations for VoiceLister (mock overrides)
- Add clear_preview_pipelines() for resource management
2026-07-12 16:20:20 +03:00

232 lines
6.4 KiB
Python

"""Shared fixtures and stubs for contract tests.
This module provides minimal stub implementations that satisfy the public API
for testing purposes. These stubs do NOT contain real business logic.
"""
from __future__ import annotations
import logging
from pathlib import Path
from typing import Iterator
import pytest
from abogen.tts_plugin.engine import Engine, EngineSession
from abogen.tts_plugin.host_context import HostContext
from abogen.tts_plugin.types import (
AudioFormat,
Duration,
EngineConfig,
ParameterValues,
SynthesisRequest,
SynthesizedAudio,
VoiceSelection,
)
class FakeHttpClient:
"""Stub HTTP client that satisfies the HttpClient protocol."""
def get(self, url: str, **kwargs: object) -> object:
return None
def post(self, url: str, **kwargs: object) -> object:
return None
class FakeEngineSession:
"""Stub EngineSession for testing protocol compliance."""
def __init__(self) -> None:
self._disposed = False
def synthesize(self, request: SynthesisRequest) -> SynthesizedAudio:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Session disposed")
return SynthesizedAudio(
data=b"\x00" * 100,
format=AudioFormat(mime="audio/wav", extension="wav"),
duration=Duration(seconds=1.0),
)
def dispose(self) -> None:
self._disposed = True
class FakeStreamingSession:
"""Stub EngineSession with StreamingSynthesizer capability."""
def __init__(self) -> None:
self._disposed = False
def synthesize(self, request: SynthesisRequest) -> SynthesizedAudio:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Session disposed")
return SynthesizedAudio(
data=b"\x00" * 100,
format=AudioFormat(mime="audio/wav", extension="wav"),
duration=Duration(seconds=1.0),
)
def synthesizeStream(self, request: SynthesisRequest) -> Iterator[bytes]:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Session disposed")
for i in range(3):
yield b"\x00" * 50
def dispose(self) -> None:
self._disposed = True
class FakeCancelableSession:
"""Stub EngineSession with CancelableSession capability."""
def __init__(self) -> None:
self._disposed = False
self._cancelled = False
def synthesize(self, request: SynthesisRequest) -> SynthesizedAudio:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Session disposed")
if self._cancelled:
from abogen.tts_plugin.errors import CancelledError
raise CancelledError("Cancelled")
return SynthesizedAudio(
data=b"\x00" * 100,
format=AudioFormat(mime="audio/wav", extension="wav"),
duration=Duration(seconds=1.0),
)
def cancel(self) -> None:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Session disposed")
self._cancelled = True
def dispose(self) -> None:
self._disposed = True
class FakeEngine:
"""Stub Engine for testing protocol compliance."""
def __init__(self, session_class: type = FakeEngineSession) -> None:
self._disposed = False
self._session_class = session_class
def createSession(self) -> EngineSession:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Engine disposed")
return self._session_class()
def dispose(self) -> None:
self._disposed = True
class FakeVoiceListerEngine:
"""Stub Engine that also implements VoiceLister."""
def __init__(self) -> None:
self._disposed = False
def createSession(self) -> EngineSession:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Engine disposed")
return FakeEngineSession()
def listVoices(self, sourceId: str) -> list:
from abogen.tts_plugin.manifest import VoiceManifest
return [
VoiceManifest(id="voice1", name="Voice 1", tags=("en",)),
VoiceManifest(id="voice2", name="Voice 2", tags=("es",)),
]
def dispose(self) -> None:
self._disposed = True
class FakePreviewEngine:
"""Stub Engine that also implements PreviewGenerator."""
def __init__(self) -> None:
self._disposed = False
def createSession(self) -> EngineSession:
if self._disposed:
from abogen.tts_plugin.errors import EngineError
raise EngineError("Engine disposed")
return FakeEngineSession()
def generatePreview(self, voice: VoiceSelection, text: str) -> SynthesizedAudio:
return SynthesizedAudio(
data=b"\x00" * 50,
format=AudioFormat(mime="audio/wav", extension="wav"),
duration=Duration(seconds=0.5),
)
def dispose(self) -> None:
self._disposed = True
@pytest.fixture
def fake_http_client() -> FakeHttpClient:
return FakeHttpClient()
@pytest.fixture
def host_context(tmp_path: Path, fake_http_client: FakeHttpClient) -> HostContext:
return HostContext(
config_dir=tmp_path,
logger=logging.getLogger("test"),
http_client=fake_http_client,
)
@pytest.fixture
def fake_engine() -> FakeEngine:
return FakeEngine()
@pytest.fixture
def fake_session() -> FakeEngineSession:
return FakeEngineSession()
@pytest.fixture
def default_voice() -> VoiceSelection:
return VoiceSelection(source="builtin", key="af_nova")
@pytest.fixture
def default_format() -> AudioFormat:
return AudioFormat(mime="audio/wav", extension="wav")
@pytest.fixture
def default_request(
default_voice: VoiceSelection, default_format: AudioFormat
) -> SynthesisRequest:
return SynthesisRequest(
text="Hello, world!",
voice=default_voice,
parameters=ParameterValues(values={}),
format=default_format,
)