"""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", {})