fix(models): preserve preset rename compatibility

This commit is contained in:
Xubin Ren
2026-08-16 11:50:56 +08:00
parent a956fe1d60
commit 0a6ee1c539
20 changed files with 307 additions and 46 deletions
+14
View File
@@ -52,6 +52,20 @@ def test_webui_session_list_reuses_valid_index_without_scanning_files(
assert rows[0]["model_preset"] == "fast"
def test_webui_session_list_refreshes_after_model_preset_rename(tmp_path: Path) -> None:
manager = SessionManager(tmp_path)
session = manager.get_or_create("websocket:renamed-preset")
session.metadata[SESSION_MODEL_PRESET_METADATA_KEY] = "openai"
session.add_message("user", "hello")
manager.save(session)
assert list_webui_sessions(manager)[0]["model_preset"] == "openai"
assert manager.rename_model_preset("openai", "Codex") == 1
assert list_webui_sessions(manager)[0]["model_preset"] == "Codex"
def test_webui_session_index_uses_unique_temp_file(tmp_path: Path) -> None:
manager = SessionManager(tmp_path)
session = manager.get_or_create("websocket:unique-index-temp")
+43 -1
View File
@@ -10,6 +10,8 @@ import pytest
from nanobot.config.loader import load_config, save_config
from nanobot.config.schema import Config, InlineFallbackConfig, ModelPresetConfig
from nanobot.providers.registry import find_by_name
from nanobot.session.manager import SessionManager
from nanobot.session.model_selection import SESSION_MODEL_PRESET_METADATA_KEY
from nanobot.webui.settings_api import (
WebUISettingsError,
_docs_version,
@@ -434,9 +436,17 @@ def test_update_model_configuration_renames_preset_and_config_references(
defaults.dream.model_override = "openai"
save_config(config, config_path)
monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path)
session_manager = SessionManager(
tmp_path / "workspace",
sessions_root=tmp_path / "sessions",
)
session = session_manager.get_or_create("websocket:selected")
session.metadata[SESSION_MODEL_PRESET_METADATA_KEY] = "openai"
session_manager.save(session)
payload = update_model_configuration(
{"name": ["openai"], "new_name": ["Codex"]}
{"name": ["openai"], "new_name": ["Codex"]},
rename_model_preset=session_manager.rename_model_preset,
)
assert payload["agent"]["model_preset"] == "Codex"
@@ -451,6 +461,11 @@ def test_update_model_configuration_renames_preset_and_config_references(
assert saved.agents.defaults.model_preset == "Codex"
assert saved.agents.defaults.fallback_models == ["backup", "Codex"]
assert saved.agents.defaults.dream.model_override == "Codex"
persisted = SessionManager(
tmp_path / "workspace",
sessions_root=tmp_path / "sessions",
).get_or_create("websocket:selected")
assert persisted.metadata[SESSION_MODEL_PRESET_METADATA_KEY] == "Codex"
def test_update_model_configuration_rejects_duplicate_rename(
@@ -473,6 +488,33 @@ def test_update_model_configuration_rejects_duplicate_rename(
assert set(load_config(config_path).model_presets) == {"openai", "Codex"}
def test_update_model_configuration_rolls_back_sessions_when_config_save_fails(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
config_path = tmp_path / "config.json"
config = Config(
model_presets={"openai": ModelPresetConfig(model="openai/gpt-4.1")}
)
save_config(config, config_path)
calls: list[tuple[str, str]] = []
def fail_save(_config: Config, _path) -> None:
raise OSError("disk full")
monkeypatch.setattr("nanobot.webui.settings_api._save_settings_config", fail_save)
with pytest.raises(OSError, match="disk full"):
update_model_configuration(
{"name": ["openai"], "new_name": ["Codex"]},
config_path=config_path,
rename_model_preset=lambda old, new: calls.append((old, new)) or 1,
)
assert calls == [("openai", "Codex"), ("Codex", "openai")]
assert list(load_config(config_path).model_presets) == ["openai"]
def test_settings_payload_exposes_named_model_call_order(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
+39 -1
View File
@@ -24,9 +24,15 @@ def _router(
config_path: Path | None = None,
mcp_runtime_status: Callable[[], Mapping[str, str]] | None = None,
mcp_reload: Callable[[], Awaitable[dict[str, object]]] | None = None,
rename_model_preset: Callable[[str, str], int] | None = None,
refresh_runtime_config: Callable[[], None] | None = None,
) -> WebUISettingsRouter:
return WebUISettingsRouter(
settings=WebUISettingsServices.create(config_path or get_config_path()),
settings=WebUISettingsServices.create(
config_path or get_config_path(),
rename_model_preset=rename_model_preset,
refresh_runtime_config=refresh_runtime_config,
),
bus=SimpleNamespace(),
logger=SimpleNamespace(exception=lambda *_args: None),
check_api_token=lambda _request: authorized,
@@ -346,6 +352,38 @@ async def test_model_preset_mutation_routes(
assert captured["query"] == expected_query
@pytest.mark.asyncio
async def test_model_update_route_forwards_session_rename_dependency(monkeypatch) -> None:
rename_model_preset = MagicMock(return_value=2)
refresh_runtime_config = MagicMock()
captured: dict[str, object] = {}
def update(query, *, config_path=None, rename_model_preset=None):
captured.update(query=query, rename_model_preset=rename_model_preset)
return {"updated": True}
monkeypatch.setattr("nanobot.webui.settings_routes.update_model_configuration", update)
path = "/api/settings/model-configurations/update"
request = _mutation_request(path, {"name": "openai", "new_name": "Codex"})
response = await _router(
rename_model_preset=rename_model_preset,
refresh_runtime_config=refresh_runtime_config,
).dispatch(
None,
request,
path,
)
assert response is not None
assert response.status_code == 200
assert captured == {
"query": {"name": ["openai"], "new_name": ["Codex"]},
"rename_model_preset": rename_model_preset,
}
refresh_runtime_config.assert_called_once_with()
@pytest.mark.asyncio
async def test_settings_get_mutation_route_is_method_not_allowed() -> None:
path = "/api/settings/provider/update"