Files
VoiceStudio/tests/test_models_catalog.py
T
debpalashandClaude Fable 5 63fd497caf feat: TTS-only first run, platform-curated ASR, guided OS permissions, parakeet-mlx
Only the TTS model (~2.4 GB) is required on first run; ASR models are
per-platform curated picks (curated_on in models.yaml) installed on demand.
Every transcription surface returns a typed asr_model_missing error with a
one-click download CTA instead of silently pulling multi-GB Whisper weights.
Settings -> Models is a grouped, platform-aware catalog. New guided
permissions UX (wizard System Check + Settings -> Permissions + mic
pre-flight) with native mic-state checks and OS settings deep-links. New
parakeet-mlx engine brings Parakeet TDT v3 to Apple Silicon (language-gated
capture preference so multilingual dictation never regresses). Docs:
expressive-speech page, Flush/Unload + CPU-fallback triage, clone-length FAQ.
Hardening: preflight fails open for custom model pins, ROCm curation no
longer inherits NVIDIA picks, Windows mic probe reads the NonPackaged
consent key, CaptureWidget setup race fixed, offline-cache CI simulation
fixes so empty-cache runners stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:20:54 +05:30

104 lines
3.9 KiB
Python

"""Regression guard for the bundled model catalog (``backend/config/models.yaml``).
Issue #239: two ASR entries pointed at Hugging Face repo IDs that returned
HTTP 404 ("Repository Not Found"), so users could not install those ASR models:
* ``UsefulSensors/moonshine-small`` — no such model (Moonshine ships *tiny* /
*base*, there is no 300M "small").
* ``Systran/faster-whisper-large-v3-turbo`` — Systran publishes no turbo repo.
These tests are intentionally **static** (no network) so they run in CI: they
assert every ``repo_id`` is well-formed and that the known-bad IDs can never be
reintroduced. They do not verify live HF availability — that would be flaky and
slow — but they stop the catalog from shipping the exact IDs that broke #239.
"""
import re
from pathlib import Path
import yaml
_YAML = Path(__file__).resolve().parents[1] / "backend" / "config" / "models.yaml"
# org-or-user / repo-name, both segments HF-legal (letters, digits, _, -, .).
_REPO_RE = re.compile(r"^[A-Za-z0-9][\w.-]*/[\w.-]+$")
# Repo IDs that returned HTTP 404 on Hugging Face (issue #239). Must never reappear.
_KNOWN_BAD = {
"UsefulSensors/moonshine-small",
"Systran/faster-whisper-large-v3-turbo",
}
def _models():
data = yaml.safe_load(_YAML.read_text(encoding="utf-8"))
return data["models"] if isinstance(data, dict) and "models" in data else data
def test_catalog_loads_and_has_entries():
models = _models()
assert isinstance(models, list) and len(models) > 0
def test_every_repo_id_is_well_formed():
for m in _models():
rid = m.get("repo_id")
assert rid and _REPO_RE.match(rid), f"malformed repo_id: {rid!r}"
def test_required_fields_present():
for m in _models():
for field in ("repo_id", "label", "role"):
assert m.get(field), f"{m.get('repo_id')!r} missing required field {field!r}"
def test_known_404_repo_ids_absent():
ids = {m.get("repo_id", "") for m in _models()}
leaked = ids & _KNOWN_BAD
assert not leaked, f"known-404 repo IDs reintroduced (issue #239): {leaked}"
# ── TTS-only required gate + curated_on (per-platform curation) ─────────────
# The app must boot with just the TTS model: ASR is optional and surfaced as
# per-platform curated picks (curated_on) instead of a required download.
_CURATED_TAGS = {"all", "darwin-arm64", "darwin-x86_64", "cuda", "rocm", "cpu"}
def test_only_tts_is_required():
required = [m for m in _models() if m.get("required")]
assert [m["repo_id"] for m in required] == ["k2-fsa/OmniVoice"], (
"exactly one required model is allowed — the TTS engine. ASR models "
f"are optional curated picks (curated_on). Got: {required}"
)
assert required[0]["role"] == "TTS"
def test_curated_on_tags_are_valid():
for m in _models():
for tag in m.get("curated_on", []) or []:
assert tag in _CURATED_TAGS, (
f"{m['repo_id']}: unknown curated_on tag {tag!r} "
f"(valid: {sorted(_CURATED_TAGS)})"
)
def test_every_platform_has_a_curated_asr_pick():
"""Each host family must resolve at least one curated offline-capable ASR
model, or the ASR-missing download CTA would have nothing to offer."""
models = _models()
for family_tags in (
{"darwin", "darwin-arm64"},
{"darwin", "darwin-x86_64", "cpu"},
{"win32", "win32-AMD64", "cuda"},
{"linux", "linux-x86_64", "cuda", "rocm"},
{"linux", "linux-x86_64", "cpu"},
):
curated_asr = [
m for m in models
if m.get("role") == "ASR"
and (
"all" in (m.get("curated_on") or [])
or set(m.get("curated_on") or []) & family_tags
)
and (not m.get("platforms") or set(m["platforms"]) & family_tags)
]
assert curated_asr, f"no curated ASR pick resolves for host tags {family_tags}"