- ruff --fix 自动修正 44 项:移除未用 import(pytest 等)、import 块排序归一(I001) - 手工修复剩余 5 项:test_cascade.py 两处未用赋值(F841);test_s2_rules_and_logic.py 中部 import 移至文件顶部(E402 ×3) - 无行为变更:全量 492 项测试通过
158 lines
4.9 KiB
Python
158 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 ScenarioValidationError, load_scenario_file, validate_scenario
|
|
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
|