| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- """
- 语义逻辑审查模块单元测试
- 测试 semantic_logic.py 中的 SemanticLogicReviewer 类
- """
- import pytest
- import asyncio
- import sys
- import os
- from unittest.mock import Mock, patch, AsyncMock, MagicMock
- from typing import Dict, Any
- # 添加项目根目录到路径
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
- from core.construction_review.component.reviewers.semantic_logic import (
- SemanticLogicReviewer,
- semantic_logic_reviewer,
- SEMANTIC_LOGIC_MODEL_CONFIG
- )
- from core.construction_review.component.reviewers.base_reviewer import ReviewResult
- class TestSemanticLogicReviewer:
- """语义逻辑审查器测试类"""
-
- @pytest.fixture
- def reviewer(self):
- """创建审查器实例"""
- return SemanticLogicReviewer()
-
- @pytest.fixture
- def mock_state(self):
- """创建模拟的状态字典"""
- mock_progress_manager = AsyncMock()
- mock_progress_manager.update_stage_progress = AsyncMock()
-
- return {
- "progress_manager": mock_progress_manager,
- "callback_task_id": "test_task_123"
- }
-
- @pytest.fixture
- def sample_review_content(self):
- """示例审查内容"""
- return """
- 施工方案概述:
- 本工程为高速公路桥梁施工项目,主要包括桥墩基础施工、桥梁上部结构施工等内容。
- 施工工期为12个月,计划2024年3月开工,2025年3月竣工。
- """
-
- @pytest.fixture
- def sample_review_references(self):
- """示例审查参考"""
- return "参考标准:《公路桥涵施工技术规范》JTG/T 3650-2020"
-
- def test_reviewer_initialization(self, reviewer):
- """测试审查器初始化"""
- assert reviewer is not None
- assert reviewer.model == SEMANTIC_LOGIC_MODEL_CONFIG["model"]
- assert reviewer.temperature == SEMANTIC_LOGIC_MODEL_CONFIG["temperature"]
- assert reviewer.max_tokens == SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"]
- assert reviewer.client is not None
-
- def test_global_singleton_instance(self):
- """测试全局单例实例"""
- assert semantic_logic_reviewer is not None
- assert isinstance(semantic_logic_reviewer, SemanticLogicReviewer)
-
- def test_model_config(self):
- """测试模型配置"""
- assert SEMANTIC_LOGIC_MODEL_CONFIG["base_url"] == "http://192.168.91.253:8003/v1"
- assert SEMANTIC_LOGIC_MODEL_CONFIG["api_key"] == "sk-123456"
- assert SEMANTIC_LOGIC_MODEL_CONFIG["model"] == "qwen3-30b"
- assert SEMANTIC_LOGIC_MODEL_CONFIG["temperature"] == 0.7
- assert SEMANTIC_LOGIC_MODEL_CONFIG["max_tokens"] == 2000
-
- @pytest.mark.asyncio
- async def test_check_semantic_logic_success(
- self,
- reviewer,
- sample_review_content,
- sample_review_references,
- mock_state
- ):
- """测试语义逻辑检查成功场景"""
- # 模拟 OpenAI API 响应
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "审查结果:内容逻辑清晰,无明显问题。"
-
- # 模拟提示词模板
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = "请审查以下内容"
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- # 执行测试
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_001",
- review_content=sample_review_content,
- review_references=sample_review_references,
- review_location_label="第一章",
- state=mock_state,
- stage_name="basic_check"
- )
-
- # 验证结果
- assert isinstance(result, ReviewResult)
- assert result.success is True
- assert result.details["name"] == "semantic_logic_check"
- assert "审查结果" in result.details["response"]
- assert result.error_message is None
- assert result.execution_time is not None
- assert result.execution_time > 0
-
- # 验证 API 调用
- mock_create.assert_called_once()
- call_kwargs = mock_create.call_args.kwargs
- assert call_kwargs["model"] == "qwen3-30b"
- assert call_kwargs["temperature"] == 0.7
- assert call_kwargs["max_tokens"] == 2000
-
- # 验证进度管理器被调用
- mock_state["progress_manager"].update_stage_progress.assert_called()
-
- @pytest.mark.asyncio
- async def test_check_semantic_logic_without_state(
- self,
- reviewer,
- sample_review_content
- ):
- """测试没有状态字典的语义逻辑检查"""
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "审查通过"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "system"
- mock_message.content = "系统提示"
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_002",
- review_content=sample_review_content,
- state=None,
- stage_name=None
- )
-
- assert result.success is True
- assert result.details["name"] == "semantic_logic_check"
-
- @pytest.mark.asyncio
- async def test_check_semantic_logic_api_error(
- self,
- reviewer,
- sample_review_content,
- mock_state
- ):
- """测试 API 调用失败场景"""
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = "测试内容"
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.side_effect = Exception("API连接失败")
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_003",
- review_content=sample_review_content,
- state=mock_state,
- stage_name="basic_check"
- )
-
- # 验证错误处理
- assert isinstance(result, ReviewResult)
- assert result.success is False
- assert result.error_message is not None
- assert "API连接失败" in result.error_message
- assert result.execution_time is not None
-
- # 验证错误通知被发送
- mock_state["progress_manager"].update_stage_progress.assert_called()
-
- @pytest.mark.asyncio
- async def test_check_semantic_logic_empty_content(
- self,
- reviewer,
- mock_state
- ):
- """测试空内容场景"""
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "内容为空,无法审查"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = ""
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_004",
- review_content="",
- state=mock_state,
- stage_name="basic_check"
- )
-
- assert result.success is True
- assert result.details["name"] == "semantic_logic_check"
-
- @pytest.mark.asyncio
- async def test_check_semantic_logic_with_references(
- self,
- reviewer,
- sample_review_content,
- sample_review_references
- ):
- """测试带参考信息的语义逻辑检查"""
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "根据参考标准,内容符合要求"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = "审查内容"
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_005",
- review_content=sample_review_content,
- review_references=sample_review_references
- )
-
- assert result.success is True
- assert "参考标准" in result.details["response"]
-
- # 验证提示词模板被正确调用
- mock_get_prompt.assert_called_once()
- call_kwargs = mock_get_prompt.call_args.kwargs
- assert call_kwargs["review_content"] == sample_review_content
- assert call_kwargs["review_references"] == sample_review_references
-
- @pytest.mark.asyncio
- async def test_message_format_conversion(self, reviewer, sample_review_content):
- """测试消息格式转换"""
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "测试响应"
-
- # 模拟不同类型的消息
- mock_prompt_template = MagicMock()
- mock_system_msg = MagicMock()
- mock_system_msg.type = "system"
- mock_system_msg.content = "系统提示"
-
- mock_user_msg = MagicMock()
- mock_user_msg.type = "user"
- mock_user_msg.content = "用户输入"
-
- mock_unknown_msg = MagicMock()
- mock_unknown_msg.type = "unknown"
- mock_unknown_msg.content = "未知类型"
-
- mock_prompt_template.format_messages.return_value = [
- mock_system_msg,
- mock_user_msg,
- mock_unknown_msg
- ]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_006",
- review_content=sample_review_content
- )
-
- # 验证消息格式转换
- call_kwargs = mock_create.call_args.kwargs
- messages = call_kwargs["messages"]
-
- assert len(messages) == 3
- assert messages[0]["role"] == "system"
- assert messages[0]["content"] == "系统提示"
- assert messages[1]["role"] == "user"
- assert messages[1]["content"] == "用户输入"
- assert messages[2]["role"] == "user" # unknown类型应转为user
- assert messages[2]["content"] == "未知类型"
-
- @pytest.mark.asyncio
- async def test_execution_time_tracking(self, reviewer, sample_review_content):
- """测试执行时间跟踪"""
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "测试"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = "测试"
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- async def slow_api_call(*args, **kwargs):
- """模拟慢速API调用"""
- await asyncio.sleep(0.1) # 模拟100ms延迟
- return mock_response
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.side_effect = slow_api_call
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_trace_007",
- review_content=sample_review_content
- )
-
- # 验证执行时间大于100ms
- assert result.execution_time >= 0.1
- assert result.execution_time < 1.0 # 应该不会太长
- class TestIntegration:
- """集成测试类"""
-
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_full_workflow(self):
- """测试完整工作流程(需要实际API可用)"""
- # 注意:此测试需要实际的API服务可用
- # 在CI/CD环境中可能需要跳过
- pytest.skip("需要实际API服务,跳过集成测试")
-
- reviewer = SemanticLogicReviewer()
-
- result = await reviewer.check_semantic_logic(
- trace_id="integration_test_001",
- review_content="测试施工方案内容",
- review_references="测试参考标准"
- )
-
- assert isinstance(result, ReviewResult)
- assert result.execution_time is not None
- class TestEdgeCases:
- """边界情况测试类"""
-
- @pytest.mark.asyncio
- async def test_very_long_content(self, reviewer):
- """测试超长内容"""
- long_content = "测试内容 " * 10000 # 非常长的内容
-
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "内容过长"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = long_content
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_edge_001",
- review_content=long_content
- )
-
- assert result.success is True
-
- @pytest.mark.asyncio
- async def test_special_characters(self, reviewer):
- """测试特殊字符"""
- special_content = "测试内容包含特殊字符:@#$%^&*(){}[]|\\:;\"'<>,.?/~`"
-
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "特殊字符处理正常"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = special_content
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_edge_002",
- review_content=special_content
- )
-
- assert result.success is True
-
- @pytest.mark.asyncio
- async def test_unicode_content(self, reviewer):
- """测试Unicode字符"""
- unicode_content = "测试内容包含各种语言:English, 中文, 日本語, 한국어, Русский, العربية, 🚀🎉"
-
- mock_response = MagicMock()
- mock_response.choices = [MagicMock()]
- mock_response.choices[0].message.content = "Unicode处理正常"
-
- mock_prompt_template = MagicMock()
- mock_message = MagicMock()
- mock_message.type = "user"
- mock_message.content = unicode_content
- mock_prompt_template.format_messages.return_value = [mock_message]
-
- with patch.object(reviewer.client.chat.completions, 'create', new_callable=AsyncMock) as mock_create, \
- patch('core.construction_review.component.reviewers.semantic_logic.prompt_loader.get_prompt_template') as mock_get_prompt:
-
- mock_create.return_value = mock_response
- mock_get_prompt.return_value = mock_prompt_template
-
- result = await reviewer.check_semantic_logic(
- trace_id="test_edge_003",
- review_content=unicode_content
- )
-
- assert result.success is True
- if __name__ == "__main__":
- # 运行测试
- pytest.main([__file__, "-v", "-s", "--tb=short"])
|