fix(models): duplicate dropdown entries, stale default model, lowercase injected label (#907 #908 #909) (#918)
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
This commit is contained in:
153
tests/test_issues_907_908_909_model_dropdown.py
Normal file
153
tests/test_issues_907_908_909_model_dropdown.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Regression tests for issues #907, #908, #909 — model dropdown fixes."""
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8")
|
||||
PANELS_JS = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# ── #907: Normalized dedup in _addLiveModelsToSelect ─────────────────────────
|
||||
|
||||
class TestIssue907LiveModelDedup:
|
||||
"""Live-fetched models with @provider: prefix must not duplicate server-injected bare entries."""
|
||||
|
||||
def test_addLiveModelsToSelect_has_norm_dedup(self):
|
||||
# _normId helper and existingNorm set must be present in _addLiveModelsToSelect
|
||||
fn_idx = UI_JS.find('function _addLiveModelsToSelect(')
|
||||
assert fn_idx != -1, "_addLiveModelsToSelect not found"
|
||||
# Find the closing brace of the function (~800 chars is enough)
|
||||
fn_body = UI_JS[fn_idx:fn_idx + 2000]
|
||||
assert '_normId' in fn_body or 'existingNorm' in fn_body, (
|
||||
"_addLiveModelsToSelect must normalise IDs before dedup check (#907)"
|
||||
)
|
||||
|
||||
def test_normId_strips_at_prefix(self):
|
||||
# The _normId lambda/function must strip @provider: prefix
|
||||
fn_idx = UI_JS.find('function _addLiveModelsToSelect(')
|
||||
fn_body = UI_JS[fn_idx:fn_idx + 2000]
|
||||
has_at_strip = ("startsWith('@')" in fn_body or "split(':'" in fn_body)
|
||||
assert has_at_strip, (
|
||||
"_normId in _addLiveModelsToSelect must strip @provider: prefix for dedup (#907)"
|
||||
)
|
||||
|
||||
def test_existingNorm_used_as_guard(self):
|
||||
fn_idx = UI_JS.find('function _addLiveModelsToSelect(')
|
||||
fn_body = UI_JS[fn_idx:fn_idx + 2000]
|
||||
assert 'existingNorm.has(' in fn_body, (
|
||||
"_addLiveModelsToSelect must check existingNorm before appending (#907)"
|
||||
)
|
||||
|
||||
def test_normId_handles_multi_colon_ollama_ids(self):
|
||||
"""_normId must strip ONLY the first colon so multi-colon Ollama tag IDs
|
||||
(e.g. '@ollama-cloud:qwen3-vl:235b-instruct' vs bare 'qwen3-vl:235b-instruct')
|
||||
still dedup correctly. JS `split(':',2)[1]` with limit=2 TRUNCATES in JS
|
||||
(unlike Python's split), so the naive variant would lose the tag suffix
|
||||
and mis-dedup.
|
||||
"""
|
||||
fn_idx = UI_JS.find('function _addLiveModelsToSelect(')
|
||||
assert fn_idx != -1
|
||||
fn_body = UI_JS[fn_idx:fn_idx + 2000]
|
||||
# The implementation must use indexOf/substring or split().slice(1).join(),
|
||||
# not split(':', 2)[1] which truncates the tail.
|
||||
good = 'indexOf' in fn_body or "slice(1).join(':')" in fn_body
|
||||
assert good, (
|
||||
"_normId must strip only the first colon to preserve Ollama multi-colon "
|
||||
"tag IDs (e.g. @ollama-cloud:qwen3-vl:235b-instruct). Use "
|
||||
"substring(indexOf(':')+1) or split(':').slice(1).join(':') — NOT "
|
||||
"split(':', 2)[1] which silently truncates in JS."
|
||||
)
|
||||
assert "split(':',2)[1]" not in fn_body and "split(':', 2)[1]" not in fn_body, (
|
||||
"_normId still uses split(':', 2)[1] which truncates multi-colon IDs in JS; "
|
||||
"use indexOf/substring instead."
|
||||
)
|
||||
|
||||
|
||||
# ── #908: window._defaultModel updated on settings save ─────────────────────
|
||||
|
||||
class TestIssue908DefaultModelSync:
|
||||
"""window._defaultModel must be updated when the user saves a new default in Preferences."""
|
||||
|
||||
def test_applySavedSettingsUi_updates_window_defaultModel(self):
|
||||
fn_idx = PANELS_JS.find('function _applySavedSettingsUi(')
|
||||
assert fn_idx != -1, "_applySavedSettingsUi not found"
|
||||
# Find the end of the function (next function definition)
|
||||
fn_end = PANELS_JS.find('\nasync function saveSettings(', fn_idx)
|
||||
fn_body = PANELS_JS[fn_idx:fn_end]
|
||||
assert 'window._defaultModel' in fn_body, (
|
||||
"_applySavedSettingsUi must update window._defaultModel so newSession() "
|
||||
"uses the newly saved default without a page reload (#908)"
|
||||
)
|
||||
|
||||
def test_defaultModel_update_conditioned_on_body_default_model(self):
|
||||
fn_idx = PANELS_JS.find('function _applySavedSettingsUi(')
|
||||
fn_end = PANELS_JS.find('\nasync function saveSettings(', fn_idx)
|
||||
fn_body = PANELS_JS[fn_idx:fn_end]
|
||||
# Must be guarded so we don't clear _defaultModel when body.default_model is absent
|
||||
assert "if(body.default_model)" in fn_body or "body.default_model &&" in fn_body, (
|
||||
"window._defaultModel assignment must be conditional on body.default_model being set"
|
||||
)
|
||||
|
||||
|
||||
# ── #909: Injected default model label quality ───────────────────────────────
|
||||
|
||||
class TestIssue909InjectedModelLabel:
|
||||
"""The server must use a proper label for the injected default model (not raw lowercase ID)."""
|
||||
|
||||
def test_get_label_for_model_helper_exists(self):
|
||||
import api.config as config
|
||||
assert hasattr(config, '_get_label_for_model'), (
|
||||
"api/config.py must define _get_label_for_model() for the injected default label (#909)"
|
||||
)
|
||||
|
||||
def test_label_helper_capitalizes_bare_id(self):
|
||||
from api.config import _get_label_for_model
|
||||
label = _get_label_for_model('minimax/minimax-m2.7', [])
|
||||
assert label != 'minimax-m2.7', (
|
||||
"_get_label_for_model should not return the raw lowercase ID (#909)"
|
||||
)
|
||||
# Should capitalize: "Minimax M2.7" or similar
|
||||
assert label[0].isupper(), "Label should start with an uppercase letter"
|
||||
|
||||
def test_label_helper_uses_catalog_when_available(self):
|
||||
from api.config import _get_label_for_model
|
||||
existing_groups = [
|
||||
{"provider": "Nous", "models": [
|
||||
{"id": "minimax/minimax-m2.7", "label": "Minimax M2.7 (Nous)"}
|
||||
]}
|
||||
]
|
||||
label = _get_label_for_model('minimax/minimax-m2.7', existing_groups)
|
||||
assert label == "Minimax M2.7 (Nous)", (
|
||||
"_get_label_for_model should prefer catalog label over generated one"
|
||||
)
|
||||
|
||||
def test_label_helper_strips_at_prefix_for_lookup(self):
|
||||
from api.config import _get_label_for_model
|
||||
existing_groups = [
|
||||
{"provider": "Nous", "models": [
|
||||
{"id": "minimax/minimax-m2.7", "label": "Minimax M2.7"}
|
||||
]}
|
||||
]
|
||||
# @nous:minimax/minimax-m2.7 should match minimax/minimax-m2.7 in catalog
|
||||
label = _get_label_for_model('@nous:minimax/minimax-m2.7', existing_groups)
|
||||
assert label == "Minimax M2.7", (
|
||||
"_get_label_for_model must strip @provider: prefix before catalog lookup"
|
||||
)
|
||||
|
||||
def test_config_uses_label_helper_not_raw_split(self):
|
||||
from pathlib import Path
|
||||
config_src = (Path(__file__).resolve().parent.parent / "api" / "config.py").read_text()
|
||||
# The raw label-building pattern should be replaced by the helper
|
||||
assert "_get_label_for_model" in config_src, (
|
||||
"api/config.py must call _get_label_for_model() for injected default model labels (#909)"
|
||||
)
|
||||
# The old raw pattern should NOT be present in the injection block
|
||||
old_pattern = 'default_model.split("/")[-1] if "/" in default_model else default_model'
|
||||
label_sections = [
|
||||
config_src[i:i+200]
|
||||
for i in [m.start() for m in re.finditer(r'label\s*=\s*', config_src)]
|
||||
]
|
||||
for sec in label_sections:
|
||||
assert old_pattern not in sec, (
|
||||
"api/config.py still uses raw split-based label for injected default model (#909)"
|
||||
)
|
||||
Reference in New Issue
Block a user