305 lines
10 KiB
Python
305 lines
10 KiB
Python
"""
|
||
依赖解析器单元测试
|
||
"""
|
||
import pytest
|
||
from typing import List
|
||
from unittest.mock import Mock, MagicMock
|
||
|
||
from app.models.risk_detection import DetectionRule, RiskLevel
|
||
from app.services.risk_detection.engine.dependency_resolver import (
|
||
DependencyResolver,
|
||
DependencyGraph,
|
||
DependencyNode,
|
||
)
|
||
|
||
|
||
class TestDependencyNode:
|
||
"""依赖节点测试"""
|
||
|
||
def test_init(self):
|
||
"""测试节点初始化"""
|
||
node = DependencyNode(
|
||
rule_id="rule_1",
|
||
algorithm_code="ALGO_1",
|
||
dependencies=["rule_0"],
|
||
dependents=["rule_2"]
|
||
)
|
||
|
||
assert node.rule_id == "rule_1"
|
||
assert node.algorithm_code == "ALGO_1"
|
||
assert node.dependencies == ["rule_0"]
|
||
assert node.dependents == ["rule_2"]
|
||
assert node.level == 0
|
||
|
||
|
||
class TestDependencyGraph:
|
||
"""依赖图测试"""
|
||
|
||
def setup_method(self):
|
||
"""每个测试方法前执行"""
|
||
self.graph = DependencyGraph()
|
||
|
||
def test_add_node(self):
|
||
"""测试添加节点"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
|
||
assert "rule_1" in self.graph.nodes
|
||
assert self.graph.nodes["rule_1"].rule_id == "rule_1"
|
||
assert self.graph.nodes["rule_1"].algorithm_code == "ALGO_1"
|
||
assert self.graph.graph["rule_1"] == []
|
||
assert self.graph.reverse_graph["rule_1"] == []
|
||
assert self.graph.in_degree["rule_1"] == 0
|
||
|
||
def test_add_edge(self):
|
||
"""测试添加依赖边"""
|
||
# 添加节点
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
|
||
# 添加边:rule_2 依赖 rule_1
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
|
||
# 验证依赖关系
|
||
assert "rule_1" in self.graph.nodes["rule_2"].dependencies
|
||
assert "rule_2" in self.graph.nodes["rule_1"].dependents
|
||
|
||
# 验证入度
|
||
assert self.graph.in_degree["rule_1"] == 0
|
||
assert self.graph.in_degree["rule_2"] == 1
|
||
|
||
def test_add_edge_invalid_rule(self):
|
||
"""测试添加边时规则不存在"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
|
||
with pytest.raises(ValueError, match="规则不存在"):
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
|
||
def test_has_cycle_no_cycle(self):
|
||
"""测试无循环依赖"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
self.graph.add_node("rule_3", "ALGO_3")
|
||
|
||
# rule_2 依赖 rule_1, rule_3 依赖 rule_2
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
self.graph.add_edge("rule_3", "rule_2")
|
||
|
||
has_cycle, cycle_path = self.graph.has_cycle()
|
||
|
||
assert not has_cycle
|
||
assert cycle_path is None
|
||
|
||
def test_has_cycle_with_cycle(self):
|
||
"""测试有循环依赖"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
self.graph.add_node("rule_3", "ALGO_3")
|
||
|
||
# 创建循环:rule_1 -> rule_2 -> rule_3 -> rule_1
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
self.graph.add_edge("rule_3", "rule_2")
|
||
self.graph.add_edge("rule_1", "rule_3")
|
||
|
||
has_cycle, cycle_path = self.graph.has_cycle()
|
||
|
||
assert has_cycle
|
||
assert cycle_path is not None
|
||
assert len(cycle_path) >= 3 # 至少包含3个节点
|
||
|
||
def test_calculate_levels(self):
|
||
"""测试计算层级"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
self.graph.add_node("rule_3", "ALGO_3")
|
||
|
||
# rule_2 依赖 rule_1, rule_3 依赖 rule_2
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
self.graph.add_edge("rule_3", "rule_2")
|
||
|
||
self.graph.calculate_levels()
|
||
|
||
# 验证层级
|
||
assert self.graph.nodes["rule_1"].level == 0
|
||
assert self.graph.nodes["rule_2"].level == 1
|
||
assert self.graph.nodes["rule_3"].level == 2
|
||
|
||
def test_get_levels(self):
|
||
"""测试获取层级分组"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
self.graph.add_node("rule_3", "ALGO_3")
|
||
|
||
# rule_2 依赖 rule_1, rule_3 依赖 rule_2
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
self.graph.add_edge("rule_3", "rule_2")
|
||
|
||
self.graph.calculate_levels()
|
||
levels = self.graph.get_levels()
|
||
|
||
# 验证层级结构
|
||
assert len(levels) == 3 # 3个层级
|
||
assert "rule_1" in levels[0]
|
||
assert "rule_2" in levels[1]
|
||
assert "rule_3" in levels[2]
|
||
|
||
def test_to_dict(self):
|
||
"""测试转换为字典"""
|
||
self.graph.add_node("rule_1", "ALGO_1")
|
||
self.graph.add_node("rule_2", "ALGO_2")
|
||
|
||
self.graph.add_edge("rule_2", "rule_1")
|
||
self.graph.calculate_levels()
|
||
|
||
result = self.graph.to_dict()
|
||
|
||
assert "nodes" in result
|
||
assert "levels" in result
|
||
assert len(result["nodes"]) == 2
|
||
assert len(result["levels"]) >= 1
|
||
|
||
|
||
class TestDependencyResolver:
|
||
"""依赖解析器测试"""
|
||
|
||
def setup_method(self):
|
||
"""每个测试方法前执行"""
|
||
self.resolver = DependencyResolver()
|
||
|
||
def _create_mock_rule(self, rule_id: str, algorithm_code: str, dependencies: List[str] = None) -> DetectionRule:
|
||
"""创建模拟规则"""
|
||
rule = Mock(spec=DetectionRule)
|
||
rule.rule_id = rule_id
|
||
rule.algorithm_code = algorithm_code
|
||
rule.parameters = {"dependencies": dependencies} if dependencies else None
|
||
return rule
|
||
|
||
def test_analyze_simple_dependencies(self):
|
||
"""测试简单依赖分析"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1"),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
]
|
||
|
||
graph = self.resolver.analyze(rules)
|
||
|
||
assert graph is not None
|
||
assert "rule_1" in graph.nodes
|
||
assert "rule_2" in graph.nodes
|
||
assert "rule_1" in graph.nodes["rule_2"].dependencies
|
||
|
||
def test_analyze_with_cycle(self):
|
||
"""测试循环依赖检测"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1", ["rule_2"]),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
]
|
||
|
||
with pytest.raises(ValueError, match="检测到循环依赖"):
|
||
self.resolver.analyze(rules)
|
||
|
||
def test_analyze_with_default_dependencies(self):
|
||
"""测试使用默认依赖关系"""
|
||
# 设置默认依赖
|
||
self.resolver.DEFAULT_ALGORITHM_DEPENDENCIES = {
|
||
"ALGO_2": ["ALGO_1"],
|
||
}
|
||
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1"),
|
||
self._create_mock_rule("rule_2", "ALGO_2"), # 无显式依赖,使用默认
|
||
]
|
||
|
||
graph = self.resolver.analyze(rules, use_default_dependencies=True)
|
||
|
||
assert "rule_1" in graph.nodes["rule_2"].dependencies
|
||
|
||
def test_validate_valid_dependencies(self):
|
||
"""测试有效依赖验证"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1"),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
]
|
||
|
||
is_valid, error = self.resolver.validate(rules)
|
||
|
||
assert is_valid
|
||
assert error is None
|
||
|
||
def test_validate_invalid_dependencies(self):
|
||
"""测试无效依赖验证"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1", ["rule_2"]),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
]
|
||
|
||
is_valid, error = self.resolver.validate(rules)
|
||
|
||
assert not is_valid
|
||
assert error is not None
|
||
assert "循环依赖" in error
|
||
|
||
def test_extract_dependencies_from_params(self):
|
||
"""测试从参数中提取依赖"""
|
||
rule = self._create_mock_rule("rule_1", "ALGO_1", ["rule_2", "rule_3"])
|
||
rule_dict = {"ALGO_2": "rule_2", "ALGO_3": "rule_3"}
|
||
|
||
deps = self.resolver._extract_dependencies(rule, rule_dict, False)
|
||
|
||
assert "rule_2" in deps
|
||
assert "rule_3" in deps
|
||
|
||
def test_extract_dependencies_from_algorithm_code(self):
|
||
"""测试从算法代码提取依赖"""
|
||
rule = self._create_mock_rule("rule_1", "ALGO_1") # 无参数依赖
|
||
rule_dict = {"ALGO_2": "rule_2"}
|
||
|
||
# 设置默认依赖
|
||
self.resolver.DEFAULT_ALGORITHM_DEPENDENCIES = {
|
||
"ALGO_1": ["ALGO_2"],
|
||
}
|
||
|
||
deps = self.resolver._extract_dependencies(rule, rule_dict, True)
|
||
|
||
assert "rule_2" in deps
|
||
|
||
def test_get_execution_order(self):
|
||
"""测试获取执行顺序"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1"),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
self._create_mock_rule("rule_3", "ALGO_3", ["rule_2"]),
|
||
]
|
||
|
||
self.resolver.analyze(rules)
|
||
execution_order = self.resolver.get_execution_order()
|
||
|
||
assert len(execution_order) == 3
|
||
# 验证顺序:rule_1 -> rule_2 -> rule_3
|
||
assert execution_order.index("rule_1") < execution_order.index("rule_2")
|
||
assert execution_order.index("rule_2") < execution_order.index("rule_3")
|
||
|
||
def test_get_execution_levels(self):
|
||
"""测试获取执行层级"""
|
||
rules = [
|
||
self._create_mock_rule("rule_1", "ALGO_1"),
|
||
self._create_mock_rule("rule_2", "ALGO_2", ["rule_1"]),
|
||
self._create_mock_rule("rule_3", "ALGO_3", ["rule_1"]),
|
||
]
|
||
|
||
self.resolver.analyze(rules)
|
||
levels = self.resolver.get_execution_levels()
|
||
|
||
assert len(levels) == 2 # 两个层级
|
||
assert "rule_1" in levels[0]
|
||
assert "rule_2" in levels[1] or "rule_3" in levels[1]
|
||
|
||
def test_get_execution_order_without_analyze(self):
|
||
"""测试未分析依赖时获取执行顺序"""
|
||
with pytest.raises(ValueError, match="尚未进行依赖分析"):
|
||
self.resolver.get_execution_order()
|
||
|
||
def test_get_execution_levels_without_analyze(self):
|
||
"""测试未分析依赖时获取执行层级"""
|
||
with pytest.raises(ValueError, match="尚未进行依赖分析"):
|
||
self.resolver.get_execution_levels()
|