tts-voice-creator-clone-and.../tests/test_stt_footer_metadata.py
mARTin-B78 0551b6651f feat(voices): redesigned My Voices list, working copy, engine-aware TTS availability
My Voices
- Top bar with search, filter toggle (active-count badge), synth-source drawer, group and table buttons;
  filters/sort in a collapsible labelled panel; one-line sort header with select-all/none checkbox
- Groups chip list is a collapsed accordion; bottom bar is Refresh/Sync/Import plus one Actions (N) menu
  (Edit / Audio / Share / Danger), disabled until voices are checked; touch targets stay 44px
- Copy selected works again (copyText was no longer exported), shows "N voice names copied to clipboard"
  and falls back to a copy dialog when the async clipboard is unavailable

TTS / engines
- Health probe works for /v1 card URLs and, behind a LiteLLM gateway, checks the selected model's own
  upstream so stopped containers are not reported as available
- Unhiding a voice publishes a missing entry to the engine's voices.json (no more silent default-voice fallback)
- Status-bar TTS picker also drives the My Voices and STT-test selectors; Use as TTS refreshes the status bar

Includes the engine connect/disconnect, custom TTS, STT footer and magazine auth work that was already
in the working tree, with their tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 04:44:27 +02:00

138 lines
8.0 KiB
Python

"""STT footer metadata uses card names and effective endpoints."""
import asyncio
import os
import tempfile
import unittest
from unittest.mock import patch
from fastapi import HTTPException
os.environ.setdefault('CONFIG_DIR', tempfile.mkdtemp(prefix='ttvc-stt-footer-tests-'))
from routes import stt
class SttFooterMetadataTests(unittest.TestCase):
def test_custom_models_use_own_legacy_name_with_stable_id_priority(self):
settings = {
'custom_engine_cards': [
{'id': 'first', 'name': 'First speech', 'url': 'http://shared', 'role': 'stt'},
{'id': 'second', 'name': 'Second speech', 'url': 'http://shared', 'role': 'stt'},
],
'engine_models': {'First speech': 'first-legacy', 'Second speech': 'second-legacy'},
}
self.assertEqual(stt._stt_backend_model('custom:first', settings), 'first-legacy')
self.assertEqual(stt._stt_backend_model('custom:second', settings), 'second-legacy')
settings['engine_models']['first'] = 'first-current'
self.assertEqual(stt._stt_backend_model('custom:first', settings), 'first-current')
settings['engine_models']['first'] = ''
self.assertEqual(stt._stt_backend_model('custom:first', settings), 'large-v3')
def test_native_selected_credentials_preserve_only_matching_global_fallback(self):
settings = {
'engine_active': {'faster_whisper_url': 'faster-whisper'},
'faster_whisper_url': 'http://gateway/v1/',
'whisper_url': 'http://gateway',
'whisper_api_key': 'global-test-key',
'engine_api_keys': {'faster-whisper': 'own-test-key', 'other': 'other-test-key'},
'custom_engine_cards': [{'id': 'other', 'url': 'http://gateway', 'role': 'stt'}],
}
self.assertEqual(stt._stt_backend_api_key(settings, 'faster_whisper'), 'own-test-key')
del settings['engine_api_keys']['faster-whisper']
self.assertEqual(stt._stt_backend_api_key(settings, 'faster_whisper'), 'global-test-key')
settings['faster_whisper_url'] = 'http://unrelated'
self.assertEqual(stt._stt_backend_api_key(settings, 'faster_whisper'), '')
settings['engine_active']['faster_whisper_url'] = 'other'
settings['faster_whisper_url'] = 'http://gateway'
del settings['engine_api_keys']['other']
self.assertEqual(stt._stt_backend_api_key(settings, 'faster_whisper'), '')
@patch('routes.stt._stt_backend_health', return_value={'ok': True, 'models': ['discovered']})
@patch('routes.stt._load_settings')
def test_disabled_native_does_not_disable_selected_custom_on_shared_url(self, load, health):
load.return_value = {
'whisper_url': 'http://shared/v1',
'nvidia_router_url': 'http://shared/v1',
'engine_active': {'whisper_url': 'realtime', 'nvidia_router_url': 'realtime'},
'engine_disabled': {'nvidia-parakeet-asr': True},
'custom_engine_cards': [{'id': 'realtime', 'name': 'Realtime', 'role': 'stt', 'url': 'http://shared/v1'}],
}
items = {item['id']: item for item in asyncio.run(stt.stt_backends())['backends']}
self.assertFalse(items['nvidia_router']['available'])
self.assertFalse(items['nvidia_router']['connected'])
self.assertTrue(items['custom:realtime']['available'])
self.assertTrue(items['custom:realtime']['connected'])
self.assertEqual(items['configured']['engine_id'], 'realtime')
self.assertEqual(items['custom:realtime']['engine_id'], 'realtime')
self.assertEqual(sum(call.args[0] == 'http://shared/v1' for call in health.call_args_list), 2)
with self.assertRaises(HTTPException) as caught:
stt._transcribe_audio(None, load.return_value, 'nvidia_router')
self.assertEqual(caught.exception.status_code, 409)
@patch('routes.stt._stt_backend_health', return_value={'ok': True, 'models': ['discovered']})
@patch('routes.stt._load_settings')
def test_legacy_native_slot_uses_known_endpoint_identity(self, load, health):
load.return_value = {
'nvidia_asr_url': 'http://native:8090',
'engine_disabled': {'nvidia-parakeet-asr': True},
}
items = {item['id']: item for item in asyncio.run(stt.stt_backends())['backends']}
item = items['nvidia_parakeet']
self.assertEqual(item['title'], 'NVIDIA Parakeet Router ASR')
self.assertEqual(item['engine_id'], 'nvidia-parakeet-asr')
self.assertFalse(item['available'])
self.assertFalse(item['connected'])
self.assertNotIn('http://native:8090', [call.args[0] for call in health.call_args_list])
with self.assertRaises(HTTPException) as caught:
stt._transcribe_audio(None, load.return_value, 'nvidia_parakeet')
self.assertEqual(caught.exception.status_code, 409)
@patch('routes.stt._stt_backend_health', return_value={'ok': True, 'models': ['discovered']})
@patch('routes.stt._load_settings')
def test_selected_alternate_native_uses_its_own_disconnect_state(self, load, health):
load.return_value = {
'nvidia_asr_url': 'http://rnnt:8092',
'engine_active': {'nvidia_asr_url': 'nvidia-parakeet-rnnt-nim'},
'engine_disabled': {'parakeet-stt-multi-9011': True},
}
items = {item['id']: item for item in asyncio.run(stt.stt_backends())['backends']}
self.assertTrue(items['nvidia_parakeet']['available'])
self.assertTrue(items['nvidia_parakeet']['connected'])
self.assertEqual(items['nvidia_parakeet']['engine_id'], 'nvidia-parakeet-rnnt-nim')
self.assertFalse(stt._stt_disconnected(load.return_value, 'nvidia_parakeet', 'http://rnnt:8092'))
load.return_value['engine_disabled']['nvidia-parakeet-rnnt-nim'] = True
self.assertTrue(stt._stt_disconnected(load.return_value, 'nvidia_parakeet', 'http://rnnt:8092'))
@patch('routes.stt._stt_backend_health', return_value={'ok': True, 'models': ['discovered']})
@patch('routes.stt._load_settings')
def test_card_titles_stable_identity_and_effective_endpoint(self, load, health):
load.return_value = {
'faster_whisper_url': 'http://native:9000/v1',
'engine_active': {'faster_whisper_url': 'stt-whisperx-blackwell'},
'custom_engine_cards': [
{'id': 'first', 'name': 'Renamed speech', 'url': 'http://stale', 'role': 'stt'},
{'id': 'second', 'label': 'Other speech', 'url': 'http://shared/v1', 'role': 'stt'},
{'id': 'off', 'name': 'Disconnected', 'url': 'http://off', 'role': 'stt'},
],
'engine_local_urls': {'first': 'http://old', 'dc-first': 'http://shared/v1'},
'engine_api_keys': {'first': 'first-test-key', 'second': 'second-test-key'},
'engine_models': {'first': 'first-model', 'second': 'second-model'},
'engine_disabled': {'off': True},
}
items = {item['id']: item for item in asyncio.run(stt.stt_backends())['backends']}
self.assertEqual(items['faster_whisper']['title'], 'WhisperX · faster-whisper (Blackwell GPU)')
self.assertEqual(items['custom:first']['title'], 'Renamed speech')
self.assertEqual(items['custom:second']['title'], 'Other speech')
self.assertEqual(items['custom:first']['url'], 'http://shared/v1')
self.assertEqual(stt._stt_backend_url(load.return_value, 'custom:first'), 'http://shared/v1')
self.assertEqual(items['custom:first']['model'], 'first-model')
self.assertEqual(items['custom:second']['model'], 'second-model')
self.assertTrue(items['custom:first']['available'])
health.assert_any_call('http://shared/v1', 'first-test-key')
health.assert_any_call('http://shared/v1', 'second-test-key')
self.assertFalse(items['custom:off']['available'])
self.assertFalse(items['custom:off']['connected'])
self.assertNotIn('http://off', [call.args[0] for call in health.call_args_list])
if __name__ == '__main__':
unittest.main()