mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-31 16:21:50 +03:00
fix(models): preserve preset rename compatibility
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user