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>
65 lines
2.6 KiB
Python
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
|