test_outline_review.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # -*- coding: utf-8 -*-
  2. """
  3. 大纲审查功能单元测试
  4. 测试OutlineReviewer.outline_review方法的入参出参
  5. """
  6. import pytest
  7. import asyncio
  8. import json
  9. from typing import Dict, Any
  10. import sys
  11. import os
  12. # 添加项目根目录到Python路径
  13. sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
  14. from core.construction_review.component.reviewers.outline_reviewer import OutlineReviewer
  15. class TestOutlineReview:
  16. """大纲审查功能测试类"""
  17. def setup_method(self):
  18. """测试前置设置"""
  19. self.outline_reviewer = OutlineReviewer()
  20. self.test_trace_id = "test_outline_review_001"
  21. self.test_stage_name = "test_stage"
  22. self.test_state = {"status": "testing"}
  23. def load_test_data(self) -> Dict[str, Any]:
  24. """从测试数据文件加载大纲结构"""
  25. test_data_path = os.path.join(
  26. os.path.dirname(__file__), '..', '..', '..',
  27. 'temp', 'document_temp', '文档切分预处理结果.json'
  28. )
  29. try:
  30. with open(test_data_path, 'r', encoding='utf-8') as f:
  31. data = json.load(f)
  32. return data.get('outline', {})
  33. except Exception as e:
  34. print(f"加载测试数据失败: {e}")
  35. return self._get_mock_outline_data()
  36. def _get_mock_outline_data(self) -> Dict[str, Any]:
  37. """获取模拟大纲数据"""
  38. return {
  39. "chapters": [
  40. {
  41. "index": 1,
  42. "title": "第一章编制依据",
  43. "page": "1",
  44. "original": "第一章编制依据...................................................... 1",
  45. "subsections": [
  46. {
  47. "title": "一、编制依据",
  48. "page": "1",
  49. "level": 2,
  50. "original": "一、编制依据................................................................. 1"
  51. },
  52. {
  53. "title": "二、编制原则",
  54. "page": "3",
  55. "level": 2,
  56. "original": "二、编制原则................................................................. 3"
  57. }
  58. ]
  59. },
  60. {
  61. "index": 2,
  62. "title": "第二章工程概况",
  63. "page": "5",
  64. "original": "第二章工程概况...................................................... 5",
  65. "subsections": [
  66. {
  67. "title": "一、项目背景",
  68. "page": "5",
  69. "level": 2,
  70. "original": "一、项目背景................................................................. 5"
  71. },
  72. {
  73. "title": "二、工程地质",
  74. "page": "6",
  75. "level": 2,
  76. "original": "二、工程地质................................................................. 6"
  77. }
  78. ]
  79. }
  80. ]
  81. }
  82. def prepare_review_data(self, outline_data: Dict[str, Any]) -> Dict[str, Any]:
  83. """准备审查数据,模拟ai_review_engine.py中的数据处理逻辑"""
  84. chapters = outline_data.get('chapters', [])
  85. # 1. 获取整体大纲(1级大纲目录)
  86. overall_outline = ""
  87. for chapter in chapters:
  88. overall_outline += f"{chapter['title']} (页码: {chapter['page']})\n"
  89. # 2. 获取大纲各章节及其子目录的详细信息
  90. detailed_outline = []
  91. for chapter in chapters:
  92. # 追加章节标题和页码
  93. detailed_outline.append(f"\n{chapter['title']}\n")
  94. detailed_outline.append(f"页码: {chapter['page']}")
  95. # 添加子目录
  96. subsections = chapter.get('subsections', [])
  97. if subsections:
  98. detailed_outline.append("\n")
  99. for subsection in subsections:
  100. indent = " " * (subsection['level'] - 1)
  101. detailed_outline.append(f"{indent}- {subsection['title']} (页码: {subsection['page']})\n")
  102. else:
  103. detailed_outline.append("\n")
  104. return {
  105. 'outline_content': outline_data,
  106. 'overall_outline': overall_outline,
  107. 'detailed_outline': detailed_outline,
  108. 'state': self.test_state,
  109. 'stage_name': self.test_stage_name
  110. }
  111. @pytest.mark.asyncio
  112. async def test_outline_review_basic_functionality(self):
  113. """测试大纲审查基本功能"""
  114. print("\n=== 测试大纲审查基本功能 ===")
  115. # 加载测试数据
  116. outline_data = self.load_test_data()
  117. print(f"加载的大纲章节数量: {len(outline_data.get('chapters', []))}")
  118. # 准备审查数据
  119. review_data = self.prepare_review_data(outline_data)
  120. print(f"整体大纲内容预览:")
  121. print(f"{review_data['overall_outline'][:200]}...")
  122. print(f"详细大纲项目数量: {len(review_data['detailed_outline'])}")
  123. for i, item in enumerate(review_data['detailed_outline'][:5]):
  124. if item.strip():
  125. print(f" {i+1}. {item.strip()[:50]}...")
  126. try:
  127. # 执行大纲审查
  128. print("\n开始执行大纲审查...")
  129. result = await self.outline_reviewer.outline_review(
  130. review_data, self.test_trace_id, self.test_stage_name, self.test_state
  131. )
  132. # 验证结果
  133. print(f"\n=== 审查结果 ===")
  134. print(f"审查成功状态: {result.get('success', False)}")
  135. print(f"执行时间: {result.get('execution_time', 0):.2f}秒")
  136. print(f"详细项目总数: {result.get('total_detailed_items', 0)}")
  137. # 阶段1结果
  138. stage1_result = result.get('stage1_overall_review')
  139. if stage1_result:
  140. print(f"\n--- 阶段1:整体大纲审查结果 ---")
  141. print(f"整体大纲审查成功: {stage1_result.get('success', False)}")
  142. parsed_overall = stage1_result.get('parsed_result')
  143. if parsed_overall and isinstance(parsed_overall, list):
  144. print(f"十大类章节审查结果数量: {len(parsed_overall)}")
  145. for item in parsed_overall[:3]: # 只显示前3个
  146. chapter_category = item.get('章节类别', 'N/A')
  147. status = item.get('完整性状态', 'N/A')
  148. # 替换特殊字符避免编码问题
  149. status_safe = status.replace('✔', '[完整]').replace('⚠', '[部分]').replace('✘', '[缺失]')
  150. print(f" - {chapter_category}: {status_safe}")
  151. # 阶段2结果
  152. stage2_result = result.get('stage2_detailed_review', [])
  153. print(f"\n--- 阶段2:详细大纲逐项审查结果 ---")
  154. print(f"详细项目审查结果数量: {len(stage2_result)}")
  155. success_count = 0
  156. for i, item_result in enumerate(stage2_result[:5]): # 只显示前5个
  157. item_index = item_result.get('item_index', i)
  158. outline_item = item_result.get('outline_item', '')[:50]
  159. review_result = item_result.get('review_result', {})
  160. print(f" 项目{item_index+1}: {outline_item}...")
  161. print(f" 审查成功: {review_result.get('success', False)}")
  162. if review_result.get('success'):
  163. category = review_result.get('category', 'N/A')
  164. parsed_result = review_result.get('parsed_result')
  165. if parsed_result and isinstance(parsed_result, list):
  166. print(f" 分类: {category}")
  167. print(f" 审查项目数: {len(parsed_result)}")
  168. success_count += 1
  169. else:
  170. print(f" 错误: {review_result.get('error_message', 'N/A')}")
  171. print(f"\n前5个项目中成功审查数量: {success_count}")
  172. # 断言基本结果
  173. assert result.get('success', False), "大纲审查应该成功"
  174. assert 'stage1_overall_review' in result, "应该包含阶段1审查结果"
  175. assert 'stage2_detailed_review' in result, "应该包含阶段2审查结果"
  176. assert isinstance(result.get('stage2_detailed_review', []), list), "阶段2结果应该是列表"
  177. print(f"\n[测试通过] 大纲审查基本功能正常")
  178. except Exception as e:
  179. print(f"\n[测试失败] {str(e)}")
  180. raise
  181. @pytest.mark.asyncio
  182. async def test_outline_review_with_empty_data(self):
  183. """测试空数据的大纲审查"""
  184. print("\n=== 测试空数据的大纲审查 ===")
  185. # 准备空数据
  186. empty_review_data = {
  187. 'outline_content': {},
  188. 'overall_outline': '',
  189. 'detailed_outline': [],
  190. 'state': self.test_state,
  191. 'stage_name': self.test_stage_name
  192. }
  193. try:
  194. result = await self.outline_reviewer.outline_review(
  195. empty_review_data, self.test_trace_id, self.test_stage_name, self.test_state
  196. )
  197. print(f"空数据审查结果: {result.get('success', False)}")
  198. print(f"错误信息: {result.get('error_message', 'N/A')}")
  199. # 空数据应该处理失败
  200. assert not result.get('success', False), "空数据应该审查失败"
  201. print(f"\n[测试通过] 空数据正确处理失败")
  202. except Exception as e:
  203. print(f"期望的异常: {str(e)}")
  204. print(f"\n[测试通过] 空数据正确抛出异常")
  205. def test_data_preparation_logic(self):
  206. """测试数据准备逻辑"""
  207. print("\n=== 测试数据准备逻辑 ===")
  208. # 加载测试数据
  209. outline_data = self.load_test_data()
  210. review_data = self.prepare_review_data(outline_data)
  211. # 验证数据格式
  212. assert 'overall_outline' in review_data, "应该包含整体大纲"
  213. assert 'detailed_outline' in review_data, "应该包含详细大纲"
  214. assert isinstance(review_data['detailed_outline'], list), "详细大纲应该是列表格式"
  215. print(f"整体大纲长度: {len(review_data['overall_outline'])}")
  216. print(f"详细大纲项目数: {len(review_data['detailed_outline'])}")
  217. # 验证整体大纲格式
  218. overall_lines = review_data['overall_outline'].strip().split('\n')
  219. print(f"整体大纲行数: {len(overall_lines)}")
  220. # 验证详细大纲格式
  221. non_empty_items = [item for item in review_data['detailed_outline'] if item.strip()]
  222. print(f"非空详细大纲项目数: {len(non_empty_items)}")
  223. print(f"\n[测试通过] 数据准备逻辑正确")
  224. if __name__ == "__main__":
  225. # 运行测试
  226. pytest.main([__file__, "-v", "-s"])