Files
document_analyzer/tests/test_detect_conflicts.py
pzhang_zywl 682dedb4b4
CI / test (pull_request) Successful in 9s
fix: 完善 UT 覆盖,统一 pytest 测试发现 - Closes #2
- 新建 pytest.ini 统一 test discovery(tests/ + skills/ir_generation_skill/tests/)
- test_step1~3 转换为 pytest 兼容格式,无输出文件时自动 skip
- 新增 tests/test_detect_conflicts.py(18 个纯函数单测)
- 新增 tests/test_config.py(7 个配置模块单测)
- CI 改为 pytest -v 使用 pytest.ini testpaths
- DEV_AGENT.md 新增 PR 提交规范

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 00:07:07 +08:00

201 lines
7.2 KiB
Python

"""Unit tests for pure functions in detect_conflicts.py — no LLM calls, no file I/O."""
import json
import sys
from pathlib import Path
import pytest
# Add detect_conflicts script to path
sys.path.insert(0, str(Path(__file__).parent.parent / "skills" / "conflict_detection_skill" / "scripts"))
import detect_conflicts as dc
# ── _is_nested_tree ──────────────────────────────────────────────────────────
def test_is_nested_tree_with_children_list():
assert dc._is_nested_tree({"children": []}) is True
def test_is_nested_tree_without_children_list():
assert dc._is_nested_tree({"nodes": []}) is False
def test_is_nested_tree_empty_dict():
assert dc._is_nested_tree({}) is False
# ── _nested_tree_to_text ─────────────────────────────────────────────────────
def test_nested_tree_simple():
tree = {
"id": "N1",
"name": "开始",
"type": "start",
"children": [
{
"id": "N2",
"name": "判断条件",
"type": "decision",
"children": [
{
"condition": "",
"node": {
"id": "N3",
"name": "执行动作",
"type": "action",
},
},
{
"condition": "",
"node": {
"id": "N4",
"name": "结束",
"type": "end",
},
},
],
},
],
}
result = dc._nested_tree_to_text(tree)
assert "[起始] N1: 开始" in result
assert "[判断] N2: 判断条件" in result
assert '分支 "":' in result
assert "[动作] N3: 执行动作" in result
assert '分支 "":' in result
assert "[结束] N4: 结束" in result
def test_nested_tree_empty_children():
tree = {"id": "N1", "name": "结束", "type": "end"}
result = dc._nested_tree_to_text(tree)
assert "[结束] N1: 结束" in result
# ── _flat_tree_to_text ───────────────────────────────────────────────────────
def test_flat_tree_with_decision_and_action():
lt = {
"root": "START",
"nodes": [
{"id": "D1", "type": "decision", "condition": "车速>15",
"branches": [{"value": "", "target": "A1"}, {"value": "", "target": "END"}]},
{"id": "A1", "type": "action", "description": "禁止视频播放"},
{"id": "END", "type": "end", "description": "结束"},
],
}
result = dc._flat_tree_to_text(lt)
assert "根节点: START" in result
assert '判断节点 D1: 条件="车速>15"' in result
assert '分支 "" → A1' in result
assert "动作节点 A1: 禁止视频播放" in result
def test_flat_tree_empty():
result = dc._flat_tree_to_text({})
assert result == ""
# ── _logic_tree_to_text (dispatcher) ─────────────────────────────────────────
def test_logic_tree_to_text_nested():
lt = {"children": [{"id": "N1", "name": "开始", "type": "start"}]}
result = dc._logic_tree_to_text(lt)
assert "[起始] N1: 开始" in result
def test_logic_tree_to_text_flat():
lt = {"nodes": [{"id": "A1", "type": "action", "description": "测试"}]}
result = dc._logic_tree_to_text(lt)
assert "动作节点 A1: 测试" in result
# ── _parse_conflict_json ─────────────────────────────────────────────────────
def test_parse_no_conflict():
assert dc._parse_conflict_json("[[NO_CONFLICT]]") == []
assert dc._parse_conflict_json("some text [[NO_CONFLICT]] more") == []
def test_parse_conflict_valid_json():
content = json.dumps([
{"conflict_type": "condition_mismatch", "severity": "high",
"section": "3.1", "image_snippet": "img", "text_snippet": "txt",
"description": "冲突描述"}
], ensure_ascii=False)
result = dc._parse_conflict_json(content)
assert len(result) == 1
assert result[0]["conflict_type"] == "condition_mismatch"
def test_parse_conflict_with_markdown_fence():
content = '```json\n[{"conflict_type": "contradiction", "severity": "high", "section": "1.0", "image_snippet": "a", "text_snippet": "b", "description": "x"}]\n```'
result = dc._parse_conflict_json(content)
assert len(result) == 1
assert result[0]["conflict_type"] == "contradiction"
def test_parse_conflict_unparseable():
assert dc._parse_conflict_json("not valid json at all") == []
assert dc._parse_conflict_json("") == []
def test_parse_conflict_mixed_content():
content = 'some prefix text\n[\n {"conflict_type": "scope_mismatch", "severity": "low", "section": "4.2", "image_snippet": "img", "text_snippet": "txt", "description": "d"}]\nsuffix'
result = dc._parse_conflict_json(content)
assert len(result) == 1
assert result[0]["conflict_type"] == "scope_mismatch"
# ── _build_text_for_section ──────────────────────────────────────────────────
def test_build_text_para_blocks():
sections = [
{"source": "3.1 测试章节",
"blocks": [
{"type": "para", "text": "第一段文字"},
{"type": "para", "text": "第二段文字"},
]},
]
result = dc._build_text_for_section(sections, "3.1 测试章节")
assert "第一段文字" in result
assert "第二段文字" in result
def test_build_text_table_blocks():
sections = [
{"source": "4.0 功能列表",
"blocks": [
{"type": "table", "table": "功能表",
"rows": [
{"columns": [{"name": "功能", "text": "视频播放"}, {"name": "状态", "text": "禁止"}]},
]},
]},
]
result = dc._build_text_for_section(sections, "4.0 功能列表")
assert "功能表" in result
assert "视频播放" in result
assert "禁止" in result
def test_build_text_section_not_found():
sections = [{"source": "1.0 概述", "blocks": []}]
result = dc._build_text_for_section(sections, "2.0 不存在的章节")
assert result == ""
def test_build_text_mixed_blocks():
sections = [
{"source": "5.1 混合章节",
"blocks": [
{"type": "para", "text": "介绍文字"},
{"type": "table", "table": "表1",
"rows": [{"columns": [{"name": "A", "text": "值1"}]}]},
]},
]
result = dc._build_text_for_section(sections, "5.1 混合章节")
assert "介绍文字" in result
assert "表1" in result
assert "值1" in result