"""Unit tests for the pure renderers: dict in, HTML/Markdown/JSON out (no DB).""" import json from agenteval.evaluation.report_render import ( render_campaign_markdown, render_html, render_json, render_markdown, ) def _run_report(**overrides) -> dict: """A hand-built report dict matching generate_report's shape.""" report = { "run_id": "run-1", "target_id": "t-1", "target_name": "客服机器人", "scenario_id": "s-1", "scenario_name": "售后场景", "scenario_version": 2, "triggered_by": "manual", "status": "completed", "started_at": "2026-07-30T10:00:00+00:00", "completed_at": "2026-07-30T10:05:00+00:00", "summary": { "total_cases": 2, "passed_cases": 1, "failed_cases": 1, "total_rules": 3, "passed_rules": 2, "pass_rate": 0.5, "connectivity_cases": 1, "judged_pass_rate": 0.0, }, "cases": [ { "case_id": "case-a", "passed": True, "connectivity": True, "turns": [ { "round": 0, "sent_text": "你好", "reply_text": "您好,请问有什么可以帮您?", "latency_ms": 120, "question_msg_id": "m-1", } ], "results": [], }, { "case_id": "case-b", "passed": False, "connectivity": False, "turns": [ { "round": 0, "sent_text": "退货流程", "reply_text": None, "latency_ms": None, "question_msg_id": "m-2", } ], "results": [ {"rule_type": "keyword_match", "passed": False, "score": 0.0, "reason": "缺少关键词"}, ], }, ], } report.update(overrides) return report def _campaign_report() -> dict: """A hand-built dict matching generate_campaign_report's shape.""" return { "campaign_id": "c-1", "name": "夜间巡检", "target_id": "t-1", "status": "completed", "window_seconds": 7200, "time_scale": 1.0, "started_at": "2026-07-30T00:00:00+00:00", "completed_at": "2026-07-30T02:00:00+00:00", "summary": { "total_runs": 2, "completed_runs": 2, "overall_pass_rate": 0.75, "overall_availability": 1.0, "avg_latency_ms": 150.0, }, "time_trend": [ { "bucket_index": 0, "start_seconds": 0.0, "end_seconds": 3600.0, "run_count": 2, "pass_rate": 0.75, "availability": 1.0, "avg_latency_ms": 150.0, }, { "bucket_index": 1, "start_seconds": 3600.0, "end_seconds": 7200.0, "run_count": 0, "pass_rate": None, "availability": None, "avg_latency_ms": None, }, ], "capability_summary": [ { "scenario_id": "s-1", "scenario_name": "售后场景", "run_count": 2, "pass_rate": 0.75, "availability": 1.0, "avg_latency_ms": 150.0, }, ], } # ── render_html ───────────────────────────────────────────────────────────── def test_render_html_contains_names_and_summary(): html = render_html(_run_report()) assert "客服机器人" in html assert "售后场景" in html assert "50.00%" in html # pass_rate 0.5 def test_render_html_contains_turns_and_rule_badges(): html = render_html(_run_report()) assert "退货流程" in html assert "keyword_match" in html assert "失败" in html # ── render_markdown ───────────────────────────────────────────────────────── def test_render_markdown_summary_table(): md = render_markdown(_run_report()) assert "| 总用例数 | 2 |" in md assert "| 通过率 | 50.0% |" in md assert "| 连通用例 | 1 |" in md def test_render_markdown_connectivity_badge_and_no_reply(): md = render_markdown(_run_report()) assert "🔗" in md # connectivity case badge assert "(连通用例,未配置判定标准)" in md assert "(无回复)" in md def test_render_markdown_judged_pass_rate_dash_when_none(): report = _run_report() report["summary"]["judged_pass_rate"] = None md = render_markdown(report) assert "| 判定型通过率 | — |" in md # ── render_json ───────────────────────────────────────────────────────────── def test_render_json_roundtrips(): report = _run_report() parsed = json.loads(render_json(report)) assert parsed == report # ── render_campaign_markdown ──────────────────────────────────────────────── def test_render_campaign_markdown_summary_and_axes(): md = render_campaign_markdown(_campaign_report()) assert "# 活动周期报告 — 夜间巡检" in md assert "| 整窗通过率 | 75.0% |" in md assert "## 时间趋势" in md assert "## 能力汇总" in md assert "| 售后场景 | 2 | 75.0% | 100.0% | 150ms |" in md def test_render_campaign_markdown_empty_bucket_dashes(): md = render_campaign_markdown(_campaign_report()) # bucket 1 has no runs: pass_rate/availability/latency all render as — assert "| 3600–7200 | 0 | — | — | — |" in md def _analysis() -> dict: """A hand-built dict matching the stored campaign analysis result shape.""" return { "overall": "整窗通过率偏低,售后场景拖后腿", "problems": [ { "severity": "high", "title": "售后答非所问", "description": "多轮对话中反复偏离用户问题", "scenario_ids": ["s-1"], "evidence_run_ids": ["run-abc", "run-def"], }, { "severity": "low", "title": "响应偏慢", "description": "高峰时段时延偏高", "scenario_ids": ["s-2"], "evidence_run_ids": [], }, ], "scenario_narratives": [ {"scenario_id": "s-1", "narrative": "售后场景表现不稳定"}, {"scenario_id": "s-2", "narrative": "售前场景表现稳定"}, ], "suggestions": [ {"priority": 2, "text": "次要建议:扩容"}, {"priority": 1, "text": "首要建议:补充售后知识库"}, ], } def test_render_campaign_markdown_appends_analysis_sections(): md = render_campaign_markdown( _campaign_report(), analysis=_analysis(), scenario_names={"s-1": "售后场景", "s-2": "售前场景"}, ) assert "## 智能分析" in md assert "### 总体结论" in md assert "整窗通过率偏低,售后场景拖后腿" in md assert "### 问题诊断" in md assert "**[高] 售后答非所问**(场景:售后场景)" in md assert "`run-abc`" in md and "`run-def`" in md assert "**[低] 响应偏慢**(场景:售前场景)" in md assert "### 分场景叙述" in md assert "**售后场景**:售后场景表现不稳定" in md assert "### 改善建议" in md # 建议按 priority 升序 assert md.index("首要建议") < md.index("次要建议") def test_render_campaign_markdown_analysis_falls_back_to_id_prefix(): md = render_campaign_markdown(_campaign_report(), analysis=_analysis(), scenario_names={}) assert "**[高] 售后答非所问**(场景:s-1)" in md def test_render_campaign_markdown_without_analysis_unchanged(): md = render_campaign_markdown(_campaign_report()) assert "智能分析" not in md