import pytest from app.core.ai_engine import ClaudeEngine @pytest.fixture def mock_settings(mocker): """Mock settings for test isolation.""" mock_settings = mocker.MagicMock() mock_settings.ANTHROPIC_API_KEY = "test-key" mock_settings.CLAUDE_MODEL = "claude-sonnet-4-6" mock_settings.CLAUDE_MAX_TOKENS = 1024 mock_settings.CLAUDE_TEMPERATURE = 0.7 mocker.patch('app.core.ai_engine.get_settings', return_value=mock_settings) return mock_settings @pytest.fixture def mock_anthropic_client(mocker): """Mock Anthropic async client.""" mock_client = mocker.AsyncMock() mocker.patch('app.core.ai_engine.anthropic.AsyncAnthropic', return_value=mock_client) return mock_client def test_claude_engine_init(mocker, mock_settings): """测试 Claude 引擎初始化""" engine = ClaudeEngine() assert engine.client is not None assert engine.model == "claude-sonnet-4-6" assert engine.max_tokens == 1024 assert engine.temperature == 0.7 def test_parse_json_response(mocker, mock_settings): """测试 JSON 解析""" engine = ClaudeEngine() # 测试纯 JSON json_str = '{"name": "test", "value": 123}' result = engine.parse_json_response(json_str) assert result["name"] == "test" assert result["value"] == 123 # 测试 markdown 代码块 md_str = '```json\n{"name": "test"}\n```' result = engine.parse_json_response(md_str) assert result["name"] == "test" def test_parse_json_response_empty_content(mocker, mock_settings): """测试空内容错误处理""" engine = ClaudeEngine() with pytest.raises(ValueError, match="Empty content provided"): engine.parse_json_response("") with pytest.raises(ValueError, match="Empty content provided"): engine.parse_json_response(" ") def test_parse_json_response_invalid_json(mocker, mock_settings): """测试无效 JSON 错误处理""" engine = ClaudeEngine() # 无效 JSON 且无法提取任何代码块 invalid_str = "This is not JSON at all" with pytest.raises(ValueError, match="无法解析 Claude 返回的 JSON"): engine.parse_json_response(invalid_str) # 无效的 JSON 代码块 invalid_json_block = '```json\n{invalid json}\n```' with pytest.raises(ValueError, match="无法解析 Claude 返回的 JSON"): engine.parse_json_response(invalid_json_block) def test_parse_json_response_code_block(mocker, mock_settings): """测试代码块 JSON 解析""" engine = ClaudeEngine() # 普通代码块(无 json 标签) code_block = '```\n{"status": "ok"}\n```' result = engine.parse_json_response(code_block) assert result["status"] == "ok" def test_parse_json_response_nested_json(mocker, mock_settings): """测试嵌套 JSON 解析""" engine = ClaudeEngine() # 带有一些额外文本的 JSON text_with_json = 'Some text before {"key": "value"} and after' result = engine.parse_json_response(text_with_json) assert result["key"] == "value" # 嵌套 JSON nested_json = '{"outer": {"inner": "value"}}' result = engine.parse_json_response(nested_json) assert result["outer"]["inner"] == "value" @pytest.mark.asyncio async def test_call_claude(mocker, mock_settings, mock_anthropic_client): """测试 call_claude 方法""" # 设置 mock 响应 mock_response = mocker.MagicMock() mock_response.content = [mocker.MagicMock(text="Hello, I am Claude")] mock_anthropic_client.messages.create.return_value = mock_response engine = ClaudeEngine() messages = [{"role": "user", "content": "Hello"}] result = await engine.call_claude(messages) assert result == "Hello, I am Claude" mock_anthropic_client.messages.create.assert_called_once_with( model="claude-sonnet-4-6", max_tokens=1024, temperature=0.7, messages=messages ) @pytest.mark.asyncio async def test_call_claude_with_temperature(mocker, mock_settings, mock_anthropic_client): """测试 call_claude 带温度参数""" mock_response = mocker.MagicMock() mock_response.content = [mocker.MagicMock(text="Response")] mock_anthropic_client.messages.create.return_value = mock_response engine = ClaudeEngine() messages = [{"role": "user", "content": "Hello"}] result = await engine.call_claude(messages, temperature=1.5) assert result == "Response" call_args = mock_anthropic_client.messages.create.call_args assert call_args.kwargs["temperature"] == 1.5 @pytest.mark.asyncio async def test_call_claude_error(mocker, mock_settings, mock_anthropic_client): """测试 call_claude 错误处理""" # 设置 mock 抛出异常 mock_anthropic_client.messages.create.side_effect = Exception("API Error") engine = ClaudeEngine() messages = [{"role": "user", "content": "Hello"}] with pytest.raises(Exception, match="API Error"): await engine.call_claude(messages)