新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
319 lines
11 KiB
Python
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
|