c5a4d559a2
- Added blank lines for better separation of test cases in multiple test files. - Reformatted event handling in tests for clarity and consistency. - Ensured consistent use of multi-line formatting for dictionary arguments in event handling. - Improved assertions and test descriptions for better understanding. - Updated test cases across various modules including test_stream_state, test_stream_utils, test_summarization, test_thread_selector, test_tool_error_handler, test_tui_widgets, test_ui_runtime, and test_wechat_channel.
229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
"""Tests for ccproxy_manager module."""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from EvoScientist.ccproxy_manager import (
|
|
is_ccproxy_available,
|
|
check_ccproxy_auth,
|
|
is_ccproxy_running,
|
|
start_ccproxy,
|
|
stop_ccproxy,
|
|
ensure_ccproxy,
|
|
setup_ccproxy_env,
|
|
maybe_start_ccproxy,
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# is_ccproxy_available
|
|
# =============================================================================
|
|
|
|
|
|
class TestIsCcproxyAvailable:
|
|
@patch("shutil.which", return_value="/usr/local/bin/ccproxy")
|
|
def test_found(self, mock_which):
|
|
assert is_ccproxy_available() is True
|
|
mock_which.assert_called_once_with("ccproxy")
|
|
|
|
@patch("shutil.which", return_value=None)
|
|
def test_not_found(self, mock_which):
|
|
assert is_ccproxy_available() is False
|
|
|
|
|
|
# =============================================================================
|
|
# check_ccproxy_auth
|
|
# =============================================================================
|
|
|
|
|
|
class TestCheckCcproxyAuth:
|
|
@patch("subprocess.run")
|
|
def test_valid_auth(self, mock_run):
|
|
mock_run.return_value = MagicMock(
|
|
returncode=0, stdout="Authenticated as user@example.com", stderr=""
|
|
)
|
|
valid, msg = check_ccproxy_auth()
|
|
assert valid is True
|
|
assert "Authenticated" in msg
|
|
|
|
@patch("subprocess.run")
|
|
def test_invalid_auth(self, mock_run):
|
|
mock_run.return_value = MagicMock(
|
|
returncode=1, stdout="", stderr="Not authenticated"
|
|
)
|
|
valid, msg = check_ccproxy_auth()
|
|
assert valid is False
|
|
assert "Not authenticated" in msg
|
|
|
|
@patch("subprocess.run", side_effect=FileNotFoundError)
|
|
def test_missing_binary(self, mock_run):
|
|
valid, msg = check_ccproxy_auth()
|
|
assert valid is False
|
|
assert "not found" in msg
|
|
|
|
|
|
# =============================================================================
|
|
# is_ccproxy_running
|
|
# =============================================================================
|
|
|
|
|
|
class TestIsCcproxyRunning:
|
|
@patch("httpx.get")
|
|
def test_running(self, mock_get):
|
|
mock_get.return_value = MagicMock(status_code=200)
|
|
assert is_ccproxy_running(8000) is True
|
|
|
|
@patch("httpx.get")
|
|
def test_not_running(self, mock_get):
|
|
import httpx
|
|
|
|
mock_get.side_effect = httpx.ConnectError("Connection refused")
|
|
assert is_ccproxy_running(8000) is False
|
|
|
|
|
|
# =============================================================================
|
|
# start_ccproxy
|
|
# =============================================================================
|
|
|
|
|
|
class TestStartCcproxy:
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_running")
|
|
@patch("subprocess.Popen")
|
|
def test_success(self, mock_popen, mock_running):
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
mock_popen.return_value = proc
|
|
# First call: not running, second call: running
|
|
mock_running.side_effect = [True]
|
|
|
|
result = start_ccproxy(8000)
|
|
assert result is proc
|
|
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_running", return_value=False)
|
|
@patch("EvoScientist.ccproxy_manager.time")
|
|
@patch("subprocess.Popen")
|
|
def test_timeout(self, mock_popen, mock_time, mock_running):
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
mock_popen.return_value = proc
|
|
# Simulate time passing beyond deadline
|
|
mock_time.monotonic.side_effect = [0, 0, 11]
|
|
mock_time.sleep = MagicMock()
|
|
|
|
with pytest.raises(RuntimeError, match="did not become healthy"):
|
|
start_ccproxy(8000)
|
|
|
|
@patch("subprocess.Popen", side_effect=FileNotFoundError)
|
|
def test_missing_binary(self, mock_popen):
|
|
with pytest.raises(FileNotFoundError):
|
|
start_ccproxy(8000)
|
|
|
|
|
|
# =============================================================================
|
|
# ensure_ccproxy
|
|
# =============================================================================
|
|
|
|
|
|
class TestEnsureCcproxy:
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_running", return_value=True)
|
|
def test_already_running(self, mock_running):
|
|
result = ensure_ccproxy(8000)
|
|
assert result is None
|
|
|
|
@patch("EvoScientist.ccproxy_manager.start_ccproxy")
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_running", return_value=False)
|
|
def test_needs_start(self, mock_running, mock_start):
|
|
proc = MagicMock()
|
|
mock_start.return_value = proc
|
|
result = ensure_ccproxy(8000)
|
|
assert result is proc
|
|
mock_start.assert_called_once_with(8000)
|
|
|
|
|
|
# =============================================================================
|
|
# setup_ccproxy_env
|
|
# =============================================================================
|
|
|
|
|
|
class TestSetupCcproxyEnv:
|
|
def test_sets_vars(self, monkeypatch):
|
|
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
|
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
|
|
|
|
setup_ccproxy_env(8000)
|
|
|
|
assert os.environ["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:8000/claude"
|
|
assert os.environ["ANTHROPIC_API_KEY"] == "ccproxy-oauth"
|
|
|
|
def test_overrides_existing(self, monkeypatch):
|
|
"""Force-sets vars even if already configured (oauth takes priority)."""
|
|
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://custom:9999")
|
|
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-real-key")
|
|
|
|
setup_ccproxy_env(8000)
|
|
|
|
assert os.environ["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:8000/claude"
|
|
assert os.environ["ANTHROPIC_API_KEY"] == "ccproxy-oauth"
|
|
|
|
|
|
# =============================================================================
|
|
# stop_ccproxy
|
|
# =============================================================================
|
|
|
|
|
|
class TestStopCcproxy:
|
|
def test_none_is_noop(self):
|
|
stop_ccproxy(None) # Should not raise
|
|
|
|
def test_terminates_process(self):
|
|
proc = MagicMock()
|
|
stop_ccproxy(proc)
|
|
proc.terminate.assert_called_once()
|
|
proc.wait.assert_called_once_with(timeout=5)
|
|
|
|
|
|
# =============================================================================
|
|
# maybe_start_ccproxy
|
|
# =============================================================================
|
|
|
|
|
|
class TestMaybeStartCcproxy:
|
|
def test_api_key_mode_noop(self):
|
|
config = MagicMock()
|
|
config.anthropic_auth_mode = "api_key"
|
|
assert maybe_start_ccproxy(config) is None
|
|
|
|
@patch("EvoScientist.ccproxy_manager.setup_ccproxy_env")
|
|
@patch("EvoScientist.ccproxy_manager.ensure_ccproxy")
|
|
@patch("EvoScientist.ccproxy_manager.check_ccproxy_auth", return_value=(True, "OK"))
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True)
|
|
def test_oauth_mode_starts(self, mock_avail, mock_auth, mock_ensure, mock_env):
|
|
proc = MagicMock()
|
|
mock_ensure.return_value = proc
|
|
config = MagicMock()
|
|
config.anthropic_auth_mode = "oauth"
|
|
|
|
result = maybe_start_ccproxy(config)
|
|
assert result is proc
|
|
mock_env.assert_called_once()
|
|
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=False)
|
|
def test_oauth_mode_raises_no_binary(self, mock_avail):
|
|
config = MagicMock()
|
|
config.anthropic_auth_mode = "oauth"
|
|
with pytest.raises(RuntimeError, match="not found"):
|
|
maybe_start_ccproxy(config)
|
|
|
|
@patch(
|
|
"EvoScientist.ccproxy_manager.check_ccproxy_auth",
|
|
return_value=(False, "expired"),
|
|
)
|
|
@patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True)
|
|
def test_oauth_mode_raises_no_auth(self, mock_avail, mock_auth):
|
|
config = MagicMock()
|
|
config.anthropic_auth_mode = "oauth"
|
|
with pytest.raises(RuntimeError, match="not authenticated"):
|
|
maybe_start_ccproxy(config)
|