Improve onboard wizard setup flow

This commit is contained in:
chengyongru
2026-06-22 13:04:05 +08:00
committed by Xubin Ren
parent 9db3dc5e32
commit fc7971b3b6
16 changed files with 483 additions and 93 deletions
+114 -9
View File
@@ -8,7 +8,6 @@ from pathlib import Path
from types import SimpleNamespace
from typing import Any, cast
import pytest
from pydantic import BaseModel, Field
from nanobot.cli import onboard as onboard_wizard
@@ -452,12 +451,23 @@ class TestConfigurePydanticModelDrafts:
onboard_wizard, "_input_with_existing", lambda *_args, **_kwargs: text_value
)
def test_discarding_section_keeps_original_model_unchanged(self, monkeypatch):
def test_back_commits_section_draft(self, monkeypatch):
model = _SimpleDraftModel()
self._patch_prompt_helpers(monkeypatch, ["first", "back"])
result = _configure_pydantic_model(model, "Simple")
assert result is not None
updated = cast(_SimpleDraftModel, result)
assert updated.api_key == "secret"
assert model.api_key == ""
def test_cancel_keeps_original_model_unchanged(self, monkeypatch):
model = _SimpleDraftModel()
self._patch_prompt_helpers(monkeypatch, ["first", None])
result = _configure_pydantic_model(model, "Simple")
assert result is None
assert model.api_key == ""
@@ -472,7 +482,7 @@ class TestConfigurePydanticModelDrafts:
assert updated.api_key == "secret"
assert model.api_key == ""
def test_nested_section_back_discards_nested_edits(self, monkeypatch):
def test_nested_section_back_commits_nested_edits(self, monkeypatch):
model = _OuterDraftModel()
self._patch_prompt_helpers(monkeypatch, ["first", "first", "back", "done"])
@@ -480,7 +490,7 @@ class TestConfigurePydanticModelDrafts:
assert result is not None
updated = cast(_OuterDraftModel, result)
assert updated.nested.api_key == ""
assert updated.nested.api_key == "secret"
assert model.nested.api_key == ""
def test_nested_section_done_commits_nested_edits(self, monkeypatch):
@@ -656,8 +666,8 @@ class TestValidateFieldConstraint:
def test_real_send_max_retries_field(self):
"""Validate against the actual ChannelsConfig.send_max_retries field."""
from nanobot.config.schema import ChannelsConfig
from nanobot.cli.onboard import _validate_field_constraint
from nanobot.config.schema import ChannelsConfig
field_info = ChannelsConfig.model_fields["send_max_retries"]
assert _validate_field_constraint(3, field_info) is None
@@ -847,14 +857,108 @@ class TestApiServerRegistration:
class TestMainMenuUpdate:
"""Tests for main menu including new Channel Common and API Server items."""
def test_run_onboard_quick_start_edit(self, monkeypatch):
"""run_onboard should route [Q] to Quick Start."""
initial_config = Config()
responses = iter([
"[Q] Quick Start (recommended)",
"[S] Save and Exit",
])
class FakePrompt:
def __init__(self, response):
self.response = response
def ask(self):
return self.response
def fake_select(*_args, **_kwargs):
return FakePrompt(next(responses))
def fake_quick_start(config):
config.agents.defaults.bot_name = "quickbot"
monkeypatch.setattr(onboard_wizard, "_show_main_menu_header", lambda: None)
monkeypatch.setattr(onboard_wizard, "questionary", SimpleNamespace(select=fake_select))
monkeypatch.setattr(onboard_wizard, "_configure_quick_start", fake_quick_start)
result = run_onboard(initial_config=initial_config)
assert result.should_save is True
assert result.config.agents.defaults.bot_name == "quickbot"
def test_quick_start_configures_primary_preset_and_telegram(self, monkeypatch):
"""Quick Start should set only the minimum provider, model and channel fields."""
config = Config()
selections = iter(["Telegram", "OpenRouter"])
def fake_select_with_back(*_args, **_kwargs):
return next(selections)
def fake_input(prompt, *_args, **_kwargs):
if "API key" in prompt:
return "sk-or-test"
if "Telegram bot token" in prompt:
return "123:abc"
raise AssertionError(prompt)
monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None)
monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", fake_select_with_back)
monkeypatch.setattr(onboard_wizard, "_input_with_existing", fake_input)
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *_args, **_kwargs: "anthropic/claude-sonnet-4.5",
)
monkeypatch.setattr(onboard_wizard, "_print_summary_panel", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_pause", lambda: None)
onboard_wizard._configure_quick_start(config)
assert config.providers.openrouter.api_key == "sk-or-test"
assert config.providers.openrouter.api_base == "https://openrouter.ai/api/v1"
assert config.agents.defaults.model_preset == "primary"
assert config.model_presets["primary"].provider == "openrouter"
assert config.model_presets["primary"].model == "anthropic/claude-sonnet-4.5"
telegram = getattr(config.channels, "telegram")
assert telegram["enabled"] is True
assert telegram["token"] == "123:abc"
def test_quick_start_webui_enables_websocket(self, monkeypatch):
"""The recommended Quick Start target should create a working WebUI channel."""
config = Config()
selections = iter(["WebUI only (recommended)", "OpenRouter"])
def fake_select_with_back(*_args, **_kwargs):
return next(selections)
monkeypatch.setattr(onboard_wizard.console, "clear", lambda: None)
monkeypatch.setattr(onboard_wizard, "_show_section_header", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_select_with_back", fake_select_with_back)
monkeypatch.setattr(onboard_wizard, "_input_with_existing", lambda *a, **kw: "sk-or-test")
monkeypatch.setattr(
onboard_wizard,
"_input_model_with_autocomplete",
lambda *_args, **_kwargs: "anthropic/claude-sonnet-4.5",
)
monkeypatch.setattr(onboard_wizard, "_print_summary_panel", lambda *a, **kw: None)
monkeypatch.setattr(onboard_wizard, "_pause", lambda: None)
onboard_wizard._configure_quick_start(config)
websocket = getattr(config.channels, "websocket")
assert websocket["enabled"] is True
assert websocket["allowFrom"] == ["*"]
def test_main_menu_dispatch_includes_channel_common(self):
"""Main menu dispatch should route [H] to Channel Common."""
from nanobot.cli.onboard import run_onboard
# We verify by checking the dispatch table is set up correctly
# The menu items are defined inline in run_onboard, so we test
# that _configure_general_settings handles the new sections.
from nanobot.cli.onboard import _SETTINGS_SECTIONS, _SETTINGS_GETTER, _SETTINGS_SETTER
from nanobot.cli.onboard import _SETTINGS_GETTER, _SETTINGS_SECTIONS, _SETTINGS_SETTER
assert "Channel Common" in _SETTINGS_SECTIONS
assert "Channel Common" in _SETTINGS_GETTER
@@ -862,7 +966,7 @@ class TestMainMenuUpdate:
def test_main_menu_dispatch_includes_api_server(self):
"""Main menu dispatch should route [I] to API Server."""
from nanobot.cli.onboard import _SETTINGS_SECTIONS, _SETTINGS_GETTER, _SETTINGS_SETTER
from nanobot.cli.onboard import _SETTINGS_GETTER, _SETTINGS_SECTIONS, _SETTINGS_SETTER
assert "API Server" in _SETTINGS_SECTIONS
assert "API Server" in _SETTINGS_GETTER
@@ -1014,6 +1118,7 @@ class TestIsStrOrNone:
def test_optional_str_true(self):
from typing import Optional
from nanobot.cli.onboard import _is_str_or_none
assert _is_str_or_none(Optional[str]) is True
@@ -1035,7 +1140,7 @@ class TestConfigurePydanticModelEmptyString:
def test_optional_str_empty_string_becomes_none(self, monkeypatch):
"""Entering '' for an optional str field should set it to None."""
from pydantic import BaseModel
from nanobot.cli.onboard import _is_str_or_none
class M(BaseModel):
api_key: str | None = None