first commit
This commit is contained in:
1
tests/config/__init__.py
Normal file
1
tests/config/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
116
tests/config/test_azure_speech.py
Normal file
116
tests/config/test_azure_speech.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from app.livekit.azure_speech import resolve_azure_speech_tts_config
|
||||
|
||||
|
||||
class AzureSpeechTTSTests(unittest.TestCase):
|
||||
def test_resolve_uses_region_voice_and_optional_language(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config(
|
||||
{},
|
||||
environ={
|
||||
"AZURE_SPEECH_KEY": "key-123",
|
||||
"AZURE_SPEECH_REGION": "brazilsouth",
|
||||
"AZURE_SPEECH_VOICE": "pt-BR-FranciscaNeural",
|
||||
"AZURE_SPEECH_LANGUAGE": "pt-BR",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(missing, [])
|
||||
self.assertEqual(config["speech_key"], "key-123")
|
||||
self.assertEqual(config["speech_region"], "brazilsouth")
|
||||
self.assertIsNone(config["speech_endpoint"])
|
||||
self.assertEqual(config["voice"], "pt-BR-FranciscaNeural")
|
||||
self.assertEqual(config["language"], "pt-BR")
|
||||
self.assertIsNone(config["deployment_id"])
|
||||
|
||||
def test_resolve_keeps_explicit_custom_endpoint_for_standard_voice(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config(
|
||||
{},
|
||||
environ={
|
||||
"AZURE_SPEECH_KEY": "key-123",
|
||||
"AZURE_SPEECH_REGION": "brazilsouth",
|
||||
"AZURE_SPEECH_ENDPOINT": "https://speech.example.cognitiveservices.azure.com/",
|
||||
"AZURE_SPEECH_VOICE": "pt-BR-FranciscaNeural",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(missing, [])
|
||||
self.assertEqual(config["speech_region"], "brazilsouth")
|
||||
self.assertEqual(
|
||||
config["speech_endpoint"],
|
||||
"https://speech.example.cognitiveservices.azure.com/tts/cognitiveservices/v1",
|
||||
)
|
||||
|
||||
def test_resolve_prefers_endpoint_and_maps_legacy_override_names(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config(
|
||||
{
|
||||
"voice_id": "pt-BR-FranciscaNeural",
|
||||
"model_id": "deployment-42",
|
||||
},
|
||||
environ={
|
||||
"AZURE_SPEECH_KEY": "key-123",
|
||||
"AZURE_SPEECH_REGION": "brazilsouth",
|
||||
"AZURE_SPEECH_ENDPOINT": "https://speech.example.cognitiveservices.azure.com/",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(missing, [])
|
||||
self.assertEqual(config["deployment_id"], "deployment-42")
|
||||
self.assertEqual(
|
||||
config["speech_endpoint"],
|
||||
"https://speech.example.cognitiveservices.azure.com/voice/cognitiveservices/v1",
|
||||
)
|
||||
self.assertEqual(config["speech_region"], "brazilsouth")
|
||||
|
||||
def test_resolve_accepts_host_alias_and_auth_token(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config(
|
||||
{},
|
||||
environ={
|
||||
"AZURE_SPEECH_AUTH_TOKEN": "token-123",
|
||||
"AZURE_SPEECH_HOST": "https://speech.example.cognitiveservices.azure.com/",
|
||||
"AZURE_SPEECH_VOICE": "pt-BR-FranciscaNeural",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(missing, [])
|
||||
self.assertIsNone(config["speech_key"])
|
||||
self.assertEqual(config["speech_auth_token"], "token-123")
|
||||
self.assertEqual(
|
||||
config["speech_endpoint"],
|
||||
"https://speech.example.cognitiveservices.azure.com/tts/cognitiveservices/v1",
|
||||
)
|
||||
|
||||
def test_resolve_keeps_full_endpoint_path(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config(
|
||||
{},
|
||||
environ={
|
||||
"AZURE_SPEECH_KEY": "key-123",
|
||||
"AZURE_SPEECH_ENDPOINT": "https://speech.example.cognitiveservices.azure.com/tts/cognitiveservices/v1",
|
||||
"AZURE_SPEECH_VOICE": "pt-BR-FranciscaNeural",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(missing, [])
|
||||
self.assertEqual(
|
||||
config["speech_endpoint"],
|
||||
"https://speech.example.cognitiveservices.azure.com/tts/cognitiveservices/v1",
|
||||
)
|
||||
|
||||
def test_resolve_reports_missing_required_settings(self) -> None:
|
||||
config, missing = resolve_azure_speech_tts_config({}, environ={})
|
||||
|
||||
self.assertEqual(config, {})
|
||||
self.assertEqual(
|
||||
missing,
|
||||
[
|
||||
"AZURE_SPEECH_VOICE",
|
||||
"AZURE_SPEECH_ENDPOINT|AZURE_SPEECH_HOST|AZURE_SPEECH_REGION",
|
||||
"AZURE_SPEECH_KEY|AZURE_SPEECH_AUTH_TOKEN",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
254
tests/config/test_call_config.py
Normal file
254
tests/config/test_call_config.py
Normal file
@@ -0,0 +1,254 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from app.livekit.call_config import (
|
||||
normalize_call_config,
|
||||
resolve_fake_agent_overrides,
|
||||
resolve_agent_backend_name,
|
||||
resolve_stt_overrides,
|
||||
resolve_tts_overrides,
|
||||
resolve_vad_logging_overrides,
|
||||
resolve_vad_overrides,
|
||||
resolve_ws_overrides,
|
||||
)
|
||||
from app.ws_gateway.call_config import build_call_config
|
||||
|
||||
|
||||
class CallConfigTests(unittest.TestCase):
|
||||
def test_build_and_normalize_call_config(self) -> None:
|
||||
payload = {
|
||||
"agentBackend": "remote_ws_fake",
|
||||
"stt": {
|
||||
"provider": "internal_http",
|
||||
"language": "pt-BR",
|
||||
"configOverride": "{\"processor\":{\"strategy\":\"faster_default\"}}",
|
||||
"minProbSingleWord": "0.15",
|
||||
"disableVosk": True,
|
||||
},
|
||||
"tts": {
|
||||
"provider": "elevenlabs",
|
||||
"voiceId": "voice-123",
|
||||
"modelId": "model-456",
|
||||
},
|
||||
"vad": {
|
||||
"minSpeechDuration": 0.04,
|
||||
"activationThreshold": 0.18,
|
||||
"deactivationThreshold": 0.10,
|
||||
"minSilenceDuration": 0.7,
|
||||
"prefixPaddingDuration": 0.75,
|
||||
"preBackendWaitNoticeFastOnVadPause": True,
|
||||
"deferredInterruptionMinAudioMs": 1350,
|
||||
"deferredInterruptionEnabled": False,
|
||||
},
|
||||
"vadLogging": {
|
||||
"logDecisions": True,
|
||||
"logActivity": False,
|
||||
"activityMinProbability": 0.03,
|
||||
},
|
||||
"ws": {
|
||||
"outputGain": 1.2,
|
||||
"audioInputBacklogShedEnabled": True,
|
||||
"audioInputBacklogShedThresholdMs": 700,
|
||||
"audioInputBacklogShedKeepMs": 250,
|
||||
"audioInputLatencyMetricsEnabled": True,
|
||||
"audioInputLatencyAlertMs": 900,
|
||||
"audioInputLatencyLogIntervalS": 10.5,
|
||||
"livekitAudioSourceQueueSizeMs": 400,
|
||||
"livekitAudioSourceClearOnShed": False,
|
||||
"audioInputBacklogEnergyShedEnabled": True,
|
||||
"audioInputBacklogEnergyShedMaxExcessMs": 600,
|
||||
"audioInputBacklogSilenceDbfs": -60,
|
||||
},
|
||||
"agentFake": {
|
||||
"delayMs": 2500,
|
||||
"responses": (
|
||||
"Esta e a primeira resposta simulada com tamanho intermediario;"
|
||||
"Esta e a resposta final simulada encerrando o atendimento"
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
built = build_call_config(payload)
|
||||
normalized = normalize_call_config(payload)
|
||||
|
||||
self.assertEqual(built, normalized)
|
||||
self.assertEqual(resolve_agent_backend_name(payload, "remote_ws"), "remote_ws_fake")
|
||||
self.assertEqual(resolve_stt_overrides(payload)["language"], "pt-BR")
|
||||
self.assertEqual(resolve_stt_overrides(payload)["disable_vosk"], "True")
|
||||
self.assertEqual(
|
||||
resolve_stt_overrides(payload)["config_override"],
|
||||
"{\"processor\":{\"strategy\":\"faster_default\"}}",
|
||||
)
|
||||
self.assertEqual(resolve_tts_overrides(payload)["voice_id"], "voice-123")
|
||||
self.assertEqual(resolve_vad_overrides(payload)["min_speech_duration"], "0.04")
|
||||
self.assertEqual(resolve_vad_overrides(payload)["activation_threshold"], "0.18")
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["pre_backend_wait_notice_fast_on_vad_pause"],
|
||||
"True",
|
||||
)
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["deferred_interruption_min_audio_ms"],
|
||||
"1350",
|
||||
)
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["deferred_interruption_enabled"],
|
||||
"False",
|
||||
)
|
||||
self.assertEqual(resolve_vad_logging_overrides(payload)["log_decisions"], "True")
|
||||
self.assertEqual(resolve_vad_logging_overrides(payload)["log_activity"], "False")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["output_gain"], "1.2")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_shed_enabled"], "True")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_shed_threshold_ms"], "700")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_shed_keep_ms"], "250")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_latency_metrics_enabled"], "True")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_latency_alert_ms"], "900")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_latency_log_interval_s"], "10.5")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["livekit_audio_source_queue_size_ms"], "400")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["livekit_audio_source_clear_on_shed"], "False")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_energy_shed_enabled"], "True")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_energy_shed_max_excess_ms"], "600")
|
||||
self.assertEqual(resolve_ws_overrides(payload)["audio_in_backlog_silence_dbfs"], "-60")
|
||||
fake = resolve_fake_agent_overrides(payload)
|
||||
self.assertEqual(fake["delay_ms"], 2500)
|
||||
self.assertEqual(len(fake["responses"]), 2)
|
||||
|
||||
def test_empty_payload_falls_back_to_defaults(self) -> None:
|
||||
payload = {}
|
||||
|
||||
self.assertEqual(
|
||||
build_call_config(payload),
|
||||
{
|
||||
"agent_backend": "",
|
||||
"stt": {
|
||||
"provider": "",
|
||||
"language": "",
|
||||
"api_key": "",
|
||||
"initial_prompt": "",
|
||||
"config_override": "",
|
||||
"min_prob_single_word": "",
|
||||
"disable_vosk": "",
|
||||
},
|
||||
"tts": {
|
||||
"provider": "",
|
||||
"voice_id": "",
|
||||
"model_id": "",
|
||||
"language": "",
|
||||
},
|
||||
"vad": {
|
||||
"min_speech_duration": "",
|
||||
"activation_threshold": "",
|
||||
"deactivation_threshold": "",
|
||||
"min_silence_duration": "",
|
||||
"prefix_padding_duration": "",
|
||||
"pre_backend_wait_notice_fast_on_vad_pause": "",
|
||||
"deferred_interruption_min_audio_ms": "",
|
||||
"deferred_interruption_enabled": "",
|
||||
},
|
||||
"vad_logging": {
|
||||
"log_decisions": "",
|
||||
"log_activity": "",
|
||||
"activity_min_probability": "",
|
||||
},
|
||||
"ws": {
|
||||
"output_gain": "",
|
||||
"audio_in_backlog_shed_enabled": "",
|
||||
"audio_in_backlog_shed_threshold_ms": "",
|
||||
"audio_in_backlog_shed_keep_ms": "",
|
||||
"audio_in_latency_metrics_enabled": "",
|
||||
"audio_in_latency_alert_ms": "",
|
||||
"audio_in_latency_log_interval_s": "",
|
||||
"livekit_audio_source_queue_size_ms": "",
|
||||
"livekit_audio_source_clear_on_shed": "",
|
||||
"audio_in_backlog_energy_shed_enabled": "",
|
||||
"audio_in_backlog_energy_shed_max_excess_ms": "",
|
||||
"audio_in_backlog_silence_dbfs": "",
|
||||
},
|
||||
"agent_fake": {"delay_ms": "", "responses": ""},
|
||||
},
|
||||
)
|
||||
self.assertEqual(resolve_agent_backend_name(payload, "remote_ws"), "remote_ws")
|
||||
|
||||
def test_fake_responses_are_trimmed_and_default_delay_is_applied(self) -> None:
|
||||
payload = {
|
||||
"agentBackend": "remote_ws_fake",
|
||||
"agentFake": {
|
||||
"responses": (
|
||||
" Primeira resposta simulada com comprimento intermediario ;"
|
||||
" Segunda resposta simulada encerrando corretamente a chamada "
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
fake = resolve_fake_agent_overrides(payload)
|
||||
|
||||
self.assertEqual(fake["delay_ms"], 2500)
|
||||
self.assertEqual(
|
||||
fake["responses"],
|
||||
[
|
||||
"Primeira resposta simulada com comprimento intermediario",
|
||||
"Segunda resposta simulada encerrando corretamente a chamada",
|
||||
],
|
||||
)
|
||||
|
||||
def test_fake_responses_reject_invalid_contract(self) -> None:
|
||||
valid = "Esta resposta simulada possui tamanho intermediario adequado"
|
||||
invalid_cases = [
|
||||
{"agentBackend": "remote_ws", "agentFake": {"responses": f"{valid};{valid}"}},
|
||||
{"agentBackend": "remote_ws_fake", "agentFake": {"responses": valid}},
|
||||
{"agentBackend": "remote_ws_fake", "agentFake": {"responses": f"{valid};;{valid}"}},
|
||||
{"agentBackend": "remote_ws_fake", "agentFake": {"responses": f"curta;{valid}"}},
|
||||
{
|
||||
"agentBackend": "remote_ws_fake",
|
||||
"agentFake": {"responses": f"{valid};{valid}", "delayMs": 180001},
|
||||
},
|
||||
]
|
||||
|
||||
for payload in invalid_cases:
|
||||
with self.subTest(payload=payload), self.assertRaises(ValueError):
|
||||
resolve_fake_agent_overrides(payload)
|
||||
|
||||
def test_disable_vosk_and_fake_backend_are_normalized_per_call(self) -> None:
|
||||
payload = {
|
||||
"agentBackend": "remote_ws_fake",
|
||||
"stt": {"provider": "internal_http", "disableVosk": True},
|
||||
}
|
||||
|
||||
self.assertEqual(resolve_agent_backend_name(payload, "remote_ws"), "remote_ws_fake")
|
||||
self.assertEqual(resolve_stt_overrides(payload)["disable_vosk"], "True")
|
||||
|
||||
def test_fast_vad_pause_override_lives_with_vad_overrides(self) -> None:
|
||||
payload = {"vad": {"preBackendWaitNoticeFastOnVadPause": True}}
|
||||
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["pre_backend_wait_notice_fast_on_vad_pause"],
|
||||
"True",
|
||||
)
|
||||
|
||||
def test_deferred_interruption_min_audio_override_lives_with_vad_overrides(self) -> None:
|
||||
payload = {"vad": {"deferredInterruptionMinAudioMs": 1250}}
|
||||
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["deferred_interruption_min_audio_ms"],
|
||||
"1250",
|
||||
)
|
||||
|
||||
def test_deferred_interruption_min_audio_accepts_root_env_style_key(self) -> None:
|
||||
payload = {"DEFERRED_INTERRUPTION_MIN_AUDIO_MS": 1400}
|
||||
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["deferred_interruption_min_audio_ms"],
|
||||
"1400",
|
||||
)
|
||||
|
||||
def test_deferred_interruption_enabled_accepts_root_env_style_key(self) -> None:
|
||||
payload = {"DEFERRED_INTERRUPTION_ENABLED": False}
|
||||
|
||||
self.assertEqual(
|
||||
resolve_vad_overrides(payload)["deferred_interruption_enabled"],
|
||||
"False",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user