ForcePilot/backend/test/unit/channels/test_irc_analysis.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

319 lines
11 KiB
Python

from __future__ import annotations
import os
import pytest
from yuxi.channels.adapters.irc._analysis.agents import (
CodeFunctionAgent,
CodeFunctionResult,
CodeQualityAgent,
CodeQualityResult,
CodeIssue,
WebResearchAgent,
WebResearchResult,
)
from yuxi.channels.adapters.irc._analysis.orchestrator import (
AnalysisOrchestrator,
AnalysisResult,
FixPlan,
FixPlanItem,
GapReport,
)
MODULE_DIR = os.path.dirname(os.path.abspath(__file__)).rsplit("test", 1)[0]
IRC_MODULE_DIR = os.path.join(
MODULE_DIR, "package", "yuxi", "channels", "adapters", "irc"
)
class TestWebResearchAgent:
@pytest.mark.asyncio
async def test_execute_returns_result(self):
agent = WebResearchAgent()
result = await agent.execute()
assert isinstance(result, WebResearchResult)
assert len(result.industry_standards) >= 2
assert len(result.reference_implementations) >= 2
assert len(result.best_practices) >= 8
assert len(result.capability_gaps) >= 8
@pytest.mark.asyncio
async def test_industry_standards_have_required_fields(self):
agent = WebResearchAgent()
result = await agent.execute()
for std in result.industry_standards:
assert "name" in std
assert "url" in std
assert "key_points" in std
assert len(std["key_points"]) >= 3
@pytest.mark.asyncio
async def test_reference_implementations_have_features(self):
agent = WebResearchAgent()
result = await agent.execute()
for impl in result.reference_implementations:
assert "name" in impl
assert "features" in impl
assert len(impl["features"]) >= 2
@pytest.mark.asyncio
async def test_best_practices_cover_key_areas(self):
agent = WebResearchAgent()
result = await agent.execute()
areas = {bp["area"] for bp in result.best_practices}
assert "SASL认证" in areas
assert "TLS安全" in areas
assert "消息标签" in areas
@pytest.mark.asyncio
async def test_capability_gaps_identify_major_gaps(self):
agent = WebResearchAgent()
result = await agent.execute()
gaps_lower = [g.lower() for g in result.capability_gaps]
assert any("monitor" in g for g in gaps_lower)
assert any("sasl" in g for g in gaps_lower)
def test_to_dict(self):
result = WebResearchResult(
industry_standards=[{"name": "test"}],
capability_gaps=["gap1"],
)
d = result.to_dict()
assert d["industry_standards"] == [{"name": "test"}]
assert d["capability_gaps"] == ["gap1"]
class TestCodeFunctionAgent:
@pytest.mark.asyncio
async def test_execute_returns_result(self):
agent = CodeFunctionAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert isinstance(result, CodeFunctionResult)
assert len(result.module_structure) >= 5
assert len(result.implemented_features) >= 10
@pytest.mark.asyncio
async def test_identifies_missing_features(self):
agent = CodeFunctionAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.missing_features) >= 15
missing_lower = [f.lower() for f in result.missing_features]
assert any("monitor" in f for f in missing_lower)
assert any("sasl" in f for f in missing_lower)
@pytest.mark.asyncio
async def test_api_design_issues(self):
agent = CodeFunctionAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.api_design_issues) >= 3
issues_text = " ".join(result.api_design_issues).lower()
assert "nickserv" in issues_text or "auth" in issues_text
@pytest.mark.asyncio
async def test_data_flow_issues(self):
agent = CodeFunctionAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.data_flow_issues) >= 3
def test_to_dict(self):
result = CodeFunctionResult(
module_structure={"core": ["adapter.py"]},
implemented_features=["f1"],
missing_features=["f2"],
)
d = result.to_dict()
assert d["module_structure"] == {"core": ["adapter.py"]}
assert d["implemented_features"] == ["f1"]
class TestCodeQualityAgent:
@pytest.mark.asyncio
async def test_execute_returns_result(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert isinstance(result, CodeQualityResult)
assert len(result.issues) >= 5
@pytest.mark.asyncio
async def test_finds_critical_auth_issue(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
auth_issues = [i for i in result.issues if i.file == "auth.py"]
assert len(auth_issues) >= 1
critical_auth = [i for i in auth_issues if i.severity == "critical"]
assert len(critical_auth) >= 1
assert "***" in critical_auth[0].message or "掩码" in critical_auth[0].message
@pytest.mark.asyncio
async def test_finds_connection_issues(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
conn_issues = [i for i in result.issues if i.file == "connection.py"]
assert len(conn_issues) >= 2
@pytest.mark.asyncio
async def test_has_security_issues(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.security_issues) >= 3
@pytest.mark.asyncio
async def test_has_performance_issues(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.performance_issues) >= 3
@pytest.mark.asyncio
async def test_has_dead_code_report(self):
agent = CodeQualityAgent(IRC_MODULE_DIR)
result = await agent.execute()
assert len(result.dead_code) >= 1
def test_code_issue_dataclass(self):
issue = CodeIssue(
file="test.py",
line=42,
severity="critical",
category="安全",
message="bad thing",
suggestion="fix it",
)
assert issue.file == "test.py"
assert issue.line == 42
assert issue.severity == "critical"
def test_to_dict(self):
result = CodeQualityResult(
issues=[
CodeIssue(file="f.py", line=1, severity="high", category="bug", message="m")
],
dead_code=["dc"],
)
d = result.to_dict()
assert len(d["issues"]) == 1
assert d["dead_code"] == ["dc"]
class TestAnalysisOrchestrator:
@pytest.mark.asyncio
async def test_run_full_analysis_succeeds(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
result = await orchestrator.run_full_analysis()
assert isinstance(result, AnalysisResult)
assert result.success
assert result.web_research is not None
assert result.code_function is not None
assert result.code_quality is not None
@pytest.mark.asyncio
async def test_integrate_results_generates_report(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
report = orchestrator.integrate_results(analysis)
assert isinstance(report, GapReport)
assert report.generated_at
assert report.executive_summary
assert len(report.critical_issues) >= 1
assert len(report.missing_features) >= 5
assert len(report.recommendations) >= 5
@pytest.mark.asyncio
async def test_report_to_json(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
report = orchestrator.integrate_results(analysis)
json_str = report.to_json()
assert isinstance(json_str, str)
assert "critical_issues" in json_str
assert "executive_summary" in json_str
@pytest.mark.asyncio
async def test_generate_fix_plan(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
plan = orchestrator.generate_fix_plan(analysis)
assert isinstance(plan, FixPlan)
assert len(plan.items) >= 4
assert plan.total_estimated_effort
priorities = {item.priority for item in plan.items}
assert 1 in priorities
@pytest.mark.asyncio
async def test_fix_plan_items_have_required_fields(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
plan = orchestrator.generate_fix_plan(analysis)
for item in plan.items:
assert item.priority > 0
assert item.category
assert item.description
assert item.affected_files
assert item.effort
assert item.risk
@pytest.mark.asyncio
async def test_fix_plan_to_dict(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
plan = orchestrator.generate_fix_plan(analysis)
d = plan.to_dict()
assert "items" in d
assert "total_estimated_effort" in d
assert len(d["items"]) == len(plan.items)
@pytest.mark.asyncio
async def test_handles_agent_failure_gracefully(self):
"""测试单个子agent失败时不影响其他agent的结果"""
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
original_execute = orchestrator._web_agent.execute
async def failing_execute():
raise RuntimeError("simulated web failure")
orchestrator._web_agent.execute = failing_execute
try:
result = await orchestrator.run_full_analysis()
assert result.web_research is None
assert len(result.errors) >= 1
assert result.code_function is not None
assert result.code_quality is not None
finally:
orchestrator._web_agent.execute = original_execute
@pytest.mark.asyncio
async def test_recommendations_include_p0_fixes(self):
orchestrator = AnalysisOrchestrator(IRC_MODULE_DIR)
analysis = await orchestrator.run_full_analysis()
report = orchestrator.integrate_results(analysis)
p0_recs = [r for r in report.recommendations if r.get("priority") == "P0"]
assert len(p0_recs) >= 1
assert any("auth" in r.get("detail", "").lower() or "auth" in r.get("action", "").lower() for r in p0_recs)
class TestFixPlanItem:
def test_fix_plan_item_creation(self):
item = FixPlanItem(
priority=1,
category="安全",
description="修复密码掩码",
affected_files=["auth.py"],
effort="",
risk="",
)
assert item.priority == 1
assert item.category == "安全"
assert item.affected_files == ["auth.py"]
class TestAnalysisResult:
def test_default_result_no_errors(self):
result = AnalysisResult()
assert result.success
assert result.errors == []
def test_result_with_errors(self):
result = AnalysisResult(errors=["something failed"])
assert not result.success
assert len(result.errors) == 1