fix: add config option for ccproxy port number (#52)
* fix: add config option for ccproxy port number * feat: add user prompt for ccproxy port configuration and validation fix: update is_ccproxy_running to use health check endpoint test: enhance tests for ccproxy port handling and validation * fix: streamline ccproxy installation process using _install_pip_package * fix: auto-patch ccproxy adapter for correct OAuth beta header --------- Co-authored-by: Xi Zhang <106144707+X-iZhang@users.noreply.github.com> Co-authored-by: X-iZhang <zacharyzhang2022@gmail.com>
This commit is contained in:
+100
-18
@@ -15,15 +15,11 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
from EvoScientist.config import EvoScientistConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_DEFAULT_PORT = 8000
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Availability & auth checks
|
||||
@@ -138,18 +134,18 @@ def check_ccproxy_auth(provider: str = "claude_api") -> tuple[bool, str]:
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def is_ccproxy_running(port: int = _DEFAULT_PORT) -> bool:
|
||||
def is_ccproxy_running(port: int) -> bool:
|
||||
"""Check if ccproxy is already serving on the given port."""
|
||||
import httpx
|
||||
|
||||
try:
|
||||
resp = httpx.get(f"http://127.0.0.1:{port}/", timeout=2.0)
|
||||
return resp.status_code < 500
|
||||
resp = httpx.get(f"http://127.0.0.1:{port}/health/live", timeout=2.0)
|
||||
return resp.status_code == 200
|
||||
except (httpx.ConnectError, httpx.TimeoutException, OSError):
|
||||
return False
|
||||
|
||||
|
||||
def start_ccproxy(port: int = _DEFAULT_PORT) -> subprocess.Popen:
|
||||
def start_ccproxy(port: int) -> subprocess.Popen:
|
||||
"""Start ccproxy serve as a background process.
|
||||
|
||||
Args:
|
||||
@@ -206,7 +202,7 @@ def stop_ccproxy(proc: subprocess.Popen | None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def ensure_ccproxy(port: int = _DEFAULT_PORT) -> subprocess.Popen | None:
|
||||
def ensure_ccproxy(port: int) -> subprocess.Popen | None:
|
||||
"""Ensure ccproxy is running — reuse existing or start new.
|
||||
|
||||
Returns:
|
||||
@@ -223,7 +219,7 @@ def ensure_ccproxy(port: int = _DEFAULT_PORT) -> subprocess.Popen | None:
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def setup_ccproxy_env(port: int = _DEFAULT_PORT) -> None:
|
||||
def setup_ccproxy_env(port: int) -> None:
|
||||
"""Set environment variables for Anthropic ccproxy routing.
|
||||
|
||||
Force-sets ``ANTHROPIC_BASE_URL`` and ``ANTHROPIC_API_KEY`` so that
|
||||
@@ -236,7 +232,7 @@ def setup_ccproxy_env(port: int = _DEFAULT_PORT) -> None:
|
||||
os.environ["ANTHROPIC_API_KEY"] = "ccproxy-oauth"
|
||||
|
||||
|
||||
def setup_codex_env(port: int = _DEFAULT_PORT) -> None:
|
||||
def setup_codex_env(port: int) -> None:
|
||||
"""Set environment variables for OpenAI/Codex ccproxy routing.
|
||||
|
||||
Force-sets ``OPENAI_BASE_URL`` and ``OPENAI_API_KEY`` so that
|
||||
@@ -255,7 +251,86 @@ def setup_codex_env(port: int = _DEFAULT_PORT) -> None:
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def maybe_start_ccproxy(config: object) -> subprocess.Popen | None:
|
||||
def _patch_ccproxy_oauth_header() -> None:
|
||||
"""Auto-patch ccproxy's adapter to send the correct OAuth beta header.
|
||||
|
||||
ccproxy 0.2.4 hardcodes ``computer-use-2025-01-24`` as the
|
||||
``anthropic-beta`` header, which causes two problems:
|
||||
- Missing ``oauth-2025-04-20`` → 401 from Anthropic
|
||||
- ``computer-use-2025-01-24`` incompatible with OAuth auth → 400
|
||||
|
||||
The ccproxy binary may use a different Python environment than the one
|
||||
running EvoScientist, so we resolve the adapter path via the ccproxy
|
||||
binary's shebang line rather than the current Python's import system.
|
||||
|
||||
This patch is idempotent and places the header AFTER cli_headers so it
|
||||
cannot be overridden.
|
||||
"""
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
try:
|
||||
ccproxy_bin = _ccproxy_exe()
|
||||
if not ccproxy_bin:
|
||||
return
|
||||
|
||||
# Find the Python interpreter used by the ccproxy binary via shebang
|
||||
shebang = pathlib.Path(ccproxy_bin).read_text().splitlines()[0]
|
||||
python_exe = shebang.lstrip("#!").strip()
|
||||
|
||||
# Ask that Python where ccproxy's adapter lives
|
||||
result = subprocess.run(
|
||||
[
|
||||
python_exe, "-c",
|
||||
"import inspect, ccproxy.plugins.claude_api.adapter as m; print(inspect.getfile(m))",
|
||||
],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return
|
||||
src_file = pathlib.Path(result.stdout.strip())
|
||||
if not src_file.exists():
|
||||
return
|
||||
|
||||
text = src_file.read_text()
|
||||
|
||||
# Check if already correctly patched (oauth header set after cli_headers)
|
||||
correct = 'filtered_headers["anthropic-beta"] = "oauth-2025-04-20"'
|
||||
cli_marker = "cli_headers = self._collect_cli_headers()"
|
||||
if correct in text:
|
||||
# Verify it's placed after cli_headers
|
||||
if text.index(correct) > text.index(cli_marker):
|
||||
return # Already correctly patched
|
||||
|
||||
# Move/replace anthropic-beta assignment to after cli_headers loop,
|
||||
# and set only oauth-2025-04-20 (computer-use-* is incompatible).
|
||||
# Step 1: remove any existing filtered_headers["anthropic-beta"] line
|
||||
patched = re.sub(
|
||||
r'\s*filtered_headers\["anthropic-beta"\]\s*=\s*"[^"]*"\n',
|
||||
"\n",
|
||||
text,
|
||||
)
|
||||
# Step 2: insert correct assignment after the cli_headers block
|
||||
insert_after = 'filtered_headers[lk] = value\n'
|
||||
replacement = (
|
||||
'filtered_headers[lk] = value\n\n'
|
||||
' # oauth-2025-04-20: required for OAuth Bearer token auth (Anthropic 2026-03)\n'
|
||||
' filtered_headers["anthropic-beta"] = "oauth-2025-04-20"\n'
|
||||
)
|
||||
patched = patched.replace(insert_after, replacement, 1)
|
||||
|
||||
if patched == text:
|
||||
return
|
||||
|
||||
src_file.write_text(patched)
|
||||
for pyc in src_file.parent.glob("__pycache__/adapter*.pyc"):
|
||||
pyc.unlink(missing_ok=True)
|
||||
logger.info("Auto-patched ccproxy adapter: set anthropic-beta=oauth-2025-04-20")
|
||||
except Exception as exc:
|
||||
logger.warning("Could not auto-patch ccproxy adapter: %s", exc)
|
||||
|
||||
|
||||
def maybe_start_ccproxy(config: EvoScientistConfig) -> subprocess.Popen | None:
|
||||
"""High-level: conditionally start ccproxy based on config.
|
||||
|
||||
Checks ``config.anthropic_auth_mode`` and ``config.openai_auth_mode``:
|
||||
@@ -301,17 +376,24 @@ def maybe_start_ccproxy(config: object) -> subprocess.Popen | None:
|
||||
"Run: ccproxy auth login codex"
|
||||
)
|
||||
|
||||
port = config.ccproxy_port
|
||||
if not (1 <= port <= 65535):
|
||||
raise ValueError(f"Invalid ccproxy port: {port}. Must be between 1 and 65535.")
|
||||
|
||||
# Auto-patch ccproxy adapter to fix OAuth header compatibility
|
||||
_patch_ccproxy_oauth_header()
|
||||
|
||||
# Start ccproxy (single process serves both providers)
|
||||
proc = ensure_ccproxy()
|
||||
proc = ensure_ccproxy(port)
|
||||
|
||||
# Set environment for each OAuth provider
|
||||
if anthropic_oauth:
|
||||
setup_ccproxy_env()
|
||||
setup_ccproxy_env(port)
|
||||
if openai_oauth:
|
||||
setup_codex_env()
|
||||
setup_codex_env(port)
|
||||
|
||||
if proc:
|
||||
logger.info("Started ccproxy on port %d", _DEFAULT_PORT)
|
||||
logger.info("Started ccproxy on port %d", port)
|
||||
else:
|
||||
logger.info("Reusing existing ccproxy on port %d", _DEFAULT_PORT)
|
||||
logger.info("Reusing existing ccproxy on port %d", port)
|
||||
return proc
|
||||
|
||||
@@ -759,6 +759,63 @@ def _prompt_and_validate_api_key(
|
||||
return new_key if new_key else None
|
||||
|
||||
|
||||
def _prompt_ccproxy_port(config: EvoScientistConfig) -> None:
|
||||
"""Prompt the user for a ccproxy port and save it to config."""
|
||||
|
||||
def valid_port(value: str) -> bool:
|
||||
if not value: # empty = keep default
|
||||
return True
|
||||
try:
|
||||
return 0 < int(value) < 2**16
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
current_port = getattr(config, "ccproxy_port", 8000)
|
||||
try:
|
||||
raw = questionary.text(
|
||||
f"Enter port number for ccproxy to run on (Current: {current_port}, Enter to keep):",
|
||||
validate=valid_port,
|
||||
style=WIZARD_STYLE,
|
||||
qmark=QMARK,
|
||||
).ask()
|
||||
ccproxy_port = int(raw) if raw else current_port
|
||||
except (ValueError, TypeError):
|
||||
ccproxy_port = current_port
|
||||
console.print(f" [dim]Using default port: {ccproxy_port}[/dim]")
|
||||
|
||||
setattr(config, "ccproxy_port", ccproxy_port)
|
||||
console.print(
|
||||
f" [green]✓ ccproxy will run on http://127.0.0.1:{ccproxy_port}[/green]"
|
||||
)
|
||||
|
||||
|
||||
def _run_ccproxy_login(provider: str, label: str) -> None:
|
||||
"""Run ccproxy auth login for the given provider and show status."""
|
||||
from ..ccproxy_manager import _ccproxy_exe, check_ccproxy_auth
|
||||
|
||||
console.print(" [dim]Opening browser for authentication...[/dim]")
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[_ccproxy_exe() or "ccproxy", "auth", "login", provider],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
for line in proc.stdout.splitlines():
|
||||
if line.strip().startswith("https://"):
|
||||
console.print(f" [dim]Visit: {line.strip()}[/dim]")
|
||||
break
|
||||
authed, msg = check_ccproxy_auth(provider)
|
||||
if authed:
|
||||
console.print(f" [green]✓ {label}: {msg}[/green]")
|
||||
else:
|
||||
console.print(f" [red]Authentication failed: {msg}[/red]")
|
||||
except subprocess.TimeoutExpired:
|
||||
console.print(" [red]Login timed out.[/red]")
|
||||
except Exception as exc:
|
||||
console.print(f" [red]Login error: {exc}[/red]")
|
||||
|
||||
|
||||
def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str:
|
||||
"""Step 2a: Select Anthropic authentication mode (API key vs OAuth).
|
||||
|
||||
@@ -768,7 +825,7 @@ def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str:
|
||||
Returns:
|
||||
Selected auth mode: "api_key", "oauth", or "auto".
|
||||
"""
|
||||
from ..ccproxy_manager import _ccproxy_exe, is_ccproxy_available, check_ccproxy_auth
|
||||
from ..ccproxy_manager import is_ccproxy_available, check_ccproxy_auth
|
||||
|
||||
ccproxy_available = is_ccproxy_available()
|
||||
|
||||
@@ -825,11 +882,22 @@ def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str:
|
||||
)
|
||||
return "api_key"
|
||||
|
||||
if auth_mode == "oauth":
|
||||
_prompt_ccproxy_port(config)
|
||||
|
||||
# If OAuth selected, check auth status and offer login
|
||||
if auth_mode in ("oauth", "auto"):
|
||||
authed, msg = check_ccproxy_auth()
|
||||
if authed:
|
||||
console.print(f" [green]✓ OAuth: {msg}[/green]")
|
||||
relogin = questionary.confirm(
|
||||
"Re-authenticate to refresh credentials?",
|
||||
default=False,
|
||||
style=CONFIRM_STYLE,
|
||||
qmark=QMARK,
|
||||
).ask()
|
||||
if relogin:
|
||||
_run_ccproxy_login("claude_api", "OAuth")
|
||||
else:
|
||||
console.print(f" [yellow]OAuth not authenticated: {msg}[/yellow]")
|
||||
login = questionary.confirm(
|
||||
@@ -839,28 +907,7 @@ def _step_anthropic_auth_mode(config: EvoScientistConfig) -> str:
|
||||
qmark=QMARK,
|
||||
).ask()
|
||||
if login:
|
||||
console.print(" [dim]Opening browser for authentication...[/dim]")
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[_ccproxy_exe() or "ccproxy", "auth", "login", "claude_api"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
# Show browser URL in case browser didn't open automatically
|
||||
for line in proc.stdout.splitlines():
|
||||
if line.strip().startswith("https://"):
|
||||
console.print(f" [dim]Visit: {line.strip()}[/dim]")
|
||||
break
|
||||
authed, msg = check_ccproxy_auth()
|
||||
if authed:
|
||||
console.print(f" [green]✓ OAuth: {msg}[/green]")
|
||||
else:
|
||||
console.print(f" [red]Authentication failed: {msg}[/red]")
|
||||
except subprocess.TimeoutExpired:
|
||||
console.print(" [red]Login timed out.[/red]")
|
||||
except Exception as exc:
|
||||
console.print(f" [red]Login error: {exc}[/red]")
|
||||
_run_ccproxy_login("claude_api", "OAuth")
|
||||
|
||||
return auth_mode
|
||||
|
||||
@@ -874,7 +921,7 @@ def _step_openai_auth_mode(config: EvoScientistConfig) -> str:
|
||||
Returns:
|
||||
Selected auth mode: "api_key" or "oauth".
|
||||
"""
|
||||
from ..ccproxy_manager import _ccproxy_exe, is_ccproxy_available, check_ccproxy_auth
|
||||
from ..ccproxy_manager import is_ccproxy_available, check_ccproxy_auth
|
||||
|
||||
ccproxy_available = is_ccproxy_available()
|
||||
|
||||
@@ -931,11 +978,20 @@ def _step_openai_auth_mode(config: EvoScientistConfig) -> str:
|
||||
)
|
||||
return "api_key"
|
||||
|
||||
# If OAuth selected, check auth status and offer login
|
||||
# If OAuth selected, prompt for port and check auth status
|
||||
if auth_mode == "oauth":
|
||||
_prompt_ccproxy_port(config)
|
||||
authed, msg = check_ccproxy_auth("codex")
|
||||
if authed:
|
||||
console.print(f" [green]✓ Codex OAuth: {msg}[/green]")
|
||||
relogin = questionary.confirm(
|
||||
"Re-authenticate to refresh credentials?",
|
||||
default=False,
|
||||
style=CONFIRM_STYLE,
|
||||
qmark=QMARK,
|
||||
).ask()
|
||||
if relogin:
|
||||
_run_ccproxy_login("codex", "Codex OAuth")
|
||||
else:
|
||||
console.print(f" [yellow]Codex OAuth not authenticated: {msg}[/yellow]")
|
||||
login = questionary.confirm(
|
||||
@@ -945,28 +1001,7 @@ def _step_openai_auth_mode(config: EvoScientistConfig) -> str:
|
||||
qmark=QMARK,
|
||||
).ask()
|
||||
if login:
|
||||
console.print(" [dim]Opening browser for authentication...[/dim]")
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[_ccproxy_exe() or "ccproxy", "auth", "login", "codex"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
# Show browser URL in case browser didn't open automatically
|
||||
for line in proc.stdout.splitlines():
|
||||
if line.strip().startswith("https://"):
|
||||
console.print(f" [dim]Visit: {line.strip()}[/dim]")
|
||||
break
|
||||
authed, msg = check_ccproxy_auth("codex")
|
||||
if authed:
|
||||
console.print(f" [green]✓ Codex OAuth: {msg}[/green]")
|
||||
else:
|
||||
console.print(f" [red]Authentication failed: {msg}[/red]")
|
||||
except subprocess.TimeoutExpired:
|
||||
console.print(" [red]Login timed out.[/red]")
|
||||
except Exception as exc:
|
||||
console.print(f" [red]Login error: {exc}[/red]")
|
||||
_run_ccproxy_login("codex", "Codex OAuth")
|
||||
|
||||
return auth_mode
|
||||
|
||||
@@ -1782,28 +1817,18 @@ def validate_imessage() -> tuple[bool, str]:
|
||||
def _install_ccproxy() -> bool:
|
||||
"""Run pip install for ccproxy (evoscientist[oauth]).
|
||||
|
||||
Uses uv pip install when available (uv-managed envs don't ship pip).
|
||||
|
||||
Returns:
|
||||
True if installation succeeded and ccproxy is available.
|
||||
"""
|
||||
from ..ccproxy_manager import is_ccproxy_available
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "evoscientist[oauth]"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
console.print(f" [red]✗ Installation failed:[/red]\n{proc.stderr.strip()}")
|
||||
return False
|
||||
return is_ccproxy_available()
|
||||
except subprocess.TimeoutExpired:
|
||||
console.print(" [red]✗ Installation timed out.[/red]")
|
||||
return False
|
||||
except Exception as e:
|
||||
console.print(f" [red]✗ Installation failed: {e}[/red]")
|
||||
ok = _install_pip_package("evoscientist[oauth]")
|
||||
if not ok:
|
||||
console.print(" [red]✗ Installation failed.[/red]")
|
||||
return False
|
||||
return is_ccproxy_available()
|
||||
|
||||
|
||||
def _install_imsg() -> bool:
|
||||
|
||||
@@ -195,6 +195,9 @@ class EvoScientistConfig:
|
||||
# DM access control policy
|
||||
dm_policy: str = "allowlist"
|
||||
|
||||
# ccproxy
|
||||
ccproxy_port: int = 8000
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Config file operations
|
||||
@@ -357,6 +360,7 @@ _ENV_MAPPINGS = {
|
||||
"default_mode": "EVOSCIENTIST_DEFAULT_MODE",
|
||||
"default_workdir": "EVOSCIENTIST_WORKSPACE_DIR",
|
||||
"ui_backend": "EVOSCIENTIST_UI_BACKEND",
|
||||
"ccproxy_port": "EVOSCIENTIST_CCPROXY_PORT",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -91,6 +91,20 @@ class TestIsCcproxyRunning:
|
||||
mock_get.return_value = MagicMock(status_code=200)
|
||||
assert is_ccproxy_running(8000) is True
|
||||
|
||||
@patch("httpx.get")
|
||||
def test_uses_health_live_endpoint(self, mock_get):
|
||||
mock_get.return_value = MagicMock(status_code=200)
|
||||
is_ccproxy_running(8000)
|
||||
url = mock_get.call_args[0][0]
|
||||
assert url == "http://127.0.0.1:8000/health/live"
|
||||
|
||||
@patch("httpx.get")
|
||||
def test_uses_custom_port(self, mock_get):
|
||||
mock_get.return_value = MagicMock(status_code=200)
|
||||
is_ccproxy_running(7777)
|
||||
url = mock_get.call_args[0][0]
|
||||
assert url == "http://127.0.0.1:7777/health/live"
|
||||
|
||||
@patch("httpx.get")
|
||||
def test_not_running(self, mock_get):
|
||||
import httpx
|
||||
@@ -98,6 +112,11 @@ class TestIsCcproxyRunning:
|
||||
mock_get.side_effect = httpx.ConnectError("Connection refused")
|
||||
assert is_ccproxy_running(8000) is False
|
||||
|
||||
@patch("httpx.get")
|
||||
def test_non_200_means_not_running(self, mock_get):
|
||||
mock_get.return_value = MagicMock(status_code=404)
|
||||
assert is_ccproxy_running(8000) is False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# start_ccproxy
|
||||
@@ -248,6 +267,7 @@ class TestMaybeStartCcproxy:
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "oauth"
|
||||
config.openai_auth_mode = "api_key"
|
||||
config.ccproxy_port = 8000
|
||||
|
||||
result = maybe_start_ccproxy(config)
|
||||
assert result is proc
|
||||
@@ -285,6 +305,7 @@ class TestMaybeStartCcproxy:
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "api_key"
|
||||
config.openai_auth_mode = "oauth"
|
||||
config.ccproxy_port = 8000
|
||||
|
||||
result = maybe_start_ccproxy(config)
|
||||
assert result is proc
|
||||
@@ -304,6 +325,7 @@ class TestMaybeStartCcproxy:
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "oauth"
|
||||
config.openai_auth_mode = "oauth"
|
||||
config.ccproxy_port = 8000
|
||||
|
||||
result = maybe_start_ccproxy(config)
|
||||
assert result is proc
|
||||
@@ -331,3 +353,44 @@ class TestMaybeStartCcproxy:
|
||||
config.openai_auth_mode = "oauth"
|
||||
with pytest.raises(RuntimeError, match="Codex OAuth not authenticated"):
|
||||
maybe_start_ccproxy(config)
|
||||
|
||||
@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_uses_config_ccproxy_port(
|
||||
self, mock_avail, mock_auth, mock_ensure, mock_env
|
||||
):
|
||||
"""maybe_start_ccproxy passes config.ccproxy_port to ensure_ccproxy."""
|
||||
proc = MagicMock()
|
||||
mock_ensure.return_value = proc
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "oauth"
|
||||
config.openai_auth_mode = "api_key"
|
||||
config.ccproxy_port = 7777
|
||||
|
||||
maybe_start_ccproxy(config)
|
||||
mock_ensure.assert_called_once_with(7777)
|
||||
|
||||
@patch("EvoScientist.ccproxy_manager.check_ccproxy_auth", return_value=(True, "OK"))
|
||||
@patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True)
|
||||
def test_invalid_port_raises(self, mock_avail, mock_auth):
|
||||
"""maybe_start_ccproxy raises ValueError for out-of-range port."""
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "oauth"
|
||||
config.openai_auth_mode = "api_key"
|
||||
config.ccproxy_port = 0
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid ccproxy port"):
|
||||
maybe_start_ccproxy(config)
|
||||
|
||||
@patch("EvoScientist.ccproxy_manager.check_ccproxy_auth", return_value=(True, "OK"))
|
||||
@patch("EvoScientist.ccproxy_manager.is_ccproxy_available", return_value=True)
|
||||
def test_port_too_large_raises(self, mock_avail, mock_auth):
|
||||
config = MagicMock()
|
||||
config.anthropic_auth_mode = "oauth"
|
||||
config.openai_auth_mode = "api_key"
|
||||
config.ccproxy_port = 99999
|
||||
|
||||
with pytest.raises(ValueError, match="Invalid ccproxy port"):
|
||||
maybe_start_ccproxy(config)
|
||||
|
||||
Reference in New Issue
Block a user