新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
601 lines
22 KiB
Python
601 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.adapters.qqbot.analysis.integrator import Integrator
|
|
from yuxi.channels.adapters.qqbot.analysis.models import (
|
|
AgentReport,
|
|
AgentRole,
|
|
Finding,
|
|
FindingCategory,
|
|
FindingSeverity,
|
|
FixProposal,
|
|
GapAnalysisReport,
|
|
ResearchSource,
|
|
)
|
|
from yuxi.channels.adapters.qqbot.analysis.orchestrator import AnalysisOrchestrator
|
|
from yuxi.channels.adapters.qqbot.analysis.researcher_func import FunctionalResearcher
|
|
from yuxi.channels.adapters.qqbot.analysis.researcher_quality import QualityResearcher
|
|
from yuxi.channels.adapters.qqbot.analysis.researcher_web import WebResearcher
|
|
|
|
|
|
def _make_finding(
|
|
fid: str,
|
|
category: FindingCategory,
|
|
severity: FindingSeverity = FindingSeverity.MEDIUM,
|
|
) -> Finding:
|
|
return Finding(
|
|
id=fid,
|
|
category=category,
|
|
severity=severity,
|
|
title=f"Test finding {fid}",
|
|
description=f"Description for {fid}",
|
|
location="adapter.py:test",
|
|
suggestion="Fix it",
|
|
)
|
|
|
|
|
|
# ==================== Web Researcher Tests ====================
|
|
|
|
|
|
class TestWebResearcher:
|
|
@pytest.mark.asyncio
|
|
async def test_research_returns_report(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
assert isinstance(report, AgentReport)
|
|
assert report.agent_role == AgentRole.WEB_RESEARCHER
|
|
assert len(report.findings) > 0
|
|
assert report.execution_time_ms > 0
|
|
assert report.sources_collected > 0
|
|
assert report.summary
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_findings_include_all_categories(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
categories = {f.category for f in report.findings}
|
|
assert FindingCategory.MISSING_FEATURE in categories
|
|
assert FindingCategory.STANDARD_GAP in categories
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_missing_message_types_detected(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
web001 = next((f for f in report.findings if f.id == "WEB-001"), None)
|
|
assert web001 is not None
|
|
assert web001.severity == FindingSeverity.HIGH
|
|
assert "Markdown" in web001.title
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_missing_events_detected(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
web005 = next((f for f in report.findings if f.id == "WEB-005"), None)
|
|
assert web005 is not None
|
|
assert "成员事件" in web005.title or "JOIN" in web005.description
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_streaming_gaps_detected(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
web008 = next((f for f in report.findings if f.id == "WEB-008"), None)
|
|
assert web008 is not None
|
|
assert "流式" in web008.title
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_platform_standard_gaps_detected(self):
|
|
researcher = WebResearcher()
|
|
report = await researcher.research()
|
|
|
|
web013 = next((f for f in report.findings if f.id == "WEB-013"), None)
|
|
assert web013 is not None
|
|
assert "Session" in web013.title or "Resume" in web013.description
|
|
|
|
|
|
# ==================== Functional Researcher Tests ====================
|
|
|
|
|
|
class TestFunctionalResearcher:
|
|
@pytest.mark.asyncio
|
|
async def test_research_returns_report(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
assert isinstance(report, AgentReport)
|
|
assert report.agent_role == AgentRole.FUNCTIONAL_RESEARCHER
|
|
assert len(report.findings) > 0
|
|
assert report.execution_time_ms > 0
|
|
assert report.summary
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unimplemented_interfaces_detected(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
interface_findings = [f for f in report.findings if f.category == FindingCategory.MISSING_INTERFACE]
|
|
assert len(interface_findings) >= 3
|
|
|
|
titles = {f.title for f in interface_findings}
|
|
assert any("编辑" in t or "edit" in t.lower() for t in titles)
|
|
assert any("删除" in t or "delete" in t.lower() for t in titles)
|
|
assert any("Reaction" in t or "表情" in t for t in titles)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_partial_implementations_detected(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
partial = [f for f in report.findings if "不完整" in f.title]
|
|
assert len(partial) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_data_flow_issues_detected(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
func007 = next((f for f in report.findings if f.id == "FUNC-007"), None)
|
|
assert func007 is not None
|
|
assert "队列" in func007.title or "queue" in func007.title.lower()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cross_module_issues_detected(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
func010 = next((f for f in report.findings if f.id == "FUNC-010"), None)
|
|
assert func010 is not None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_functional_gaps_detected(self):
|
|
researcher = FunctionalResearcher()
|
|
report = await researcher.research()
|
|
|
|
func012 = next((f for f in report.findings if f.id == "FUNC-012"), None)
|
|
assert func012 is not None
|
|
assert "去重" in func012.title or "dedup" in func012.title.lower() or "重复" in func012.title
|
|
|
|
|
|
# ==================== Quality Researcher Tests ====================
|
|
|
|
|
|
class TestQualityResearcher:
|
|
@pytest.mark.asyncio
|
|
async def test_research_returns_report(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
assert isinstance(report, AgentReport)
|
|
assert report.agent_role == AgentRole.QUALITY_RESEARCHER
|
|
assert len(report.findings) > 0
|
|
assert report.execution_time_ms > 0
|
|
assert report.summary
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_error_handling_issues_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
error_findings = [f for f in report.findings if f.category == FindingCategory.ERROR_HANDLING]
|
|
assert len(error_findings) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wait_for_hello_issue_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
qual001 = next((f for f in report.findings if f.id == "QUAL-001"), None)
|
|
assert qual001 is not None
|
|
assert "HELLO" in qual001.title.upper()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_heartbeat_issue_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
qual002 = next((f for f in report.findings if f.id == "QUAL-002"), None)
|
|
assert qual002 is not None
|
|
assert "心跳" in qual002.title or "heartbeat" in qual002.title.lower()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_security_issues_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
security_findings = [f for f in report.findings if f.category == FindingCategory.SECURITY]
|
|
assert len(security_findings) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ed25519_validation_issue_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
qual011 = next((f for f in report.findings if f.id == "QUAL-011"), None)
|
|
assert qual011 is not None
|
|
assert "ed25519" in qual011.title.lower() or "签名" in qual011.title or "密钥" in qual011.title
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_code_quality_issues_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
quality_findings = [f for f in report.findings if f.category == FindingCategory.CODE_QUALITY]
|
|
assert len(quality_findings) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_logic_bugs_detected(self):
|
|
researcher = QualityResearcher()
|
|
report = await researcher.research()
|
|
|
|
logic_findings = [f for f in report.findings if f.category == FindingCategory.LOGIC_BUG]
|
|
assert len(logic_findings) > 0
|
|
|
|
|
|
# ==================== Integrator Tests ====================
|
|
|
|
|
|
class TestIntegrator:
|
|
@pytest.mark.asyncio
|
|
async def test_integrate_empty_reports(self):
|
|
integrator = Integrator()
|
|
gap_report = await integrator.integrate([])
|
|
|
|
assert isinstance(gap_report, GapAnalysisReport)
|
|
assert gap_report.module_name == "qqbot"
|
|
assert gap_report.total_findings == 0
|
|
assert gap_report.critical_count == 0
|
|
assert gap_report.summary
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_integrate_with_findings(self):
|
|
web_report = AgentReport(
|
|
agent_role=AgentRole.WEB_RESEARCHER,
|
|
findings=[
|
|
_make_finding("W-001", FindingCategory.MISSING_FEATURE, FindingSeverity.HIGH),
|
|
_make_finding("W-002", FindingCategory.STANDARD_GAP),
|
|
],
|
|
summary="Web findings",
|
|
)
|
|
func_report = AgentReport(
|
|
agent_role=AgentRole.FUNCTIONAL_RESEARCHER,
|
|
findings=[
|
|
_make_finding("F-001", FindingCategory.MISSING_INTERFACE, FindingSeverity.CRITICAL),
|
|
_make_finding("F-002", FindingCategory.PERFORMANCE, FindingSeverity.LOW),
|
|
],
|
|
summary="Func findings",
|
|
)
|
|
quality_report = AgentReport(
|
|
agent_role=AgentRole.QUALITY_RESEARCHER,
|
|
findings=[
|
|
_make_finding("Q-001", FindingCategory.SECURITY, FindingSeverity.CRITICAL),
|
|
_make_finding("Q-002", FindingCategory.ERROR_HANDLING, FindingSeverity.HIGH),
|
|
_make_finding("Q-003", FindingCategory.CODE_QUALITY, FindingSeverity.LOW),
|
|
],
|
|
summary="Quality findings",
|
|
)
|
|
|
|
integrator = Integrator()
|
|
gap_report = await integrator.integrate([web_report, func_report, quality_report])
|
|
|
|
assert gap_report.total_findings == 7
|
|
assert gap_report.critical_count == 2
|
|
assert len(gap_report.missing_features) == 1
|
|
assert len(gap_report.missing_interfaces) == 1
|
|
assert len(gap_report.standard_gaps) == 1
|
|
assert len(gap_report.security_issues) == 1
|
|
assert len(gap_report.error_handling_issues) == 1
|
|
assert len(gap_report.code_quality_issues) == 1
|
|
assert len(gap_report.performance_bottlenecks) == 1
|
|
assert len(gap_report.sub_agent_reports) == 3
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_integrate_category_routing(self):
|
|
all_categories = [
|
|
(FindingCategory.MISSING_FEATURE, "missing_features"),
|
|
(FindingCategory.MISSING_INTERFACE, "missing_interfaces"),
|
|
(FindingCategory.STANDARD_GAP, "standard_gaps"),
|
|
(FindingCategory.PERFORMANCE, "performance_bottlenecks"),
|
|
(FindingCategory.ERROR_HANDLING, "error_handling_issues"),
|
|
(FindingCategory.CODE_QUALITY, "code_quality_issues"),
|
|
(FindingCategory.SECURITY, "security_issues"),
|
|
(FindingCategory.LOGIC_BUG, "logic_bugs"),
|
|
]
|
|
|
|
for category, field_name in all_categories:
|
|
report = AgentReport(
|
|
agent_role=AgentRole.QUALITY_RESEARCHER,
|
|
findings=[_make_finding("T-001", category)],
|
|
)
|
|
integrator = Integrator()
|
|
gap_report = await integrator.integrate([report])
|
|
assert len(getattr(gap_report, field_name)) == 1, f"Category {category} should route to {field_name}"
|
|
|
|
|
|
# ==================== Orchestrator Tests ====================
|
|
|
|
|
|
class TestOrchestrator:
|
|
@pytest.mark.asyncio
|
|
async def test_run_diagnostics(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
gap_report = await orchestrator.run_diagnostics()
|
|
|
|
assert isinstance(gap_report, GapAnalysisReport)
|
|
assert gap_report.module_name == "qqbot"
|
|
assert gap_report.total_findings > 0
|
|
assert gap_report.summary
|
|
assert len(gap_report.sub_agent_reports) == 3
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_diagnostics_has_all_sub_reports(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
gap_report = await orchestrator.run_diagnostics()
|
|
|
|
roles = {r.agent_role for r in gap_report.sub_agent_reports}
|
|
assert AgentRole.WEB_RESEARCHER in roles
|
|
assert AgentRole.FUNCTIONAL_RESEARCHER in roles
|
|
assert AgentRole.QUALITY_RESEARCHER in roles
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_full_analysis_no_apply(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
gap_report, fix_report = await orchestrator.run_full_analysis(apply_fixes=False)
|
|
|
|
assert isinstance(gap_report, GapAnalysisReport)
|
|
assert len(fix_report.proposals) > 0
|
|
assert fix_report.applied_count == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_run_auto_fix_generates_proposals(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
fix_report = await orchestrator.run_auto_fix()
|
|
|
|
assert len(fix_report.proposals) > 0
|
|
assert isinstance(fix_report.summary, str)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_gap_report_has_all_category_lists(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
gap_report = await orchestrator.run_diagnostics()
|
|
|
|
assert isinstance(gap_report.missing_features, list)
|
|
assert isinstance(gap_report.missing_interfaces, list)
|
|
assert isinstance(gap_report.standard_gaps, list)
|
|
assert isinstance(gap_report.performance_bottlenecks, list)
|
|
assert isinstance(gap_report.error_handling_issues, list)
|
|
assert isinstance(gap_report.code_quality_issues, list)
|
|
assert isinstance(gap_report.security_issues, list)
|
|
assert isinstance(gap_report.logic_bugs, list)
|
|
|
|
|
|
# ==================== Fixer Tests ====================
|
|
|
|
|
|
class TestFixer:
|
|
@pytest.fixture
|
|
def sample_report(self):
|
|
return GapAnalysisReport(
|
|
module_name="qqbot",
|
|
all_findings=[
|
|
_make_finding("QUAL-001", FindingCategory.ERROR_HANDLING, FindingSeverity.HIGH),
|
|
_make_finding("QUAL-011", FindingCategory.SECURITY, FindingSeverity.HIGH),
|
|
_make_finding("QUAL-007", FindingCategory.CODE_QUALITY, FindingSeverity.MEDIUM),
|
|
],
|
|
error_handling_issues=[
|
|
_make_finding("QUAL-001", FindingCategory.ERROR_HANDLING, FindingSeverity.HIGH),
|
|
],
|
|
security_issues=[
|
|
_make_finding("QUAL-011", FindingCategory.SECURITY, FindingSeverity.HIGH),
|
|
],
|
|
code_quality_issues=[
|
|
_make_finding("QUAL-007", FindingCategory.CODE_QUALITY, FindingSeverity.MEDIUM),
|
|
],
|
|
logic_bugs=[],
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_proposals(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
proposals = await fixer.generate_proposals(sample_report)
|
|
|
|
assert len(proposals) > 0
|
|
assert all(isinstance(p, FixProposal) for p in proposals)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_proposals_have_ids(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
proposals = await fixer.generate_proposals(sample_report)
|
|
|
|
ids = {p.id for p in proposals}
|
|
assert len(ids) == len(proposals)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_proposals_include_security_fix(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
proposals = await fixer.generate_proposals(sample_report)
|
|
|
|
security_patches = [p for p in proposals if p.fix_type == "security_patch"]
|
|
assert len(security_patches) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_proposals_include_bug_fix(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
proposals = await fixer.generate_proposals(sample_report)
|
|
|
|
bug_fixes = [p for p in proposals if p.fix_type == "fix_bug"]
|
|
assert len(bug_fixes) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_proposals_include_refactor(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
proposals = await fixer.generate_proposals(sample_report)
|
|
|
|
refactors = [p for p in proposals if p.fix_type == "refactor"]
|
|
assert len(refactors) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_apply_with_empty_proposals(self, sample_report):
|
|
from yuxi.channels.adapters.qqbot.analysis.fixer import Fixer
|
|
|
|
fixer = Fixer()
|
|
fix_report = await fixer.apply_proposals([])
|
|
|
|
assert fix_report.applied_count == 0
|
|
assert fix_report.skipped_count == 0
|
|
|
|
|
|
# ==================== Models Tests ====================
|
|
|
|
|
|
class TestModels:
|
|
def test_finding_creation(self):
|
|
finding = Finding(
|
|
id="TEST-001",
|
|
category=FindingCategory.SECURITY,
|
|
severity=FindingSeverity.CRITICAL,
|
|
title="Test finding",
|
|
description="Test",
|
|
location="adapter.py:100",
|
|
suggestion="Fix it",
|
|
sources=[
|
|
ResearchSource(
|
|
url="https://example.com",
|
|
title="Source",
|
|
relevance=0.95,
|
|
)
|
|
],
|
|
)
|
|
assert finding.id == "TEST-001"
|
|
assert finding.severity == FindingSeverity.CRITICAL
|
|
|
|
def test_gap_report_properties(self):
|
|
report = GapAnalysisReport(
|
|
all_findings=[
|
|
_make_finding("A", FindingCategory.SECURITY, FindingSeverity.CRITICAL),
|
|
_make_finding("B", FindingCategory.ERROR_HANDLING, FindingSeverity.HIGH),
|
|
_make_finding("C", FindingCategory.CODE_QUALITY),
|
|
],
|
|
)
|
|
assert report.total_findings == 3
|
|
assert report.critical_count == 1
|
|
|
|
def test_fix_proposal_creation(self):
|
|
proposal = FixProposal(
|
|
id="FIX-001",
|
|
finding_id="QUAL-001",
|
|
title="Fix _wait_for_hello",
|
|
description="Fix the HELLO handling",
|
|
fix_type="fix_bug",
|
|
estimated_effort="low",
|
|
affected_files=["adapter.py"],
|
|
test_suggestions=["Test HELLO after non-HELLO message"],
|
|
)
|
|
assert proposal.fix_type == "fix_bug"
|
|
assert not proposal.applied
|
|
|
|
def test_agent_report_summary(self):
|
|
report = AgentReport(
|
|
agent_role=AgentRole.WEB_RESEARCHER,
|
|
findings=[
|
|
_make_finding("W1", FindingCategory.MISSING_FEATURE),
|
|
_make_finding("W2", FindingCategory.STANDARD_GAP),
|
|
],
|
|
summary="Web research completed",
|
|
execution_time_ms=150.0,
|
|
sources_collected=10,
|
|
)
|
|
assert report.agent_role == AgentRole.WEB_RESEARCHER
|
|
assert len(report.findings) == 2
|
|
assert report.execution_time_ms == 150.0
|
|
|
|
def test_finding_severity_ordering(self):
|
|
severities = [
|
|
FindingSeverity.CRITICAL,
|
|
FindingSeverity.HIGH,
|
|
FindingSeverity.MEDIUM,
|
|
FindingSeverity.LOW,
|
|
FindingSeverity.INFO,
|
|
]
|
|
assert len(severities) == 5
|
|
assert len(set(severities)) == 5
|
|
|
|
|
|
# ==================== Integration: Full Flow Tests ====================
|
|
|
|
|
|
class TestFullAnalysisFlow:
|
|
@pytest.mark.asyncio
|
|
async def test_all_three_researchers_produce_findings(self):
|
|
web = WebResearcher()
|
|
func = FunctionalResearcher()
|
|
quality = QualityResearcher()
|
|
|
|
web_report = await web.research()
|
|
func_report = await func.research()
|
|
quality_report = await quality.research()
|
|
|
|
assert len(web_report.findings) > 0
|
|
assert len(func_report.findings) > 0
|
|
assert len(quality_report.findings) > 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_integration_produces_comprehensive_report(self):
|
|
web = WebResearcher()
|
|
func = FunctionalResearcher()
|
|
quality = QualityResearcher()
|
|
|
|
web_report = await web.research()
|
|
func_report = await func.research()
|
|
quality_report = await quality.research()
|
|
|
|
integrator = Integrator()
|
|
gap_report = await integrator.integrate([web_report, func_report, quality_report])
|
|
|
|
assert gap_report.total_findings > 0
|
|
assert gap_report.summary
|
|
|
|
total_by_category = (
|
|
len(gap_report.missing_features)
|
|
+ len(gap_report.missing_interfaces)
|
|
+ len(gap_report.standard_gaps)
|
|
+ len(gap_report.performance_bottlenecks)
|
|
+ len(gap_report.error_handling_issues)
|
|
+ len(gap_report.code_quality_issues)
|
|
+ len(gap_report.security_issues)
|
|
+ len(gap_report.logic_bugs)
|
|
)
|
|
assert total_by_category == gap_report.total_findings
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_diagnostics_report_contains_all_sections(self):
|
|
orchestrator = AnalysisOrchestrator()
|
|
gap_report = await orchestrator.run_diagnostics()
|
|
|
|
non_empty_sections = [
|
|
name for name in [
|
|
"missing_features",
|
|
"missing_interfaces",
|
|
"standard_gaps",
|
|
"error_handling_issues",
|
|
"code_quality_issues",
|
|
"security_issues",
|
|
"logic_bugs",
|
|
]
|
|
if len(getattr(gap_report, name)) > 0
|
|
]
|
|
assert len(non_empty_sections) >= 4 |