- claude_settings: 目标文件改为全局 ~/.claude/settings.json,与 cc-switch 行为一致 - claude_settings: 切回 claude 时保留用户已有的 ANTHROPIC_API_KEY - backend: 修正 save_project_settings -> save_settings,提示文本同步 - test_backends: 清理未用 import - test_entrypoints: 新增 backend 命令注册测试 - 新增 test_claude_settings / test_secrets / test_backend_command(共 36 tests)
186 lines
7.5 KiB
Python
186 lines
7.5 KiB
Python
"""Tests for myagents.commands.backend."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from myagents import claude_settings as claude_settings_mod
|
|
from myagents import secrets as secrets_mod
|
|
from myagents.commands import backend as backend_mod
|
|
|
|
|
|
@pytest.fixture()
|
|
def fake_config_dirs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Path]:
|
|
xiaohe_dir = tmp_path / ".xiaohe" / "agent"
|
|
claude_dir = tmp_path / ".claude"
|
|
monkeypatch.setattr(secrets_mod, "XIAOHE_CONFIG_DIR", xiaohe_dir)
|
|
monkeypatch.setattr(secrets_mod, "XIAOHE_CONFIG_PATH", xiaohe_dir / "config.json")
|
|
monkeypatch.setattr(
|
|
claude_settings_mod, "_settings_path", lambda: claude_dir / "settings.json"
|
|
)
|
|
return {"xiaohe": xiaohe_dir, "claude": claude_dir}
|
|
|
|
|
|
class TestBackendList:
|
|
def test_list_shows_providers(self) -> None:
|
|
result = CliRunner().invoke(backend_mod.backend_cmd, ["list"])
|
|
assert result.exit_code == 0
|
|
assert "deepseek" in result.output
|
|
assert "kimi" in result.output
|
|
assert "kimi-code" in result.output
|
|
assert "claude" in result.output
|
|
|
|
|
|
class TestBackendCurrent:
|
|
def test_current_shows_default_when_unset(
|
|
self, fake_config_dirs: dict[str, Path]
|
|
) -> None:
|
|
result = CliRunner().invoke(backend_mod.backend_cmd, ["current"])
|
|
assert result.exit_code == 0
|
|
assert "Claude (default / not configured)" in result.output
|
|
|
|
|
|
class TestBackendUse:
|
|
def test_use_deepseek_stores_env(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "deepseek", "--key", "sk-test", "--yes"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
settings = json.loads(settings_path.read_text())
|
|
assert settings["env"]["ANTHROPIC_AUTH_TOKEN"] == "sk-test"
|
|
assert settings["env"]["ANTHROPIC_BASE_URL"] == "https://api.deepseek.com/anthropic"
|
|
|
|
def test_use_kimi_code(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "kimi-code", "--key", "sk-test", "--yes"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
settings = json.loads(settings_path.read_text())
|
|
assert settings["env"]["ANTHROPIC_BASE_URL"] == "https://api.kimi.com/coding"
|
|
assert settings["env"]["ANTHROPIC_MODEL"] == "kimi-for-coding"
|
|
|
|
def test_use_unknown_provider_errors(self) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd, ["use", "openai", "--yes"]
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "Unknown backend" in result.output
|
|
|
|
def test_use_without_key_prompts(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "deepseek", "--yes"],
|
|
input="sk-from-prompt\n",
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
settings = json.loads(settings_path.read_text())
|
|
assert settings["env"]["ANTHROPIC_AUTH_TOKEN"] == "sk-from-prompt"
|
|
|
|
def test_use_cancelled_does_not_write(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "deepseek", "--key", "sk-test"],
|
|
input="n\n",
|
|
)
|
|
assert result.exit_code == 0
|
|
assert "Cancelled" in result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
assert not settings_path.exists()
|
|
|
|
def test_use_model_override(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "deepseek", "--key", "sk-test", "--model", "custom-model", "--yes"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
settings = json.loads(settings_path.read_text())
|
|
assert settings["env"]["ANTHROPIC_MODEL"] == "custom-model"
|
|
|
|
def test_use_claude_clears_third_party(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
fake_config_dirs["claude"].mkdir(parents=True)
|
|
settings_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"env": {
|
|
"ANTHROPIC_AUTH_TOKEN": "sk-test",
|
|
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
|
|
}
|
|
}
|
|
)
|
|
)
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd, ["use", "claude", "--yes"]
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
settings = json.loads(settings_path.read_text())
|
|
assert "env" not in settings
|
|
|
|
|
|
class TestBackendReset:
|
|
def test_reset_alias(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_cmd,
|
|
["use", "deepseek", "--key", "sk-test", "--yes"],
|
|
)
|
|
assert result.exit_code == 0
|
|
result = CliRunner().invoke(backend_mod.backend_cmd, ["reset", "--yes"])
|
|
assert result.exit_code == 0, result.output
|
|
settings_path = fake_config_dirs["claude"] / "settings.json"
|
|
settings = json.loads(settings_path.read_text())
|
|
assert "ANTHROPIC_AUTH_TOKEN" not in settings.get("env", {})
|
|
|
|
|
|
class TestBackendKeySet:
|
|
def test_key_set_stores_in_xiaohe_config(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_key,
|
|
["set", "deepseek", "--key", "sk-test"],
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
config_path = fake_config_dirs["xiaohe"] / "config.json"
|
|
data = json.loads(config_path.read_text())
|
|
assert data["keys"]["deepseek"] == "sk-test"
|
|
|
|
def test_key_set_for_claude_errors(self) -> None:
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_key,
|
|
["set", "claude", "--key", "sk-test"],
|
|
)
|
|
assert result.exit_code != 0
|
|
assert "ANTHROPIC_API_KEY" in result.output
|
|
|
|
|
|
class TestBackendKeyRm:
|
|
def test_key_rm_removes_stored_key(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
config_path = fake_config_dirs["xiaohe"] / "config.json"
|
|
fake_config_dirs["xiaohe"].mkdir(parents=True)
|
|
config_path.write_text(json.dumps({"keys": {"deepseek": "sk-test"}}))
|
|
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_key, ["rm", "deepseek", "--yes"]
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
data = json.loads(config_path.read_text())
|
|
assert "deepseek" not in data.get("keys", {})
|
|
|
|
def test_key_rm_without_yes_confirms(self, fake_config_dirs: dict[str, Path]) -> None:
|
|
config_path = fake_config_dirs["xiaohe"] / "config.json"
|
|
fake_config_dirs["xiaohe"].mkdir(parents=True)
|
|
config_path.write_text(json.dumps({"keys": {"deepseek": "sk-test"}}))
|
|
|
|
result = CliRunner().invoke(
|
|
backend_mod.backend_key, ["rm", "deepseek"], input="y\n"
|
|
)
|
|
assert result.exit_code == 0, result.output
|
|
data = json.loads(config_path.read_text())
|
|
assert "deepseek" not in data.get("keys", {})
|