Files
praxis/tests/test_tts_adapters.py
Praxis CI fbd6602814 docs(milestone): complete v0.1 foundation
---ci---
phase: 0
milestone: v0.1
status: complete
---/ci---
2026-08-01 13:32:48 +00:00

101 lines
3.0 KiB
Python

"""Unit tests for the TTS adapters (TASK-02-02).
Both adapters must pass with a mock stream and degrade gracefully when keys /
voice models are absent. PRAXIS_TTS selection must route to the right adapter.
"""
from __future__ import annotations
import asyncio
from unittest.mock import patch
import pytest
from server.services.base import TTSProvider, TTSResult
from server.tts.cartesia_tts import CartesiaTTS
from server.tts.piper_tts import PiperTTS
def test_cartesia_selectable_via_env(monkeypatch):
"""PRAXIS_TTS=cartesia selects CartesiaTTS."""
from server.services.registry import get_tts
monkeypatch.setenv("PRAXIS_TTS", "cartesia")
monkeypatch.setenv("CARTESIA_API_KEY", "test-key")
get_tts.cache_clear()
tts = get_tts()
assert isinstance(tts, CartesiaTTS)
assert tts.name == "cartesia"
assert tts.voice_id # has a default voice id
def test_piper_selectable_via_env(monkeypatch):
"""PRAXIS_TTS=piper selects PiperTTS."""
from server.services.registry import get_tts
monkeypatch.setenv("PRAXIS_TTS", "piper")
get_tts.cache_clear()
tts = get_tts()
assert isinstance(tts, PiperTTS)
assert tts.name == "piper"
def test_cartesia_missing_key_no_audio():
"""Cartesia with no API key yields no audio but doesn't crash (graceful)."""
tts = CartesiaTTS(api_key="")
async def _run():
chunks = [c async for c in tts.synthesize("hello")]
return chunks
chunks = asyncio.run(_run())
assert chunks == []
def test_cartesia_synthesize_all_with_mock(monkeypatch):
"""Cartesia.synthesize_all returns audio + TTSResult with a mocked stream."""
tts = CartesiaTTS(api_key="test-key", voice_id="v1")
async def _fake_stream(text):
yield b"\x00\x01"
yield b"\x02\x03"
monkeypatch.setattr(tts, "synthesize", _fake_stream)
audio, result = asyncio.run(tts.synthesize_all("hi"))
assert audio == b"\x00\x01\x02\x03"
assert result.chars == 2
assert result.voice_id == "v1"
assert result.first_audio_ms is not None
def test_piper_missing_model_no_audio():
"""Piper with no voice model yields no audio but doesn't crash (graceful)."""
tts = PiperTTS(voice_model="")
async def _run():
chunks = [c async for c in tts.synthesize("hello")]
return chunks
chunks = asyncio.run(_run())
assert chunks == []
def test_piper_synthesize_all_with_mock(monkeypatch):
"""Piper.synthesize_all returns audio + TTSResult with a mocked stream."""
tts = PiperTTS(voice_model="/nonexistent", voice_id="p1")
async def _fake_stream(text):
yield b"\x10\x20"
yield b"\x30\x40"
monkeypatch.setattr(tts, "synthesize", _fake_stream)
audio, result = asyncio.run(tts.synthesize_all("hi"))
assert audio == b"\x10\x20\x30\x40"
assert result.chars == 2
assert result.voice_id == "p1"
def test_both_adapters_are_ttsprovider():
"""Both adapters satisfy the TTSProvider ABC."""
assert isinstance(CartesiaTTS(api_key="k"), TTSProvider)
assert isinstance(PiperTTS(), TTSProvider)