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