""" Coze 客户端单元测试 """ import os import pytest from unittest.mock import Mock, patch, MagicMock from cozepy import Coze, TokenAuth, OAuthJWT from app.services.ai.coze.client import ( CozeAuthManager, get_coze_client, get_bot_config, get_workspace_id ) from app.services.ai.coze.exceptions import CozeAuthError class TestCozeAuthManager: """测试认证管理器""" def test_init_with_env_vars(self): """测试从环境变量初始化""" with patch.dict(os.environ, { "COZE_API_BASE": "https://test.coze.cn", "COZE_WORKSPACE_ID": "test-workspace", "COZE_API_TOKEN": "test-token" }): manager = CozeAuthManager() assert manager.api_base == "https://test.coze.cn" assert manager.workspace_id == "test-workspace" assert manager.api_token == "test-token" def test_init_with_params(self): """测试从参数初始化""" manager = CozeAuthManager( api_base="https://custom.coze.cn", workspace_id="custom-workspace", api_token="custom-token" ) assert manager.api_base == "https://custom.coze.cn" assert manager.workspace_id == "custom-workspace" assert manager.api_token == "custom-token" def test_setup_direct_connection(self): """测试直连设置""" manager = CozeAuthManager() no_proxy = os.environ.get("NO_PROXY", "") assert "api.coze.cn" in no_proxy assert ".coze.cn" in no_proxy assert "localhost" in no_proxy @patch("app.services.ai.coze.client.TokenAuth") @patch("app.services.ai.coze.client.Coze") def test_token_auth_success(self, mock_coze_class, mock_token_auth): """测试 Token 认证成功""" manager = CozeAuthManager(api_token="test-token") mock_client = Mock() mock_coze_class.return_value = mock_client client = manager.get_client() mock_token_auth.assert_called_once_with("test-token") mock_coze_class.assert_called_once() assert client == mock_client def test_token_auth_no_token(self): """测试没有 Token 时的错误""" manager = CozeAuthManager(api_token=None) with pytest.raises(CozeAuthError, match="API Token 未配置"): manager.get_client() @patch("builtins.open", create=True) @patch("app.services.ai.coze.client.serialization.load_pem_private_key") @patch("app.services.ai.coze.client.OAuthJWT") @patch("app.services.ai.coze.client.Coze") def test_oauth_auth_success(self, mock_coze_class, mock_oauth_jwt, mock_load_key, mock_open): """测试 OAuth 认证成功""" # 模拟私钥文件 mock_open.return_value.__enter__.return_value.read.return_value = b"fake-private-key" mock_load_key.return_value = Mock() manager = CozeAuthManager( oauth_client_id="test-client", oauth_public_key_id="test-key-id", oauth_private_key_path="/path/to/key.pem" ) mock_client = Mock() mock_coze_class.return_value = mock_client client = manager.get_client() mock_oauth_jwt.assert_called_once() assert client == mock_client @patch("builtins.open", side_effect=FileNotFoundError) @patch("app.services.ai.coze.client.TokenAuth") @patch("app.services.ai.coze.client.Coze") def test_oauth_fallback_to_token(self, mock_coze_class, mock_token_auth, mock_open): """测试 OAuth 失败后回退到 Token""" manager = CozeAuthManager( api_token="fallback-token", oauth_client_id="test-client", oauth_public_key_id="test-key-id", oauth_private_key_path="/nonexistent/key.pem" ) mock_client = Mock() mock_coze_class.return_value = mock_client client = manager.get_client() # 应该使用 Token 认证 mock_token_auth.assert_called_once_with("fallback-token") assert client == mock_client def test_refresh_token(self): """测试刷新令牌""" manager = CozeAuthManager(api_token="test-token") with patch.object(manager, '_init_client') as mock_init: manager.refresh_token() assert manager._client is None mock_init.assert_called_once() class TestHelperFunctions: """测试辅助函数""" def test_get_bot_config(self): """测试获取 Bot 配置""" with patch.dict(os.environ, { "COZE_CHAT_BOT_ID": "chat-bot-123", "COZE_TRAINING_BOT_ID": "training-bot-456", "COZE_EXAM_BOT_ID": "exam-bot-789" }): config = get_bot_config() assert config["course_chat"] == "chat-bot-123" assert config["training"] == "training-bot-456" assert config["exam"] == "exam-bot-789" def test_get_workspace_id_success(self): """测试获取工作空间 ID 成功""" with patch.dict(os.environ, {"COZE_WORKSPACE_ID": "workspace-123"}): workspace_id = get_workspace_id() assert workspace_id == "workspace-123" def test_get_workspace_id_not_configured(self): """测试工作空间 ID 未配置""" with patch.dict(os.environ, {}, clear=True): with pytest.raises(CozeAuthError, match="COZE_WORKSPACE_ID 未配置"): get_workspace_id() @patch("app.services.ai.coze.client.get_auth_manager") def test_get_coze_client(self, mock_get_auth_manager): """测试获取 Coze 客户端""" mock_manager = Mock() mock_client = Mock() mock_manager.get_client.return_value = mock_client mock_get_auth_manager.return_value = mock_manager # 清除缓存 get_coze_client.cache_clear() client = get_coze_client() assert client == mock_client mock_manager.get_client.assert_called_once()