AgentEvalTool/tests/unit/test_scenarios.py
sinohqb e0b69fa2b9 v0.4-t1t2: 测试覆盖率 62%→77% + UTC 时区根本修复
## T1: P0 测试补全(+67 个测试)
- test_utils_llm.py: extract_reply_text / extract_content_from_llm_response / parse_json_from_llm_text 各边界
- test_file_repository.py: 分类 CRUD / 树形结构 / 级联删除 / 文件创建/查询/删除/物理文件清理
- test_report.py: generate_report / generate_compare_report / render_markdown / render_json
- test_llm_score.py: OpenAI 格式 / Anthropic content-block 格式 / JSON 回退解析 / 异常降级

## T2: P1 测试补全(+28 个测试)
- test_scenarios.py: 模板列表/字段完整性/规则类型有效性 + YAML/JSON 加载/校验
- test_webhook.py: 未配置不发送 / 正确 payload / secret header / 异常静默忽略
- test_reports_api.py: GET /reports/{id} / /html / /json / /markdown / /compare 集成测试

## UTC 时区根本修复
- storage/db.py: 新增 iso_utc() 函数,确保所有 datetime 序列化输出带 Z 后缀
- runs.py / files.py / report.py: 6 处 .isoformat() → iso_utc()
- 前端 toDate() 兜底仍保留(向下兼容),但后端不再输出无时区时间戳

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 14:19:16 +08:00

160 lines
4.9 KiB
Python

"""Unit tests for scenarios loader and templates."""
import json
import tempfile
from pathlib import Path
import pytest
import yaml
from agenteval.scenarios.loader import load_scenario_file, validate_scenario, ScenarioValidationError
from agenteval.scenarios.templates import get_template, list_templates
# ── templates ─────────────────────────────────────────────────────────────
def test_list_templates_returns_list():
tpls = list_templates()
assert isinstance(tpls, list)
assert len(tpls) >= 6
def test_template_has_required_fields():
for tpl in list_templates():
assert "id" in tpl
assert "name" in tpl
assert "description" in tpl
assert "cases" in tpl
assert isinstance(tpl["cases"], list)
assert len(tpl["cases"]) > 0
def test_get_template_by_id():
tpl = get_template("tpl-single-qa")
assert tpl is not None
assert tpl["id"] == "tpl-single-qa"
def test_get_template_not_found():
assert get_template("no-such-template") is None
def test_all_templates_have_valid_case_ids():
for tpl in list_templates():
for case in tpl["cases"]:
assert "id" in case
assert "type" in case
def test_template_eval_rules_reference_valid_types():
valid_types = {"keyword_match", "response_time", "llm_score", "semantic_similarity", "json_schema", "safety"}
for tpl in list_templates():
for case in tpl["cases"]:
for rule in case.get("eval_rules", []):
assert rule["type"] in valid_types, f"Unknown rule type {rule['type']} in template {tpl['id']}"
# ── scenario loader ───────────────────────────────────────────────────────
def _write_tmp(content: str, suffix: str) -> Path:
f = tempfile.NamedTemporaryFile(mode="w", suffix=suffix, delete=False, encoding="utf-8")
f.write(content)
f.flush()
return Path(f.name)
def test_load_json_scenario():
data = {
"name": "JSON 场景",
"cases": [{"id": "c1", "type": "single", "messages": ["你好"]}],
}
path = _write_tmp(json.dumps(data), ".json")
scenario = load_scenario_file(path)
assert scenario.name == "JSON 场景"
assert len(scenario.cases) == 1
path.unlink()
def test_load_yaml_scenario():
data = {
"name": "YAML 场景",
"cases": [{"id": "c1", "type": "single", "messages": ["Hello"]}],
}
path = _write_tmp(yaml.dump(data, allow_unicode=True), ".yaml")
scenario = load_scenario_file(path)
assert scenario.name == "YAML 场景"
path.unlink()
def test_load_yml_extension():
data = {
"name": "YML 场景",
"cases": [{"id": "c1", "type": "single", "messages": ["test"]}],
}
path = _write_tmp(yaml.dump(data, allow_unicode=True), ".yml")
scenario = load_scenario_file(path)
assert scenario.name == "YML 场景"
path.unlink()
def test_load_scenario_with_tags():
data = {
"name": "带标签",
"tags": ["health", "basic"],
"cases": [{"id": "c1", "type": "single", "messages": ["hi"]}],
}
path = _write_tmp(json.dumps(data), ".json")
scenario = load_scenario_file(path)
assert "health" in scenario.tags
path.unlink()
def test_load_scenario_with_eval_rules():
data = {
"name": "含规则",
"cases": [{
"id": "c1", "type": "single", "messages": ["hi"],
"eval_rules": [{"type": "response_time", "params": {"max_ms": 5000}}],
}],
}
path = _write_tmp(json.dumps(data), ".json")
scenario = load_scenario_file(path)
assert len(scenario.cases[0].eval_rules) == 1
assert scenario.cases[0].eval_rules[0].type == "response_time"
path.unlink()
def test_load_scenario_file_not_found():
with pytest.raises(ScenarioValidationError, match="not found"):
load_scenario_file(Path("/tmp/nonexistent-scenario-xyz.json"))
def test_load_unsupported_extension():
path = Path(_write_tmp("{}", ".txt"))
with pytest.raises(ScenarioValidationError, match="unsupported"):
load_scenario_file(path)
path.unlink()
def test_validate_scenario_valid():
data = {
"name": "有效场景",
"cases": [{"id": "c1", "type": "single", "messages": ["hi"]}],
}
valid, errors = validate_scenario(data)
assert valid is True
assert errors == []
def test_validate_scenario_missing_name():
data = {"cases": [{"id": "c1", "type": "single", "messages": ["hi"]}]}
valid, errors = validate_scenario(data)
assert valid is False
assert len(errors) > 0
def test_validate_scenario_empty_cases():
data = {"name": "空用例", "cases": []} # empty list triggers the cases_not_empty validator
valid, errors = validate_scenario(data)
assert valid is False