Files
abogen/tests/contracts/test_session_contract.py
T
Artem Akymenko d8fcfb1cce chore: normalize line endings to LF, add .gitattributes
- Add .gitattributes with text=auto eol=lf for all text files
- Renormalize all files in index to LF line endings
- Fixes massive whitespace-only diffs between main and feature branch
2026-07-12 16:20:42 +03:00

136 lines
4.6 KiB
Python

"""Contract tests for EngineSession protocol.
These tests verify that EngineSession implementations satisfy the architectural requirements:
- synthesize() returns SynthesizedAudio
- dispose() is idempotent
- After dispose(), synthesize() raises EngineError
- Session remains usable after synthesize() failure
"""
import pytest
from abogen.tts_plugin.engine import EngineSession
from abogen.tts_plugin.errors import EngineError
from abogen.tts_plugin.types import (
AudioFormat,
Duration,
ParameterValues,
SynthesisRequest,
SynthesizedAudio,
VoiceSelection,
)
from .conftest import FakeEngineSession
class TestEngineSessionProtocolContract:
"""Contract tests for the EngineSession protocol itself."""
def test_engine_session_is_protocol(self) -> None:
assert hasattr(EngineSession, "__protocol_attrs__")
def test_fake_session_satisfies_protocol(self) -> None:
session = FakeEngineSession()
assert isinstance(session, EngineSession)
class TestSessionSynthesizeContract:
"""Contract tests for EngineSession.synthesize()."""
def test_synthesize_returns_synthesized_audio(self) -> None:
session = FakeEngineSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
result = session.synthesize(request)
assert isinstance(result, SynthesizedAudio)
def test_synthesize_returns_valid_audio_data(self) -> None:
session = FakeEngineSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
result = session.synthesize(request)
assert isinstance(result.data, bytes)
assert len(result.data) > 0
assert isinstance(result.format, AudioFormat)
assert isinstance(result.duration, Duration)
class TestSessionDisposeContract:
"""Contract tests for EngineSession.dispose()."""
def test_dispose_is_idempotent(self) -> None:
"""Architecture spec: dispose() is idempotent."""
session = FakeEngineSession()
session.dispose()
session.dispose() # Should not raise
def test_dispose_never_raises(self) -> None:
"""Architecture spec: dispose() never raises exceptions."""
session = FakeEngineSession()
session.dispose() # Should not raise
class TestSessionAfterDisposeContract:
"""Contract tests for behavior after dispose()."""
def test_synthesize_after_dispose_raises(self) -> None:
"""Architecture spec: After dispose(), all methods except dispose() raise EngineError."""
session = FakeEngineSession()
session.dispose()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
with pytest.raises(EngineError):
session.synthesize(request)
class TestSessionLifecycleContract:
"""Contract tests for EngineSession lifecycle."""
def test_full_lifecycle(self) -> None:
"""Test complete session lifecycle: create -> synthesize -> dispose."""
session = FakeEngineSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
# Synthesize
result = session.synthesize(request)
assert isinstance(result, SynthesizedAudio)
# Dispose
session.dispose()
def test_multiple_synthesize_before_dispose(self) -> None:
"""Architecture spec: Session remains usable after synthesize() failure."""
session = FakeEngineSession()
request = SynthesisRequest(
text="Hello",
voice=VoiceSelection(source="builtin", key="af_nova"),
parameters=ParameterValues(),
format=AudioFormat(mime="audio/wav", extension="wav"),
)
# Multiple synthesize calls
result1 = session.synthesize(request)
result2 = session.synthesize(request)
assert isinstance(result1, SynthesizedAudio)
assert isinstance(result2, SynthesizedAudio)
# Dispose
session.dispose()