Files
VoiceStudio/tests/test_hf_mirror_settings.py
T
e2c4ea93b0 fix(models-settings): surface async install errors, disk-space guard, cancel wiring, honest restart (#908)
Live-audit fixes for the Models settings surface — the P1s were cases where
the feature silently didn't work for the user.

P1-A — Async install errors were invisible. The `install_error` SSE event
carries excellent mirror-aware text (#890 core/failure.py), but the Model
Store auto-purged the errored row ~800ms later (same as a success) and the
first-run WizardLibrary DELETED the row without ever reading `ev.error`. The
SSE→rowState reduction is now a pure, tested reducer (downloadReducer.js /
reduceWizardDownloadEvent); only SUCCESS terminals auto-purge
(isAutoPurgeTerminal), an error persists on the row with inline text + Retry +
Dismiss (Model Store) / a Retry (wizard).

P1-B — No disk-space check on install. `POST /models/install` now compares the
FDL-05 plan's exact `to_download_bytes` (+ MIN_FREE_GB headroom) against
`shutil.disk_usage(cache).free` BEFORE downloading and emits an actionable
install_error naming the sizes (needs X, headroom Y, have Z) instead of failing
mid-download. `/models` also surfaces `disk_free_gb` in the header. MIN_FREE_GB
+ disk_free_bytes are single-sourced in setup/models.py (wizard delegates).

P2-A — Wired the orphaned cancel. `POST /models/install/cancel` (FDL-11) had
zero frontend refs; the in-progress row now shows a Cancel button that calls it
and transitions the row to install_cancelled.

P2-B — Honest restart_required. The HF-mirror PUT returned restart_required:true
unconditionally; it now returns true only when the persisted value actually
changed, with accurate copy (Model Store downloads use the new mirror
immediately — resolved per-call; only transformers model loads need a restart).

P3 — i18n the un-localized panels (HFMirrorPanel, ApiKeysPanel source
labels/help/status, MODEL_ROLE_LABEL) via new en.json keys; other locales fall
back to en.

Tests: new tests/test_install_disk_space.py (reject-when-over-budget incl. the
worker wiring; allow-when-fits; degrade on unknown size/unprobeable volume),
updated tests/test_hf_mirror_settings.py (change-only restart_required), and new
frontend reducer + column-render tests for install_error persistence, Retry,
Dismiss, and Cancel.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:40:15 +05:30

65 lines
2.6 KiB
Python

"""HF mirror (HF_ENDPOINT) setting — Wave 4.3. Pure, prefs stubbed."""
import os
os.environ.setdefault("OMNIVOICE_MODEL", "test")
os.environ.setdefault("OMNIVOICE_DISABLE_FILE_LOG", "1")
import importlib
import pytest
@pytest.fixture
def settings_mod(monkeypatch):
store = {}
import core.user_env as ue
monkeypatch.setattr(ue, "get_user_env", lambda k, path=None: store.get(k))
monkeypatch.setattr(ue, "set_user_env", lambda k, v, path=None: store.__setitem__(k, v))
monkeypatch.setattr(ue, "unset_user_env", lambda k, path=None: store.pop(k, None))
monkeypatch.delenv("HF_ENDPOINT", raising=False)
return importlib.import_module("api.routers.settings")
def test_get_default_empty(settings_mod):
st = settings_mod.get_hf_mirror()
assert st["configured"] == "" and st["effective"] == ""
assert any(p["url"] == "https://hf-mirror.com" for p in st["presets"])
def test_set_and_clear(settings_mod):
st = settings_mod.set_hf_mirror(settings_mod._HFMirrorBody(url="https://hf-mirror.com/"))
assert st["configured"] == "https://hf-mirror.com" # trailing slash trimmed
assert st["restart_required"] is True # empty → mirror is a real change
assert os.environ["HF_ENDPOINT"] == "https://hf-mirror.com"
assert settings_mod.get_hf_mirror()["configured"] == "https://hf-mirror.com"
st2 = settings_mod.set_hf_mirror(settings_mod._HFMirrorBody(url=""))
assert st2["restart_required"] is True # mirror → cleared is a real change
assert settings_mod.get_hf_mirror()["configured"] == ""
assert "HF_ENDPOINT" not in os.environ
def test_restart_required_only_on_change(settings_mod):
"""restart_required is honest: True only when the persisted value actually
changes — a no-op re-save of the same URL must NOT nag the user to restart."""
# First save of a value is a change.
assert settings_mod.set_hf_mirror(
settings_mod._HFMirrorBody(url="https://hf-mirror.com")
)["restart_required"] is True
# Re-saving the SAME value (even with a trailing slash) is a no-op.
assert settings_mod.set_hf_mirror(
settings_mod._HFMirrorBody(url="https://hf-mirror.com/")
)["restart_required"] is False
# Saving empty when already empty is also a no-op.
settings_mod.set_hf_mirror(settings_mod._HFMirrorBody(url=""))
assert settings_mod.set_hf_mirror(
settings_mod._HFMirrorBody(url="")
)["restart_required"] is False
def test_rejects_non_http(settings_mod):
from fastapi import HTTPException
with pytest.raises(HTTPException) as ei:
settings_mod.set_hf_mirror(settings_mod._HFMirrorBody(url="hf-mirror.com"))
assert ei.value.status_code == 400