/ tests / hermes_cli / test_opencode_kimi_oauth_provider.py
test_opencode_kimi_oauth_provider.py
  1  """Tests for the OpenCode Kimi OAuth provider bridge."""
  2  
  3  import subprocess
  4  from unittest.mock import patch
  5  
  6  from hermes_cli.auth import resolve_external_process_provider_credentials
  7  from hermes_cli.model_switch import list_authenticated_providers, switch_model
  8  from hermes_cli.runtime_provider import resolve_runtime_provider
  9  
 10  
 11  _AUTH_LIST = "kimi-for-coding-oauth oauth\n"
 12  
 13  
 14  def _opencode_auth_completed_process() -> subprocess.CompletedProcess[str]:
 15      return subprocess.CompletedProcess(
 16          args=["opencode", "auth", "list"],
 17          returncode=0,
 18          stdout=_AUTH_LIST,
 19          stderr="",
 20      )
 21  
 22  
 23  def test_opencode_kimi_oauth_appears_in_model_picker_when_opencode_auth_exists(monkeypatch):
 24      """Hermes /model should list OpenCode Kimi OAuth when OpenCode auth is present."""
 25      for key in ("KIMI_API_KEY", "KIMI_CODING_API_KEY", "KIMI_BASE_URL"):
 26          monkeypatch.delenv(key, raising=False)
 27  
 28      with patch("agent.models_dev.fetch_models_dev", return_value={}), \
 29           patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \
 30           patch("subprocess.run", return_value=_opencode_auth_completed_process()):
 31          providers = list_authenticated_providers(current_provider="openai-codex", max_models=50)
 32  
 33      opencode_kimi = next((p for p in providers if p["slug"] == "opencode-kimi-oauth"), None)
 34  
 35      assert opencode_kimi is not None
 36      assert opencode_kimi["name"] == "OpenCode Kimi OAuth"
 37      assert opencode_kimi["models"] == ["kimi-for-coding"]
 38      assert opencode_kimi["total_models"] == 1
 39  
 40  
 41  def test_opencode_kimi_oauth_runtime_uses_opencode_acp(monkeypatch):
 42      """Runtime credentials should launch OpenCode ACP without reading OAuth secrets."""
 43      monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False)
 44      monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False)
 45      monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False)
 46      monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False)
 47  
 48      with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \
 49           patch("subprocess.run", return_value=_opencode_auth_completed_process()):
 50          creds = resolve_external_process_provider_credentials("opencode-kimi-oauth")
 51  
 52      assert creds["provider"] == "opencode-kimi-oauth"
 53      assert creds["api_key"] == "***"
 54      assert creds["base_url"] == "acp://opencode"
 55      assert creds["command"] == "/home/user/.local/bin/opencode"
 56      assert creds["args"] == ["acp"]
 57  
 58  
 59  def test_opencode_kimi_oauth_resolves_for_agent_runtime(monkeypatch):
 60      """The shared runtime resolver should expose command/args to AIAgent."""
 61      monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False)
 62      monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False)
 63      monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False)
 64      monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False)
 65  
 66      with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \
 67           patch("subprocess.run", return_value=_opencode_auth_completed_process()):
 68          runtime = resolve_runtime_provider(
 69              requested="opencode-kimi-oauth",
 70              target_model="kimi-for-coding",
 71          )
 72  
 73      assert runtime["provider"] == "opencode-kimi-oauth"
 74      assert runtime["api_mode"] == "chat_completions"
 75      assert runtime["base_url"] == "acp://opencode"
 76      assert runtime["api_key"] == "***"
 77      assert runtime["command"] == "/home/user/.local/bin/opencode"
 78      assert runtime["args"] == ["acp"]
 79  
 80  
 81  def test_switch_model_accepts_opencode_kimi_oauth_provider(monkeypatch):
 82      """/model kimi-for-coding --provider opencode-kimi-oauth should switch cleanly."""
 83      monkeypatch.delenv("HERMES_OPENCODE_ACP_COMMAND", raising=False)
 84      monkeypatch.delenv("OPENCODE_CLI_PATH", raising=False)
 85      monkeypatch.delenv("HERMES_OPENCODE_ACP_ARGS", raising=False)
 86      monkeypatch.delenv("OPENCODE_KIMI_ACP_BASE_URL", raising=False)
 87  
 88      with patch("hermes_cli.auth.shutil.which", return_value="/home/user/.local/bin/opencode"), \
 89           patch("subprocess.run", return_value=_opencode_auth_completed_process()):
 90          result = switch_model(
 91              "kimi-for-coding",
 92              current_provider="openai-codex",
 93              current_model="gpt-5.4",
 94              explicit_provider="opencode-kimi-oauth",
 95          )
 96  
 97      assert result.success is True
 98      assert result.target_provider == "opencode-kimi-oauth"
 99      assert result.new_model == "kimi-for-coding"
100      assert result.base_url == "acp://opencode"
101      assert result.api_key == "***"
102      assert result.command == "/home/user/.local/bin/opencode"
103      assert result.args == ["acp"]