diff options
Diffstat (limited to 'tests/test_tts.py')
| -rw-r--r-- | tests/test_tts.py | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/tests/test_tts.py b/tests/test_tts.py new file mode 100644 index 0000000..b605daa --- /dev/null +++ b/tests/test_tts.py @@ -0,0 +1,155 @@ +"""Tests for the Qwen TTS client wrapper (language handling and payloads).""" + +import tempfile +import unittest +from pathlib import Path +from unittest.mock import MagicMock, patch + +from converter import config +from converter.tts import QwenTTSClient, normalize_language + + +class NormalizeLanguageTests(unittest.TestCase): + def test_display_names_case_insensitive(self): + self.assertEqual(normalize_language("english"), "English") + self.assertEqual(normalize_language("ENGLISH"), "English") + self.assertEqual(normalize_language(" Japanese "), "Japanese") + + def test_auto_accepted(self): + self.assertEqual(normalize_language("auto"), "Auto") + self.assertEqual(normalize_language("Auto"), "Auto") + + def test_iso_aliases(self): + self.assertEqual(normalize_language("en"), "English") + self.assertEqual(normalize_language("ja"), "Japanese") + self.assertEqual(normalize_language("zh"), "Chinese") + self.assertEqual(normalize_language("ko"), "Korean") + self.assertEqual(normalize_language("de"), "German") + self.assertEqual(normalize_language("fr"), "French") + self.assertEqual(normalize_language("ru"), "Russian") + self.assertEqual(normalize_language("pt"), "Portuguese") + self.assertEqual(normalize_language("es"), "Spanish") + self.assertEqual(normalize_language("it"), "Italian") + + def test_all_supported_languages_round_trip(self): + for name in config.TTS_LANGUAGES: + self.assertEqual(normalize_language(name.lower()), name) + + def test_unknown_language_rejected_with_guidance(self): + with self.assertRaises(ValueError) as ctx: + normalize_language("klingon") + message = str(ctx.exception) + self.assertIn("klingon", message) + self.assertIn("English", message) + + def test_none_and_empty_rejected(self): + with self.assertRaises(ValueError): + normalize_language(None) + with self.assertRaises(ValueError): + normalize_language(" ") + + +class QwenTTSClientLanguageTests(unittest.TestCase): + """Language validation and defaults, without touching the network.""" + + def _make_client(self, **kwargs): + with patch.object(QwenTTSClient, "_connect"): + return QwenTTSClient(**kwargs) + + def test_default_follows_config_for_each_mode(self): + custom = self._make_client(voice_mode=config.VOICE_MODE_CUSTOM) + self.assertEqual(custom.language, config.CUSTOM_VOICE_LANGUAGE) + clone = self._make_client(voice_mode=config.VOICE_MODE_CLONE, + voice_clone_ref_audio="ref.wav") + self.assertEqual(clone.language, config.VOICE_CLONE_LANGUAGE) + + def test_explicit_language_normalized(self): + client = self._make_client(voice_mode=config.VOICE_MODE_CUSTOM, language="ja") + self.assertEqual(client.language, "Japanese") + + def test_invalid_language_fails_before_connect(self): + with patch.object(QwenTTSClient, "_connect") as mock_connect: + with self.assertRaises(ValueError): + QwenTTSClient(language="klingon") + mock_connect.assert_not_called() + + +class PayloadLanguageTests(unittest.TestCase): + """The language must reach the API payload in every endpoint variant.""" + + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.ref_audio = Path(self._tmp.name) / "reference.wav" + self.ref_audio.write_bytes(b"x") + + def tearDown(self): + self._tmp.cleanup() + + def _custom_client(self, language, endpoint, api_info=None): + client = QwenTTSClient.__new__(QwenTTSClient) + client.voice_mode = config.VOICE_MODE_CUSTOM + client.language = language + client.api_info = api_info if api_info is not None else { + "named_endpoints": {endpoint: {}} + } + client.client = MagicMock() + return client + + def _clone_client(self, language, endpoint, api_info=None, ref_text="hello"): + client = QwenTTSClient.__new__(QwenTTSClient) + client.voice_mode = config.VOICE_MODE_CLONE + client.language = language + client.voice_clone_ref_audio = str(self.ref_audio) + client.voice_clone_ref_text = ref_text + client.clone_api_info = api_info if api_info is not None else { + "named_endpoints": {endpoint: {}} + } + client.clone_client = MagicMock() + client._ref_audio_filedata = {"dummy": "payload"} + return client + + def test_custom_voice_run_instruct_uses_language(self): + client = self._custom_client("Japanese", "/run_instruct") + client._generate_custom_voice("text") + kwargs = client.client.predict.call_args.kwargs + self.assertEqual(kwargs["lang_disp"], "Japanese") + + def test_custom_voice_alt_endpoint_uses_language(self): + client = self._custom_client("French", "/run_custom_voice") + client._generate_custom_voice("text") + kwargs = client.client.predict.call_args.kwargs + self.assertEqual(kwargs["language"], "French") + + def test_voice_clone_run_voice_clone_uses_language(self): + client = self._clone_client("Japanese", "/run_voice_clone") + client._generate_voice_clone("text") + kwargs = client.clone_client.predict.call_args.kwargs + self.assertEqual(kwargs["lang_disp"], "Japanese") + + def test_voice_clone_alt_endpoint_uses_language(self): + client = self._clone_client("Korean", "/generate_voice_clone") + client._generate_voice_clone("text") + kwargs = client.clone_client.predict.call_args.kwargs + self.assertEqual(kwargs["language"], "Korean") + + def test_voice_clone_alt_endpoint_includes_optional_params(self): + api_info = { + "named_endpoints": { + "/generate_voice_clone": { + "parameters": [ + {"parameter_name": "model_size"}, + {"parameter_name": "seed"}, + ] + } + } + } + client = self._clone_client("English", "/generate_voice_clone", api_info=api_info) + client._generate_voice_clone("text") + kwargs = client.clone_client.predict.call_args.kwargs + self.assertEqual(kwargs["model_size"], config.VOICE_CLONE_MODEL_SIZE) + self.assertEqual(kwargs["seed"], config.VOICE_CLONE_SEED) + self.assertNotIn("max_chunk_chars", kwargs) + + +if __name__ == "__main__": + unittest.main() |
