from __future__ import annotations import json from pathlib import Path from typer.testing import CliRunner from reyna_cli.cli import app from reyna_cli.voice_direct import PocketTTSDirectClient, UnifiedVoiceClient runner = CliRunner() def test_unified_voice_config_keeps_three_named_engines_separate(tmp_path): status = UnifiedVoiceClient(pocket_voice_state=tmp_path / "jv_pocket.pt").config_status() assert set(status["engines"]) == {"kokoro", "pocket", "qwen3-tts"} assert status["engines"]["pocket"]["voice_state"] == str(tmp_path / "jv_pocket.pt") assert status["engines"]["qwen3-tts"]["model_id"] == "mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16" def test_pocket_validation_requires_exact_custom_state(tmp_path): client = PocketTTSDirectClient(voice_state=tmp_path / "missing.pt") try: client.validate_generate("hello") except FileNotFoundError as exc: assert "Pocket voice state" in str(exc) else: raise AssertionError("missing Pocket state must not silently fall back") def test_unified_voice_cli_exposes_explicit_engine_selection(): result = runner.invoke(app, ["local-services", "voice", "generate", "--help"]) assert result.exit_code == 0, result.output assert "--engine" in result.output assert "kokoro" in result.output assert "pocket" in result.output assert "qwen3-tts" in result.output def test_unified_voice_config_cli_is_offline(monkeypatch): result = runner.invoke(app, ["local-services", "voice", "config", "--json"]) assert result.exit_code == 0, result.output assert set(json.loads(result.stdout)["result"]["engines"]) == {"kokoro", "pocket", "qwen3-tts"}