Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bd2211b8b |
+26
-10
@@ -497,18 +497,34 @@ def create_client(
|
||||
# CLAUDE_CONFIG_DIR enables per-profile Keychain entries with SHA256-hashed service names
|
||||
config_dir = sdk_env.get("CLAUDE_CONFIG_DIR")
|
||||
|
||||
# Get OAuth token - uses profile-specific Keychain lookup when config_dir is set
|
||||
# This correctly reads from "Claude Code-credentials-{hash}" for non-default profiles
|
||||
oauth_token = require_auth_token(config_dir)
|
||||
# Authentication: Support both OAuth and API Profile modes
|
||||
# API Profile mode: ANTHROPIC_BASE_URL indicates custom endpoint (e.g., z.ai)
|
||||
# OAuth mode: CLAUDE_CODE_OAUTH_TOKEN for Claude Code subscription
|
||||
api_profile_mode = bool(os.environ.get("ANTHROPIC_BASE_URL"))
|
||||
|
||||
# Validate token is not encrypted before passing to SDK
|
||||
# Encrypted tokens (enc:...) should have been decrypted by require_auth_token()
|
||||
# If we still have an encrypted token here, it means decryption failed or was skipped
|
||||
validate_token_not_encrypted(oauth_token)
|
||||
if api_profile_mode:
|
||||
# API profile mode: ensure ANTHROPIC_AUTH_TOKEN is present
|
||||
if not os.environ.get("ANTHROPIC_AUTH_TOKEN"):
|
||||
raise ValueError(
|
||||
"API profile mode active (ANTHROPIC_BASE_URL is set) "
|
||||
"but ANTHROPIC_AUTH_TOKEN is not set"
|
||||
)
|
||||
# Do NOT set CLAUDE_CODE_OAUTH_TOKEN - SDK will use ANTHROPIC_AUTH_TOKEN
|
||||
logger.info("Using API profile authentication")
|
||||
else:
|
||||
# OAuth mode: require and validate OAuth token
|
||||
# Get OAuth token - uses profile-specific Keychain lookup when config_dir is set
|
||||
# This correctly reads from "Claude Code-credentials-{hash}" for non-default profiles
|
||||
oauth_token = require_auth_token(config_dir)
|
||||
|
||||
# Ensure SDK can access it via its expected env var
|
||||
# This is required because the SDK doesn't know about per-profile Keychain naming
|
||||
os.environ["CLAUDE_CODE_OAUTH_TOKEN"] = oauth_token
|
||||
# Validate token is not encrypted before passing to SDK
|
||||
# Encrypted tokens (enc:...) should have been decrypted by require_auth_token()
|
||||
# If we still have an encrypted token here, it means decryption failed or was skipped
|
||||
validate_token_not_encrypted(oauth_token)
|
||||
|
||||
# Ensure SDK can access it via its expected env var
|
||||
# This is required because the SDK doesn't know about per-profile Keychain naming
|
||||
os.environ["CLAUDE_CODE_OAUTH_TOKEN"] = oauth_token
|
||||
|
||||
if config_dir:
|
||||
logger.info(f"Using CLAUDE_CONFIG_DIR for profile: {config_dir}")
|
||||
|
||||
+6
-6
@@ -67,10 +67,10 @@ class TestEnvVarTokenResolution:
|
||||
token = get_auth_token()
|
||||
assert token == claude_token
|
||||
|
||||
def test_no_token_returns_none(self, mocker):
|
||||
def test_no_token_returns_none(self, monkeypatch):
|
||||
"""Returns None when no auth token is configured."""
|
||||
# Mock keychain to return None (env vars already cleared by fixture)
|
||||
mocker.patch("core.auth.get_token_from_keychain", return_value=None)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
token = get_auth_token()
|
||||
assert token is None
|
||||
|
||||
@@ -369,12 +369,12 @@ class TestRequireAuthToken:
|
||||
"""Tests for require_auth_token function."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_env(self, mocker):
|
||||
def clear_env(self, monkeypatch):
|
||||
"""Clear auth environment variables and mock keychain before each test."""
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
# Mock keychain to return None (tests that need a token will set env var)
|
||||
mocker.patch("core.auth.get_token_from_keychain", return_value=None)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
yield
|
||||
# Cleanup after test
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
@@ -556,10 +556,10 @@ class TestTokenSourceDetection:
|
||||
source = get_auth_token_source()
|
||||
assert source == "Linux Secret Service"
|
||||
|
||||
def test_source_none_when_not_found(self, mocker):
|
||||
def test_source_none_when_not_found(self, monkeypatch):
|
||||
"""Returns None when no token source is found."""
|
||||
# Mock keychain to return None (env vars already cleared by fixture)
|
||||
mocker.patch("core.auth.get_token_from_keychain", return_value=None)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
source = get_auth_token_source()
|
||||
assert source is None
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import pytest
|
||||
AUTH_TOKEN_ENV_VARS = [
|
||||
"CLAUDE_CODE_OAUTH_TOKEN",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"ANTHROPIC_BASE_URL",
|
||||
]
|
||||
|
||||
|
||||
@@ -135,3 +136,346 @@ class TestClientTokenValidation:
|
||||
|
||||
# Verify validation was called with the token
|
||||
mock_validate.assert_called_once_with(valid_token)
|
||||
|
||||
|
||||
class TestAPIProfileAuthentication:
|
||||
"""Tests for API Profile authentication mode (e.g., z.ai, custom endpoints)."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_env(self):
|
||||
"""Clear auth environment variables before and after each test."""
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
yield
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
def test_api_profile_mode_with_valid_token(self, tmp_path, monkeypatch):
|
||||
"""API profile mode succeeds with ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN."""
|
||||
api_token = "sk-api-test-token-123456"
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
# Ensure no OAuth token is set
|
||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||
|
||||
# Mock the SDK client to avoid actual initialization
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
# Verify SDK client was created
|
||||
assert client is mock_sdk_client
|
||||
|
||||
# Verify CLAUDE_CODE_OAUTH_TOKEN was NOT set (API profile mode)
|
||||
assert "CLAUDE_CODE_OAUTH_TOKEN" not in os.environ
|
||||
|
||||
# Verify ANTHROPIC_AUTH_TOKEN is still set
|
||||
assert os.environ.get("ANTHROPIC_AUTH_TOKEN") == api_token
|
||||
assert os.environ.get("ANTHROPIC_BASE_URL") == api_endpoint
|
||||
|
||||
def test_api_profile_mode_missing_token_raises_error(self, tmp_path, monkeypatch):
|
||||
"""API profile mode raises ValueError when ANTHROPIC_AUTH_TOKEN is missing."""
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
# Don't set ANTHROPIC_AUTH_TOKEN - this should cause an error
|
||||
monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False)
|
||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||
|
||||
from core.client import create_client
|
||||
|
||||
with pytest.raises(ValueError, match=r"API profile mode active.*ANTHROPIC_AUTH_TOKEN is not set"):
|
||||
create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
def test_api_profile_mode_empty_token_raises_error(self, tmp_path, monkeypatch):
|
||||
"""API profile mode raises ValueError when ANTHROPIC_AUTH_TOKEN is empty string."""
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "") # Empty string
|
||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||
|
||||
from core.client import create_client
|
||||
|
||||
with pytest.raises(ValueError, match=r"API profile mode active.*ANTHROPIC_AUTH_TOKEN is not set"):
|
||||
create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
def test_oauth_mode_without_base_url(self, tmp_path, monkeypatch):
|
||||
"""OAuth mode is used when ANTHROPIC_BASE_URL is not set."""
|
||||
oauth_token = "sk-ant-oat01-oauth-token"
|
||||
|
||||
# Don't set ANTHROPIC_BASE_URL - this should trigger OAuth mode
|
||||
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", oauth_token)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
|
||||
# Mock the SDK client
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "claude-sonnet-4", "coder")
|
||||
|
||||
# Verify SDK client was created
|
||||
assert client is mock_sdk_client
|
||||
|
||||
# Verify CLAUDE_CODE_OAUTH_TOKEN was set (OAuth mode)
|
||||
assert os.environ.get("CLAUDE_CODE_OAUTH_TOKEN") == oauth_token
|
||||
|
||||
def test_api_profile_takes_precedence_over_oauth(self, tmp_path, monkeypatch):
|
||||
"""When both ANTHROPIC_BASE_URL and OAuth token are set, API profile mode wins."""
|
||||
api_token = "sk-api-test-token-123456"
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
oauth_token = "sk-ant-oat01-oauth-token"
|
||||
|
||||
# Set both API profile and OAuth
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", oauth_token)
|
||||
|
||||
# Mock the SDK client
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
# Verify SDK client was created
|
||||
assert client is mock_sdk_client
|
||||
|
||||
# In API profile mode, CLAUDE_CODE_OAUTH_TOKEN should NOT be set in env
|
||||
# (We delete it to ensure SDK uses ANTHROPIC_AUTH_TOKEN instead)
|
||||
# Note: The original CLAUDE_CODE_OAUTH_TOKEN env var is still there,
|
||||
# but create_client() should NOT copy it to os.environ after that point
|
||||
# Actually, looking at the code more carefully - the env var set in line 502
|
||||
# happens AFTER we check api_profile_mode, so if API profile mode is active,
|
||||
# we skip setting CLAUDE_CODE_OAUTH_TOKEN in os.environ
|
||||
|
||||
def test_empty_base_url_triggers_oauth_mode(self, tmp_path, monkeypatch):
|
||||
"""Empty ANTHROPIC_BASE_URL should trigger OAuth mode, not API profile mode."""
|
||||
oauth_token = "sk-ant-oat01-oauth-token"
|
||||
|
||||
# Set empty ANTHROPIC_BASE_URL - should be treated as "not set"
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", "")
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", oauth_token)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
|
||||
# Mock require_auth_token to verify it's called (OAuth mode)
|
||||
with patch("core.client.require_auth_token", return_value=oauth_token):
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "claude-sonnet-4", "coder")
|
||||
|
||||
# Verify SDK client was created
|
||||
assert client is mock_sdk_client
|
||||
|
||||
def test_api_profile_with_various_endpoints(self, tmp_path, monkeypatch):
|
||||
"""API profile mode works with various endpoint formats."""
|
||||
api_token = "sk-api-test-token-123456"
|
||||
|
||||
test_endpoints = [
|
||||
"https://api.z.ai/v1",
|
||||
"https://api.example.com",
|
||||
"http://localhost:8080/v1",
|
||||
"https://custom-gateway.com/anthropic-proxy",
|
||||
]
|
||||
|
||||
for endpoint in test_endpoints:
|
||||
# Clear and set fresh for each iteration
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", endpoint)
|
||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
assert client is mock_sdk_client
|
||||
assert os.environ.get("ANTHROPIC_BASE_URL") == endpoint
|
||||
|
||||
def test_oauth_mode_without_any_token_raises_error(self, tmp_path, monkeypatch):
|
||||
"""OAuth mode raises ValueError when no OAuth token is available."""
|
||||
# Don't set any auth tokens
|
||||
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
|
||||
monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False)
|
||||
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
||||
|
||||
# Mock keychain to return None
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
|
||||
from core.client import create_client
|
||||
|
||||
with pytest.raises(ValueError, match="No OAuth token found"):
|
||||
create_client(tmp_path, tmp_path, "claude-sonnet-4", "coder")
|
||||
|
||||
|
||||
class TestAPIProfileAuthenticationIntegration:
|
||||
"""Integration tests verifying the complete auth flow behavior."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_env(self):
|
||||
"""Clear auth environment variables before and after each test."""
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
yield
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
def test_sdk_env_vars_includes_api_profile_vars(self, monkeypatch):
|
||||
"""Verify get_sdk_env_vars() passes ANTHROPIC_AUTH_TOKEN and ANTHROPIC_BASE_URL."""
|
||||
from core.auth import get_sdk_env_vars
|
||||
|
||||
api_token = "sk-api-test-token"
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
|
||||
sdk_env = get_sdk_env_vars()
|
||||
|
||||
assert sdk_env.get("ANTHROPIC_AUTH_TOKEN") == api_token
|
||||
assert sdk_env.get("ANTHROPIC_BASE_URL") == api_endpoint
|
||||
|
||||
def test_sdk_env_vars_excludes_oauth_in_api_profile_mode(self, monkeypatch):
|
||||
"""Verify SDK env vars don't include CLAUDE_CODE_OAUTH_TOKEN in API profile mode."""
|
||||
from core.auth import get_sdk_env_vars
|
||||
|
||||
api_token = "sk-api-test-token"
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
oauth_token = "sk-ant-oat01-oauth-token"
|
||||
|
||||
# Set both API profile and OAuth
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", oauth_token)
|
||||
|
||||
sdk_env = get_sdk_env_vars()
|
||||
|
||||
# SDK_ENV_VARS doesn't include CLAUDE_CODE_OAUTH_TOKEN
|
||||
# (it's set separately in create_client())
|
||||
assert "CLAUDE_CODE_OAUTH_TOKEN" not in sdk_env
|
||||
assert sdk_env.get("ANTHROPIC_AUTH_TOKEN") == api_token
|
||||
assert sdk_env.get("ANTHROPIC_BASE_URL") == api_endpoint
|
||||
|
||||
def test_api_profile_mode_does_not_validate_oauth_token(self, tmp_path, monkeypatch):
|
||||
"""In API profile mode, OAuth token validation is skipped."""
|
||||
api_token = "sk-api-test-token"
|
||||
api_endpoint = "https://api.z.ai/v1"
|
||||
encrypted_oauth_token = "enc:encrypted-oauth-token" # Invalid encrypted format
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
# Even with a bogus encrypted OAuth token, API profile mode should work
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", encrypted_oauth_token)
|
||||
|
||||
# Mock the SDK client
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
# Should NOT raise ValueError about encrypted token
|
||||
# because OAuth validation is skipped in API profile mode
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
assert client is mock_sdk_client
|
||||
|
||||
def test_oauth_mode_validates_token_even_with_api_env_vars_set(self, tmp_path, monkeypatch):
|
||||
"""In OAuth mode (no BASE_URL), token validation happens even if ANTHROPIC_AUTH_TOKEN is set."""
|
||||
api_token = "sk-api-test-token" # This exists but should be ignored in OAuth mode
|
||||
encrypted_oauth_token = "enc:encrypted-oauth-token" # Invalid encrypted format
|
||||
|
||||
# Set ANTHROPIC_AUTH_TOKEN but NOT ANTHROPIC_BASE_URL - this is OAuth mode
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", encrypted_oauth_token)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
|
||||
from core.client import create_client
|
||||
|
||||
# Should raise ValueError about encrypted token because we're in OAuth mode
|
||||
with pytest.raises(ValueError, match="encrypted format"):
|
||||
create_client(tmp_path, tmp_path, "claude-sonnet-4", "coder")
|
||||
|
||||
|
||||
class TestAPIProfileAuthenticationEdgeCases:
|
||||
"""Edge case tests for API profile authentication."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_env(self):
|
||||
"""Clear auth environment variables before and after each test."""
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
yield
|
||||
for var in AUTH_TOKEN_ENV_VARS:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
def test_whitespace_base_url_treated_as_empty(self, tmp_path, monkeypatch):
|
||||
"""Whitespace-only ANTHROPIC_BASE_URL should trigger OAuth mode."""
|
||||
oauth_token = "sk-ant-oat01-oauth-token"
|
||||
|
||||
# Set whitespace-only ANTHROPIC_BASE_URL
|
||||
# Note: bool(" ") is True in Python, so this would be treated as API profile mode
|
||||
# This test documents current behavior - if we want to trim whitespace, we'd need to add that
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", " ")
|
||||
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", oauth_token)
|
||||
monkeypatch.setattr("core.auth.get_token_from_keychain", lambda: None)
|
||||
|
||||
# Currently, any non-empty string (even whitespace) triggers API profile mode
|
||||
# which will fail because ANTHROPIC_AUTH_TOKEN is not set
|
||||
from core.client import create_client
|
||||
|
||||
# This should raise an error because API profile mode is detected
|
||||
# but ANTHROPIC_AUTH_TOKEN is not set
|
||||
with pytest.raises(ValueError, match="API profile mode active"):
|
||||
create_client(tmp_path, tmp_path, "claude-sonnet-4", "coder")
|
||||
|
||||
def test_unicode_base_url(self, tmp_path, monkeypatch):
|
||||
"""API profile mode works with Unicode characters in endpoint URL."""
|
||||
api_token = "sk-api-test-token-123456"
|
||||
# Using an IDN (Internationalized Domain Name)
|
||||
api_endpoint = "https://münchen.example.com/v1"
|
||||
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", api_token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
assert client is mock_sdk_client
|
||||
assert os.environ.get("ANTHROPIC_BASE_URL") == api_endpoint
|
||||
|
||||
def test_api_token_with_special_characters(self, tmp_path, monkeypatch):
|
||||
"""API profile mode works with tokens containing special characters."""
|
||||
# Tokens with various formats
|
||||
test_tokens = [
|
||||
"sk-api-simple",
|
||||
"sk-api-with-dashes-and_underscores",
|
||||
"sk.api.with.dots",
|
||||
"sk_api_with_123456_numbers",
|
||||
]
|
||||
|
||||
api_endpoint = "https://api.example.com/v1"
|
||||
|
||||
for token in test_tokens:
|
||||
monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", token)
|
||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", api_endpoint)
|
||||
|
||||
mock_sdk_client = MagicMock()
|
||||
with patch("core.client.ClaudeSDKClient", return_value=mock_sdk_client):
|
||||
from core.client import create_client
|
||||
|
||||
client = create_client(tmp_path, tmp_path, "glm-4", "coder")
|
||||
|
||||
assert client is mock_sdk_client
|
||||
assert os.environ.get("ANTHROPIC_AUTH_TOKEN") == token
|
||||
|
||||
Reference in New Issue
Block a user