Files
voculinux/tests/test_model_deletion.py
T
Jatin K MalikandGitHub ecd5839a13 feat(settings): delete unused downloaded speech models (#671)
* feat(settings): delete unused downloaded speech models

Add a Remove Model dropdown on the Speech Model page so whisper.cpp,
Whisper, and VOSK downloads can be deleted from Settings. The currently
selected model is marked in use and cannot be removed until you switch
away from it.

Fixes #650

* fix(settings): move unused model deletion below the picker

Unused downloads is its own group under the model info card. Only leftover
files appear, each with a Delete button. The group hides when empty.

* fix(settings): collapse unused downloads into an expander

Keep leftover models behind one row so a long list does not stretch the
Speech Model page. Expand to delete files one at a time; the list scrolls
once it grows past a few entries.

* fix(settings): keep leftover model list from clipping in the expander

Wrap the unused-downloads list so the expander can show a few rows and
scroll the rest, instead of painting only the first leftover.
2026-08-12 23:42:36 -07:00

115 lines
4.0 KiB
Python

"""Tests for deleting downloaded speech models from disk."""
import os
from unittest.mock import patch
import pytest
from vocalinux.utils.vosk_model_info import (
delete_vosk_model,
list_downloaded_vosk_models,
vosk_model_dirname,
)
from vocalinux.utils.whispercpp_model_info import delete_model, list_downloaded_models
def test_whispercpp_list_and_delete(tmp_path):
with patch("vocalinux.utils.whispercpp_model_info.models_dir", return_value=str(tmp_path)):
assert list_downloaded_models() == []
from vocalinux.utils.whispercpp_model_info import get_model_path
tiny = get_model_path("tiny")
base = get_model_path("base")
os.makedirs(os.path.dirname(tiny), exist_ok=True)
with open(tiny, "wb") as handle:
handle.write(b"tiny")
with open(base, "wb") as handle:
handle.write(b"base")
downloaded = list_downloaded_models()
assert downloaded == ["tiny", "base"]
deleted = delete_model("tiny")
assert deleted == tiny
assert not os.path.exists(tiny)
assert os.path.exists(base)
assert list_downloaded_models() == ["base"]
def test_whispercpp_delete_unknown_and_missing(tmp_path):
with patch("vocalinux.utils.whispercpp_model_info.models_dir", return_value=str(tmp_path)):
with pytest.raises(ValueError, match="Unknown whisper.cpp model"):
delete_model("not-a-real-model")
with pytest.raises(FileNotFoundError):
delete_model("tiny")
def test_vosk_dirname_and_unique_list(tmp_path):
assert vosk_model_dirname("small", "en-us") == "vosk-model-small-en-us-0.15"
assert vosk_model_dirname("medium", "en-us") == "vosk-model-en-us-0.22"
# Korean medium reuses the small folder.
assert vosk_model_dirname("small", "ko") == vosk_model_dirname("medium", "ko")
small_en = tmp_path / "vosk-model-small-en-us-0.15"
small_en.mkdir()
(small_en / "am").write_text("x")
korean = tmp_path / "vosk-model-small-ko-0.22"
korean.mkdir()
with patch("vocalinux.utils.vosk_model_info.models_dir", return_value=str(tmp_path)):
found = list_downloaded_vosk_models()
dirnames = [item.dirname for item in found]
assert dirnames == [
"vosk-model-small-en-us-0.15",
"vosk-model-small-ko-0.22",
]
assert dirnames.count("vosk-model-small-ko-0.22") == 1
deleted = delete_vosk_model("vosk-model-small-en-us-0.15")
assert deleted == str(small_en)
assert not small_en.exists()
assert korean.exists()
assert [item.dirname for item in list_downloaded_vosk_models()] == [
"vosk-model-small-ko-0.22"
]
def test_vosk_delete_rejects_unknown_and_missing(tmp_path):
with patch("vocalinux.utils.vosk_model_info.models_dir", return_value=str(tmp_path)):
with pytest.raises(ValueError, match="Unknown VOSK model"):
delete_vosk_model("../etc")
with pytest.raises(ValueError, match="Unknown VOSK model"):
delete_vosk_model("not-a-vosk-model")
with pytest.raises(FileNotFoundError):
delete_vosk_model("vosk-model-small-en-us-0.15")
def test_whisper_list_and_delete(tmp_path):
cache = tmp_path / "whisper"
cache.mkdir()
tiny = cache / "tiny.pt"
tiny.write_bytes(b"weights")
missing_default = tmp_path / "missing-whisper-cache"
from vocalinux.ui.settings_dialog import (
_delete_whisper_model,
_list_downloaded_whisper_models,
)
with (
patch(
"vocalinux.ui.settings_dialog._get_whisper_cache_dir",
return_value=str(cache),
),
patch(
"vocalinux.ui.settings_dialog.os.path.expanduser",
return_value=str(missing_default),
),
):
assert _list_downloaded_whisper_models() == ["tiny"]
deleted = _delete_whisper_model("tiny")
assert deleted == [str(tiny)]
assert not tiny.exists()
assert _list_downloaded_whisper_models() == []