"""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