feat: MCP toolsets in WebUI + onboarding fix for non-standard providers — v0.50.63
Squash-merges PR #578 (rebased from #574 by @renheqiang + #575 by @nesquena-hermes). MCP server toolsets now included in WebUI sessions; onboarding wizard no longer fires for non-standard providers. 1331 tests pass. Nathan override applied for self-built #575.
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Hermes Web UI -- Changelog
|
# Hermes Web UI -- Changelog
|
||||||
|
|
||||||
|
## [v0.50.63] — 2026-04-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Onboarding wizard no longer fires for non-standard providers** — providers outside the quick-setup list (`minimax-cn`, `deepseek`, `xai`, `gemini`, etc.) were always evaluated as `chat_ready=False` because `_provider_api_key_present()` only knew the four built-in env-var names. Those users saw the wizard on every page load and risked `config.yaml` being silently overwritten if the provider dropdown defaulted. The fix adds a `hermes_cli.auth.get_auth_status()` fallback covering every API-key provider in the full registry, and tightens the frontend guard so an unchanged unsupported-provider form never POSTs. (Fixes #572, PR #575)
|
||||||
|
- **MCP server toolsets now included in WebUI agent sessions** — previously the WebUI read `platform_toolsets.cli` directly from `config.yaml`, which only carries built-in toolset names. MCP server names (`tidb`, `kyuubi`, etc.) were silently dropped, so MCP tools configured via `~/.hermes/config.yaml` were unavailable in chat. The fix delegates to `hermes_cli.tools_config._get_platform_tools()` — the same code the CLI uses — which merges all enabled MCP servers automatically. Falls back gracefully when `hermes_cli` is unavailable. (PR #574 by @renheqiang)
|
||||||
|
|
||||||
## [v0.50.62] — 2026-04-16
|
## [v0.50.62] — 2026-04-16
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -342,7 +342,6 @@ MAX_UPLOAD_BYTES = 20 * 1024 * 1024
|
|||||||
# ── File type maps ───────────────────────────────────────────────────────────
|
# ── File type maps ───────────────────────────────────────────────────────────
|
||||||
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", ".bmp"}
|
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", ".bmp"}
|
||||||
MD_EXTS = {".md", ".markdown", ".mdown"}
|
MD_EXTS = {".md", ".markdown", ".mdown"}
|
||||||
OFFICE_EXTS = {".xls", ".xlsx", ".doc", ".docx"}
|
|
||||||
CODE_EXTS = {
|
CODE_EXTS = {
|
||||||
".py",
|
".py",
|
||||||
".js",
|
".js",
|
||||||
@@ -404,7 +403,19 @@ _DEFAULT_TOOLSETS = [
|
|||||||
"web",
|
"web",
|
||||||
"webhook",
|
"webhook",
|
||||||
]
|
]
|
||||||
CLI_TOOLSETS = get_config().get("platform_toolsets", {}).get("cli", _DEFAULT_TOOLSETS)
|
def _resolve_cli_toolsets(cfg=None):
|
||||||
|
"""Resolve CLI toolsets using the agent's _get_platform_tools() so that
|
||||||
|
MCP server toolsets are automatically included, matching CLI behaviour."""
|
||||||
|
if cfg is None:
|
||||||
|
cfg = get_config()
|
||||||
|
try:
|
||||||
|
from hermes_cli.tools_config import _get_platform_tools
|
||||||
|
return list(_get_platform_tools(cfg, "cli"))
|
||||||
|
except Exception:
|
||||||
|
# Fallback: read raw list from config (MCP toolsets will be missing)
|
||||||
|
return cfg.get("platform_toolsets", {}).get("cli", _DEFAULT_TOOLSETS)
|
||||||
|
|
||||||
|
CLI_TOOLSETS = _resolve_cli_toolsets()
|
||||||
|
|
||||||
# ── Model / provider discovery ───────────────────────────────────────────────
|
# ── Model / provider discovery ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -210,6 +210,22 @@ def _provider_api_key_present(
|
|||||||
and str(custom_cfg.get("api_key") or "").strip()
|
and str(custom_cfg.get("api_key") or "").strip()
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# For providers not in _SUPPORTED_PROVIDER_SETUPS (e.g. minimax-cn, deepseek,
|
||||||
|
# xai, etc.), ask the hermes_cli auth registry — it knows every provider's env
|
||||||
|
# var names and can check os.environ for a valid key.
|
||||||
|
# Exclude known OAuth/token-flow providers — those are handled separately by
|
||||||
|
# _provider_oauth_authenticated() and should not be short-circuited here.
|
||||||
|
_known_oauth = {"openai-codex", "copilot", "copilot-acp", "qwen-oauth", "nous"}
|
||||||
|
if provider not in _SUPPORTED_PROVIDER_SETUPS and provider not in _known_oauth:
|
||||||
|
try:
|
||||||
|
from hermes_cli.auth import get_auth_status as _gas
|
||||||
|
status = _gas(provider)
|
||||||
|
if isinstance(status, dict) and status.get("logged_in"):
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -288,11 +304,13 @@ def _status_from_runtime(cfg: dict, imports_ok: bool) -> dict:
|
|||||||
elif provider in _SUPPORTED_PROVIDER_SETUPS:
|
elif provider in _SUPPORTED_PROVIDER_SETUPS:
|
||||||
provider_ready = _provider_api_key_present(provider, cfg, env_values)
|
provider_ready = _provider_api_key_present(provider, cfg, env_values)
|
||||||
else:
|
else:
|
||||||
# Unknown / OAuth provider (e.g. openai-codex, copilot, qwen-oauth).
|
# Unknown provider — may be an OAuth flow (openai-codex, copilot, etc.)
|
||||||
# These do not use a plain API key; auth lives in auth.json or a
|
# OR an API-key provider not in the quick-setup list (minimax-cn, deepseek,
|
||||||
# credential pool managed by hermes_cli.
|
# xai, etc.). Check both: api key presence first (covers the majority of
|
||||||
provider_ready = _provider_oauth_authenticated(
|
# third-party providers), then OAuth auth.json.
|
||||||
provider, _get_active_hermes_home()
|
provider_ready = (
|
||||||
|
_provider_api_key_present(provider, cfg, env_values)
|
||||||
|
or _provider_oauth_authenticated(provider, _get_active_hermes_home())
|
||||||
)
|
)
|
||||||
|
|
||||||
chat_ready = bool(_HERMES_FOUND and imports_ok and provider_ready)
|
chat_ready = bool(_HERMES_FOUND and imports_ok and provider_ready)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from api.config import (
|
|||||||
STREAMS_LOCK,
|
STREAMS_LOCK,
|
||||||
CANCEL_FLAGS,
|
CANCEL_FLAGS,
|
||||||
SERVER_START_TIME,
|
SERVER_START_TIME,
|
||||||
CLI_TOOLSETS,
|
_resolve_cli_toolsets,
|
||||||
_INDEX_HTML_PATH,
|
_INDEX_HTML_PATH,
|
||||||
get_available_models,
|
get_available_models,
|
||||||
IMAGE_EXTS,
|
IMAGE_EXTS,
|
||||||
@@ -2070,7 +2070,7 @@ def _handle_chat_sync(handler, body):
|
|||||||
api_key=_api_key,
|
api_key=_api_key,
|
||||||
platform="cli",
|
platform="cli",
|
||||||
quiet_mode=True,
|
quiet_mode=True,
|
||||||
enabled_toolsets=CLI_TOOLSETS,
|
enabled_toolsets=_resolve_cli_toolsets(),
|
||||||
session_id=s.session_id,
|
session_id=s.session_id,
|
||||||
)
|
)
|
||||||
workspace_ctx = f"[Workspace: {s.workspace}]\n"
|
workspace_ctx = f"[Workspace: {s.workspace}]\n"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from typing import Optional
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
from api.config import (
|
from api.config import (
|
||||||
STREAMS, STREAMS_LOCK, CANCEL_FLAGS, AGENT_INSTANCES, CLI_TOOLSETS,
|
STREAMS, STREAMS_LOCK, CANCEL_FLAGS, AGENT_INSTANCES,
|
||||||
LOCK, SESSIONS, SESSION_DIR,
|
LOCK, SESSIONS, SESSION_DIR,
|
||||||
_get_session_agent_lock, _set_thread_env, _clear_thread_env,
|
_get_session_agent_lock, _set_thread_env, _clear_thread_env,
|
||||||
resolve_model_provider,
|
resolve_model_provider,
|
||||||
@@ -804,9 +804,10 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta
|
|||||||
from api.config import get_config as _get_config
|
from api.config import get_config as _get_config
|
||||||
_cfg = _get_config()
|
_cfg = _get_config()
|
||||||
|
|
||||||
# Per-profile toolsets (fall back to module-level CLI_TOOLSETS)
|
# Per-profile toolsets — use _resolve_cli_toolsets() so MCP
|
||||||
_pt = _cfg.get('platform_toolsets', {})
|
# server toolsets are included, matching native CLI behaviour.
|
||||||
_toolsets = _pt.get('cli', CLI_TOOLSETS) if isinstance(_pt, dict) else CLI_TOOLSETS
|
from api.config import _resolve_cli_toolsets
|
||||||
|
_toolsets = _resolve_cli_toolsets(_cfg)
|
||||||
|
|
||||||
# Fallback model from profile config (e.g. for rate-limit recovery)
|
# Fallback model from profile config (e.g. for rate-limit recovery)
|
||||||
_fallback = _cfg.get('fallback_model') or None
|
_fallback = _cfg.get('fallback_model') or None
|
||||||
|
|||||||
@@ -553,7 +553,7 @@
|
|||||||
<div class="settings-section-title">System</div>
|
<div class="settings-section-title">System</div>
|
||||||
<div class="settings-section-meta">Instance version and access controls.</div>
|
<div class="settings-section-meta">Instance version and access controls.</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="settings-version-badge">v0.50.62</span>
|
<span class="settings-version-badge">v0.50.63</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
<div class="settings-field" style="border-top:1px solid var(--border);padding-top:12px;margin-top:8px">
|
||||||
<label for="settingsPassword" data-i18n="settings_label_password">Access Password</label>
|
<label for="settingsPassword" data-i18n="settings_label_password">Access Password</label>
|
||||||
|
|||||||
@@ -296,7 +296,14 @@ async function _saveOnboardingProviderSetup(){
|
|||||||
const baseUrl=(ONBOARDING.form.baseUrl||'').trim();
|
const baseUrl=(ONBOARDING.form.baseUrl||'').trim();
|
||||||
const current=_getOnboardingCurrentSetup();
|
const current=_getOnboardingCurrentSetup();
|
||||||
const isUnchanged=current.provider===provider&&((current.model||'')===model)&&((current.base_url||'')===baseUrl);
|
const isUnchanged=current.provider===provider&&((current.model||'')===model)&&((current.base_url||'')===baseUrl);
|
||||||
if(isUnchanged && !apiKey && (ONBOARDING.status.system||{}).chat_ready) return;
|
// Skip the POST when nothing changed. We also skip when the provider is
|
||||||
|
// unsupported/OAuth-based and already working — chat_ready may be false for
|
||||||
|
// providers not in the quick-setup list (e.g. minimax-cn) even though they are
|
||||||
|
// fully configured. Posting in that case would either be a no-op (the server
|
||||||
|
// just marks complete for unsupported providers) or could silently overwrite
|
||||||
|
// config.yaml if the user accidentally changed the provider dropdown.
|
||||||
|
const currentIsOauth=!!(ONBOARDING.status&&ONBOARDING.status.setup&&ONBOARDING.status.setup.current_is_oauth);
|
||||||
|
if(isUnchanged && !apiKey && ((ONBOARDING.status.system||{}).chat_ready || currentIsOauth)) return;
|
||||||
const body={provider,model};
|
const body={provider,model};
|
||||||
if(apiKey) body.api_key=apiKey;
|
if(apiKey) body.api_key=apiKey;
|
||||||
if(baseUrl) body.base_url=baseUrl;
|
if(baseUrl) body.base_url=baseUrl;
|
||||||
|
|||||||
205
tests/test_issue572.py
Normal file
205
tests/test_issue572.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
"""Tests for issue #572: onboarding must not fire or overwrite config for
|
||||||
|
providers not in the quick-setup list (minimax-cn, deepseek, xai, etc.).
|
||||||
|
|
||||||
|
Root cause: _provider_api_key_present() only knew about the four providers in
|
||||||
|
_SUPPORTED_PROVIDER_SETUPS. For any other provider it returned False, causing
|
||||||
|
chat_ready=False, which made the wizard fire even when the user was fully
|
||||||
|
configured. The second part of the fix ensures _saveOnboardingProviderSetup()
|
||||||
|
in the frontend also skips the POST when current_is_oauth is set.
|
||||||
|
|
||||||
|
Covers:
|
||||||
|
1. _provider_api_key_present returns True for minimax-cn when
|
||||||
|
MINIMAX_CN_API_KEY is in env (via hermes_cli.auth.get_auth_status)
|
||||||
|
2. _status_from_runtime gives chat_ready=True for minimax-cn with a key set
|
||||||
|
3. get_onboarding_status returns completed=True for a fully-configured
|
||||||
|
unsupported provider when config.yaml exists
|
||||||
|
4. The hermes_cli import failure path is safe (falls back gracefully)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _inject_hermes_cli_auth(get_auth_status_return):
|
||||||
|
"""Inject a minimal hermes_cli.auth stub into sys.modules.
|
||||||
|
|
||||||
|
CI doesn't install hermes_cli (it's a separate package). Tests that
|
||||||
|
exercise the hermes_cli fallback path must inject the module themselves
|
||||||
|
rather than relying on mock.patch('hermes_cli.auth.get_auth_status')
|
||||||
|
which fails with ModuleNotFoundError when the module isn't installed.
|
||||||
|
"""
|
||||||
|
mock_auth = types.ModuleType("hermes_cli.auth")
|
||||||
|
mock_auth.get_auth_status = mock.MagicMock(return_value=get_auth_status_return)
|
||||||
|
mock_hermes_cli = types.ModuleType("hermes_cli")
|
||||||
|
|
||||||
|
return mock.patch.dict(sys.modules, {
|
||||||
|
"hermes_cli": mock_hermes_cli,
|
||||||
|
"hermes_cli.auth": mock_auth,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helper
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _call_provider_api_key_present(provider: str, cfg: dict = None, env_values: dict = None):
|
||||||
|
from api.onboarding import _provider_api_key_present
|
||||||
|
return _provider_api_key_present(provider, cfg or {}, env_values or {})
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. _provider_api_key_present via hermes_cli fallback
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestProviderApiKeyPresentFallback:
|
||||||
|
|
||||||
|
def test_minimax_cn_logged_in_returns_true(self):
|
||||||
|
"""minimax-cn: if hermes_cli.auth.get_auth_status returns logged_in, must be True."""
|
||||||
|
with mock.patch("api.onboarding._SUPPORTED_PROVIDER_SETUPS", {
|
||||||
|
"openrouter": {}, "anthropic": {}, "openai": {}, "custom": {}
|
||||||
|
}):
|
||||||
|
with _inject_hermes_cli_auth({"logged_in": True}):
|
||||||
|
result = _call_provider_api_key_present("minimax-cn")
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_unsupported_provider_logged_out_returns_false(self):
|
||||||
|
"""Unsupported provider with no key → False, no crash."""
|
||||||
|
with mock.patch("api.onboarding._SUPPORTED_PROVIDER_SETUPS", {
|
||||||
|
"openrouter": {}, "anthropic": {}, "openai": {}, "custom": {}
|
||||||
|
}):
|
||||||
|
with _inject_hermes_cli_auth({"logged_in": False}):
|
||||||
|
result = _call_provider_api_key_present("deepseek")
|
||||||
|
assert result is False
|
||||||
|
|
||||||
|
def test_hermes_cli_import_failure_is_safe(self):
|
||||||
|
"""If hermes_cli is unavailable, falls back silently to False."""
|
||||||
|
import builtins
|
||||||
|
real_import = builtins.__import__
|
||||||
|
|
||||||
|
def _block_hermes_cli(name, *args, **kwargs):
|
||||||
|
if name.startswith("hermes_cli"):
|
||||||
|
raise ImportError("hermes_cli not available")
|
||||||
|
return real_import(name, *args, **kwargs)
|
||||||
|
|
||||||
|
with mock.patch("api.onboarding._SUPPORTED_PROVIDER_SETUPS", {
|
||||||
|
"openrouter": {}, "anthropic": {}, "openai": {}, "custom": {}
|
||||||
|
}):
|
||||||
|
with mock.patch("builtins.__import__", side_effect=_block_hermes_cli):
|
||||||
|
result = _call_provider_api_key_present("minimax-cn")
|
||||||
|
assert result is False # safe fallback
|
||||||
|
|
||||||
|
def test_supported_provider_still_works_without_fallback(self):
|
||||||
|
"""openrouter with env key must still succeed via the original path."""
|
||||||
|
from api.onboarding import _provider_api_key_present, _SUPPORTED_PROVIDER_SETUPS
|
||||||
|
env_values = {"OPENROUTER_API_KEY": "sk-test"}
|
||||||
|
result = _provider_api_key_present("openrouter", {}, env_values)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
def test_inline_api_key_in_cfg_still_works(self):
|
||||||
|
"""model.api_key in config.yaml must be recognized for any provider."""
|
||||||
|
cfg = {"model": {"provider": "minimax-cn", "default": "MiniMax-M2.7", "api_key": "key123"}}
|
||||||
|
result = _call_provider_api_key_present("minimax-cn", cfg)
|
||||||
|
assert result is True
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. _status_from_runtime: unsupported provider with key → chat_ready=True
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestStatusFromRuntimeUnsupportedProvider:
|
||||||
|
|
||||||
|
def _run(self, provider: str, model: str, api_key_present: bool, oauth_present: bool = False):
|
||||||
|
from api.onboarding import _status_from_runtime
|
||||||
|
cfg = {"model": {"provider": provider, "default": model}}
|
||||||
|
with (
|
||||||
|
mock.patch("api.onboarding._HERMES_FOUND", True),
|
||||||
|
mock.patch("api.onboarding._load_env_file", return_value={}),
|
||||||
|
mock.patch("api.onboarding._get_active_hermes_home", return_value=pathlib.Path("/tmp")),
|
||||||
|
mock.patch("api.onboarding._provider_api_key_present", return_value=api_key_present),
|
||||||
|
mock.patch("api.onboarding._provider_oauth_authenticated", return_value=oauth_present),
|
||||||
|
):
|
||||||
|
return _status_from_runtime(cfg, True)
|
||||||
|
|
||||||
|
def test_minimax_cn_with_key_gives_chat_ready(self):
|
||||||
|
"""minimax-cn + api key present → chat_ready must be True."""
|
||||||
|
result = self._run("minimax-cn", "MiniMax-M2.7", api_key_present=True)
|
||||||
|
assert result["chat_ready"] is True, f"Expected chat_ready=True, got: {result}"
|
||||||
|
assert result["provider_ready"] is True
|
||||||
|
assert result["setup_state"] == "ready"
|
||||||
|
|
||||||
|
def test_deepseek_with_key_gives_chat_ready(self):
|
||||||
|
"""deepseek + api key → chat_ready."""
|
||||||
|
result = self._run("deepseek", "deepseek-chat", api_key_present=True)
|
||||||
|
assert result["chat_ready"] is True
|
||||||
|
|
||||||
|
def test_unsupported_provider_no_key_no_oauth_gives_not_ready(self):
|
||||||
|
"""No key, no oauth → provider_ready=False."""
|
||||||
|
result = self._run("minimax-cn", "MiniMax-M2.7", api_key_present=False, oauth_present=False)
|
||||||
|
assert result["chat_ready"] is False
|
||||||
|
assert result["provider_ready"] is False
|
||||||
|
|
||||||
|
def test_oauth_provider_still_works_via_oauth_path(self):
|
||||||
|
"""openai-codex (OAuth) with no api_key but oauth present → ready."""
|
||||||
|
result = self._run("openai-codex", "codex-model", api_key_present=False, oauth_present=True)
|
||||||
|
assert result["chat_ready"] is True
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. get_onboarding_status: minimax-cn fully configured → completed=True
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestOnboardingStatusUnsupportedProvider:
|
||||||
|
|
||||||
|
def _make_status(self, chat_ready: bool, provider: str = "minimax-cn"):
|
||||||
|
import api.onboarding as mod
|
||||||
|
fake_config_path = pathlib.Path("/tmp/_test_572_config.yaml")
|
||||||
|
cfg = {"model": {"provider": provider, "default": "MiniMax-M2.7"}}
|
||||||
|
runtime = {
|
||||||
|
"chat_ready": chat_ready,
|
||||||
|
"provider_configured": True,
|
||||||
|
"provider_ready": chat_ready,
|
||||||
|
"setup_state": "ready" if chat_ready else "provider_incomplete",
|
||||||
|
"provider_note": "test",
|
||||||
|
"current_provider": provider,
|
||||||
|
"current_model": "MiniMax-M2.7",
|
||||||
|
"current_base_url": None,
|
||||||
|
"env_path": "/tmp/.env",
|
||||||
|
}
|
||||||
|
with (
|
||||||
|
mock.patch.object(mod, "load_settings", return_value={}),
|
||||||
|
mock.patch.object(mod, "get_config", return_value=cfg),
|
||||||
|
mock.patch.object(mod, "verify_hermes_imports", return_value=(True, [], {})),
|
||||||
|
mock.patch.object(mod, "_status_from_runtime", return_value=runtime),
|
||||||
|
mock.patch.object(mod, "load_workspaces", return_value=[]),
|
||||||
|
mock.patch.object(mod, "get_last_workspace", return_value=None),
|
||||||
|
mock.patch.object(mod, "get_available_models", return_value=[]),
|
||||||
|
mock.patch.object(mod, "_get_config_path", return_value=fake_config_path),
|
||||||
|
mock.patch.object(pathlib.Path, "exists", return_value=True),
|
||||||
|
):
|
||||||
|
return mod.get_onboarding_status()
|
||||||
|
|
||||||
|
def test_minimax_cn_chat_ready_skips_wizard(self):
|
||||||
|
"""minimax-cn + chat_ready=True + config.yaml exists → wizard must NOT fire."""
|
||||||
|
result = self._make_status(chat_ready=True)
|
||||||
|
assert result["completed"] is True, (
|
||||||
|
"Wizard fired for minimax-cn user with valid config! "
|
||||||
|
"config.yaml + chat_ready=True must auto-complete onboarding regardless of provider."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_minimax_cn_not_ready_shows_wizard(self):
|
||||||
|
"""minimax-cn + chat_ready=False → wizard fires so user can fix it."""
|
||||||
|
result = self._make_status(chat_ready=False)
|
||||||
|
assert result["completed"] is False
|
||||||
|
|
||||||
|
def test_current_is_oauth_set_for_unsupported_provider(self):
|
||||||
|
"""setup.current_is_oauth must be True for minimax-cn (not in quick-setup list)."""
|
||||||
|
result = self._make_status(chat_ready=True)
|
||||||
|
assert result["setup"]["current_is_oauth"] is True, (
|
||||||
|
"current_is_oauth should be True for providers not in _SUPPORTED_PROVIDER_SETUPS"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user