test(backend): unit/integration tests for automation, otp, neo4j, contract
新增自动化审批状态机/执行器/意图路由/会话管理、OTP 绑定流程、neo4j 客户端、响应契约、置信度门禁、环境门控、Tier1 API 等测试。
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 阶段5 自动化闭环 服务单元测试(简化版)
|
||||
# =============================================================================
|
||||
# 说明:简化版测试,直接导入服务类进行测试,避免 conftest 导入问题
|
||||
# 创建日期: 2026-07-01
|
||||
# =============================================================================
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 设置测试环境变量(在导入 app 之前)
|
||||
os.environ.setdefault("DEV_MODE", "true")
|
||||
os.environ.setdefault("WECOM_SCO_CALLBACK_BASE", "https://test.example.com")
|
||||
|
||||
# 测试意图路由 - 这些不需要数据库
|
||||
class TestIntentRouterSimple:
|
||||
"""简化版意图识别测试"""
|
||||
|
||||
def test_fallback_intent_virus_keywords(self):
|
||||
"""测试病毒关键词识别"""
|
||||
from app.services.automation.intent_router import IntentRouter
|
||||
|
||||
router = IntentRouter()
|
||||
|
||||
# 模拟 DifyClient._fallback_intent 方法的行为
|
||||
from app.integrations.dify import DifyClient
|
||||
|
||||
result = DifyClient._fallback_intent("电脑中病毒了")
|
||||
|
||||
assert result is not None
|
||||
assert "scenario_key" in result
|
||||
|
||||
def test_fallback_intent_password_keywords(self):
|
||||
"""测试密码重置关键词识别"""
|
||||
from app.integrations.dify import DifyClient
|
||||
|
||||
result = DifyClient._fallback_intent("我忘记密码了")
|
||||
|
||||
assert result.get("scenario_key") == "password_reset"
|
||||
|
||||
def test_fallback_intent_software_keywords(self):
|
||||
"""测试软件安装关键词识别"""
|
||||
from app.integrations.dify import DifyClient
|
||||
|
||||
result = DifyClient._fallback_intent("帮我安装一个软件")
|
||||
|
||||
assert result.get("scenario_key") == "software_install"
|
||||
|
||||
def test_fallback_intent_terminal_keywords(self):
|
||||
"""测试终端定位关键词识别"""
|
||||
from app.integrations.dify import DifyClient
|
||||
|
||||
result = DifyClient._fallback_intent("我的电脑在哪里")
|
||||
|
||||
assert result.get("scenario_key") == "terminal_locate"
|
||||
|
||||
def test_fallback_intent_unknown(self):
|
||||
"""测试未知意图"""
|
||||
from app.integrations.dify import DifyClient
|
||||
|
||||
result = DifyClient._fallback_intent("今天天气不错")
|
||||
|
||||
assert result.get("scenario_key") is None
|
||||
assert result.get("confidence") == 0.0
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""测试常量定义"""
|
||||
|
||||
def test_error_codes(self):
|
||||
"""测试错误码定义"""
|
||||
from app.constants import AutomationErrorCode
|
||||
|
||||
assert AutomationErrorCode.SCENARIO_NOT_FOUND == 4001
|
||||
assert AutomationErrorCode.INTENT_FAILED == 4002
|
||||
assert AutomationErrorCode.EXTERNAL_CALL_FAILED == 4003
|
||||
assert AutomationErrorCode.ACTION_REJECTED == 4004
|
||||
assert AutomationErrorCode.SESSION_NOT_FOUND == 4005
|
||||
assert AutomationErrorCode.APPROVAL_REJECTED == 4006
|
||||
assert AutomationErrorCode.TIMEOUT_HANDOFF == 4007
|
||||
assert AutomationErrorCode.EMPLOYEE_DECLINED == 4008
|
||||
assert AutomationErrorCode.CONFIG_ERROR == 4009
|
||||
assert AutomationErrorCode.MAPPING_FAILED == 4010
|
||||
assert AutomationErrorCode.INVALID_MODE == 4011
|
||||
assert AutomationErrorCode.ROLLBACK_FAILED == 4012
|
||||
|
||||
def test_risk_levels(self):
|
||||
"""测试风险等级"""
|
||||
from app.constants import (
|
||||
AUTOMATION_RISK_READ,
|
||||
AUTOMATION_RISK_LOW,
|
||||
AUTOMATION_RISK_HIGH,
|
||||
AUTOMATION_AUTO_EXECUTABLE_RISKS,
|
||||
)
|
||||
|
||||
assert AUTOMATION_RISK_READ == "read"
|
||||
assert AUTOMATION_RISK_LOW == "low"
|
||||
assert AUTOMATION_RISK_HIGH == "high"
|
||||
assert AUTOMATION_RISK_READ in AUTOMATION_AUTO_EXECUTABLE_RISKS
|
||||
assert AUTOMATION_RISK_LOW in AUTOMATION_AUTO_EXECUTABLE_RISKS
|
||||
assert AUTOMATION_RISK_HIGH not in AUTOMATION_AUTO_EXECUTABLE_RISKS
|
||||
|
||||
def test_session_states(self):
|
||||
"""测试会话状态"""
|
||||
from app.constants import (
|
||||
AUTOMATION_SESSION_CREATED,
|
||||
AUTOMATION_SESSION_RUNNING,
|
||||
AUTOMATION_SESSION_PAUSED,
|
||||
AUTOMATION_SESSION_RESOLVED,
|
||||
AUTOMATION_SESSION_CLOSED,
|
||||
AUTOMATION_SESSION_HANDOFF,
|
||||
AUTOMATION_SESSION_ERROR,
|
||||
AUTOMATION_SESSION_TERMINAL_STATES,
|
||||
)
|
||||
|
||||
assert AUTOMATION_SESSION_CREATED == "created"
|
||||
assert AUTOMATION_SESSION_RUNNING == "running"
|
||||
assert AUTOMATION_SESSION_PAUSED == "paused"
|
||||
assert AUTOMATION_SESSION_RESOLVED == "resolved"
|
||||
assert AUTOMATION_SESSION_CLOSED == "closed"
|
||||
assert AUTOMATION_SESSION_HANDOFF == "handoff"
|
||||
assert AUTOMATION_SESSION_ERROR == "error"
|
||||
|
||||
assert "closed" in AUTOMATION_SESSION_TERMINAL_STATES
|
||||
assert "handoff" in AUTOMATION_SESSION_TERMINAL_STATES
|
||||
assert "error" in AUTOMATION_SESSION_TERMINAL_STATES
|
||||
|
||||
def test_action_states(self):
|
||||
"""测试动作状态"""
|
||||
from app.constants import (
|
||||
AUTOMATION_ACTION_PENDING,
|
||||
AUTOMATION_ACTION_RUNNING,
|
||||
AUTOMATION_ACTION_SUCCESS,
|
||||
AUTOMATION_ACTION_FAILED,
|
||||
AUTOMATION_ACTION_SKIPPED,
|
||||
AUTOMATION_ACTION_AWAIT_APPROVAL,
|
||||
AUTOMATION_ACTION_APPROVED,
|
||||
AUTOMATION_ACTION_REJECTED,
|
||||
)
|
||||
|
||||
assert AUTOMATION_ACTION_PENDING == "pending"
|
||||
assert AUTOMATION_ACTION_RUNNING == "running"
|
||||
assert AUTOMATION_ACTION_SUCCESS == "success"
|
||||
assert AUTOMATION_ACTION_FAILED == "failed"
|
||||
assert AUTOMATION_ACTION_SKIPPED == "skipped"
|
||||
assert AUTOMATION_ACTION_AWAIT_APPROVAL == "await_approval"
|
||||
assert AUTOMATION_ACTION_APPROVED == "approved"
|
||||
assert AUTOMATION_ACTION_REJECTED == "rejected"
|
||||
|
||||
|
||||
class TestActionRegistry:
|
||||
"""测试动作注册表"""
|
||||
|
||||
def test_handler_registry_populated(self):
|
||||
"""测试处理器注册表已填充"""
|
||||
from app.services.automation.action_registry import (
|
||||
HANDLER_REGISTRY,
|
||||
register_builtin_handlers,
|
||||
)
|
||||
|
||||
# 确保内置处理器已注册
|
||||
register_builtin_handlers()
|
||||
|
||||
assert "terminal_locate" in HANDLER_REGISTRY
|
||||
assert "virus_scan" in HANDLER_REGISTRY
|
||||
assert "virus_quarantine" in HANDLER_REGISTRY
|
||||
assert "software_install_guide" in HANDLER_REGISTRY
|
||||
assert "password_reset_link" in HANDLER_REGISTRY
|
||||
|
||||
def test_get_handler_returns_correct_type(self):
|
||||
"""测试获取处理器返回正确类型"""
|
||||
from app.services.automation.action_registry import (
|
||||
get_handler,
|
||||
BaseActionHandler,
|
||||
)
|
||||
|
||||
handler = get_handler("software_install_guide")
|
||||
|
||||
assert handler is not None
|
||||
assert isinstance(handler, BaseActionHandler)
|
||||
assert handler.action_type == "software_install_guide"
|
||||
|
||||
def test_get_handler_unknown_returns_none(self):
|
||||
"""测试获取未知处理器返回 None"""
|
||||
from app.services.automation.action_registry import get_handler
|
||||
|
||||
handler = get_handler("unknown_action_type")
|
||||
|
||||
assert handler is None
|
||||
|
||||
|
||||
class TestExceptionHandler:
|
||||
"""测试异常处理"""
|
||||
|
||||
def test_automation_exception_creation(self):
|
||||
"""测试自动化异常创建"""
|
||||
from app.services.automation.exception_handler import AutomationException
|
||||
from app.constants import AutomationErrorCode
|
||||
|
||||
exc = AutomationException(
|
||||
AutomationErrorCode.SESSION_NOT_FOUND,
|
||||
"会话不存在"
|
||||
)
|
||||
|
||||
assert exc.code == AutomationErrorCode.SESSION_NOT_FOUND
|
||||
assert exc.message == "会话不存在"
|
||||
|
||||
def test_automation_exception_str(self):
|
||||
"""测试异常字符串表示"""
|
||||
from app.services.automation.exception_handler import AutomationException
|
||||
from app.constants import AutomationErrorCode
|
||||
|
||||
exc = AutomationException(
|
||||
AutomationErrorCode.INTENT_FAILED,
|
||||
"意图识别失败"
|
||||
)
|
||||
|
||||
exc_str = str(exc)
|
||||
assert "4002" in exc_str
|
||||
assert "意图识别失败" in exc_str
|
||||
|
||||
|
||||
class TestDefaultScenarioConfigs:
|
||||
"""测试默认场景配置"""
|
||||
|
||||
def test_default_configs_exist(self):
|
||||
"""测试默认配置存在"""
|
||||
from app.services.automation import DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
assert "terminal_locate" in DEFAULT_SCENARIO_CONFIGS
|
||||
assert "virus_dispose" in DEFAULT_SCENARIO_CONFIGS
|
||||
assert "software_install" in DEFAULT_SCENARIO_CONFIGS
|
||||
assert "password_reset" in DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
def test_terminal_locate_config(self):
|
||||
"""测试终端定位配置"""
|
||||
from app.services.automation import DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
config = DEFAULT_SCENARIO_CONFIGS["terminal_locate"]
|
||||
|
||||
assert config["name"] == "终端定位"
|
||||
assert config["enabled"] is True
|
||||
assert "actions" in config
|
||||
assert len(config["actions"]) > 0
|
||||
|
||||
def test_virus_dispose_config(self):
|
||||
"""测试病毒处置配置"""
|
||||
from app.services.automation import DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
config = DEFAULT_SCENARIO_CONFIGS["virus_dispose"]
|
||||
|
||||
assert config["name"] == "病毒查杀处置"
|
||||
assert config["enabled"] is True
|
||||
assert "actions" in config
|
||||
# 应该有扫描和隔离两个动作
|
||||
action_types = [a["action_type"] for a in config["actions"]]
|
||||
assert "virus_scan" in action_types
|
||||
assert "virus_quarantine" in action_types
|
||||
|
||||
def test_software_install_config(self):
|
||||
"""测试软件安装配置"""
|
||||
from app.services.automation import DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
config = DEFAULT_SCENARIO_CONFIGS["software_install"]
|
||||
|
||||
assert config["name"] == "软件自助安装"
|
||||
assert config["enabled"] is True
|
||||
assert len(config["actions"]) == 1
|
||||
assert config["actions"][0]["action_type"] == "software_install_guide"
|
||||
|
||||
def test_password_reset_config(self):
|
||||
"""测试密码重置配置"""
|
||||
from app.services.automation import DEFAULT_SCENARIO_CONFIGS
|
||||
|
||||
config = DEFAULT_SCENARIO_CONFIGS["password_reset"]
|
||||
|
||||
assert config["name"] == "密码重置"
|
||||
assert config["enabled"] is True
|
||||
assert len(config["actions"]) == 1
|
||||
assert config["actions"][0]["action_type"] == "password_reset_link"
|
||||
# 密码重置是高风险操作,需要 H5 确认
|
||||
assert config["actions"][0]["risk_level"] == "high"
|
||||
assert config["actions"][0]["confirm_channel"] == "h5"
|
||||
Reference in New Issue
Block a user