Files
wehub-resource-sync 6ede33ccdb
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:44 +08:00

99 lines
3.0 KiB
Python

"""Default language and voice selection for a fresh brief.
Resolution is what lets most briefs need no edits: it proposes a sensible
language and a distinct voice per speaker. These tests state the policy
("reuse what the user last chose, else English"; "two speakers should sound
like two people") through the public resolver functions and the real catalog.
We never guess the language from source content.
"""
from __future__ import annotations
import pytest
from app.podcasts.resolution import (
DEFAULT_LANGUAGE,
LanguageContext,
VoiceResolutionError,
resolve_language,
resolve_voices,
)
from app.podcasts.voices import TtsProvider, get_voice_catalog
pytestmark = pytest.mark.unit
def test_last_used_language_is_reused():
context = LanguageContext(last_used="fr")
assert resolve_language(context) == "fr"
def test_first_time_user_with_no_signal_gets_the_default():
assert resolve_language(LanguageContext()) == DEFAULT_LANGUAGE
def test_two_speakers_get_distinct_voices():
"""A two-speaker episode should not voice both with the same person."""
catalog = get_voice_catalog()
voices = resolve_voices(
catalog=catalog, provider=TtsProvider.KOKORO, language="en", speaker_count=2
)
assert len(voices) == 2
assert voices[0].voice_id != voices[1].voice_id
def test_a_users_preferred_voice_is_reused_when_still_valid():
catalog = get_voice_catalog()
voices = resolve_voices(
catalog=catalog,
provider=TtsProvider.KOKORO,
language="en",
speaker_count=2,
preferred=["kokoro:af_bella"],
)
assert voices[0].voice_id == "kokoro:af_bella"
def test_a_preferred_voice_invalid_for_the_language_is_replaced():
"""A stale preference (wrong provider/language) is silently dropped."""
catalog = get_voice_catalog()
voices = resolve_voices(
catalog=catalog,
provider=TtsProvider.KOKORO,
language="en",
speaker_count=1,
preferred=["kokoro:does-not-exist"],
)
assert voices[0].voice_id in {
v.voice_id for v in catalog.for_provider(TtsProvider.KOKORO)
}
def test_resolution_fails_when_no_voice_speaks_the_language():
"""If a provider can't speak the language at all, that is surfaced loudly."""
catalog = get_voice_catalog()
with pytest.raises(VoiceResolutionError):
resolve_voices(
catalog=catalog,
provider=TtsProvider.KOKORO,
language="xx",
speaker_count=1,
)
def test_every_speaker_is_assigned_even_when_voices_run_out():
"""With one available voice, both speakers still get one rather than failing."""
catalog = get_voice_catalog()
voices = resolve_voices(
catalog=catalog, provider=TtsProvider.KOKORO, language="fr", speaker_count=2
)
assert len(voices) == 2
def test_speaker_count_must_be_positive():
catalog = get_voice_catalog()
with pytest.raises(ValueError):
resolve_voices(
catalog=catalog, provider=TtsProvider.KOKORO, language="en", speaker_count=0
)