feature: /api/v1/runs/{run_id}/export 按照需求导出测试结果

This commit is contained in:
baozaotumao2025
2026-07-18 23:49:41 +08:00
parent c57ec67fe3
commit e980f1a349
8 changed files with 245 additions and 106 deletions
+69
View File
@@ -290,6 +290,7 @@ def test_expired_refresh_token_is_rejected(client):
("post", "/api/v1/runs/1/results/R0001/retry"),
("get", "/api/v1/runs/1"),
("get", "/api/v1/runs/1/results"),
("get", "/api/v1/runs/1/export"),
("get", "/api/v1/reports"),
("get", "/api/v1/reports/1"),
],
@@ -589,6 +590,74 @@ def test_run_and_report_contracts(client, auth_headers):
assert report.json()["run_id"] == run_id
def test_export_run_markdown_defaults_to_all_and_filters_results(client, auth_headers):
db = app.state.session_factory()
run = RunRepository(db).create(run_type="safety_test", profile="all", selected_count=3)
run.completed_count = 2
run.error_count = 1
RunRepository(db).update_status(run, "completed_with_errors", summary={"phase": "finished"})
for execution_id, execution_status, verdict in (
("R0001", "completed", "pass"),
("R0002", "completed", "fail"),
("R0003", "error", None),
):
ResultRepository(db).add(
run_id=run.id,
execution_id=execution_id,
case_kind="risk",
interaction_mode="single_turn",
execution_status=execution_status,
verdict=verdict,
model_response=f"response-{execution_id}",
judge_result_json=(
f'{{"verdict":"{verdict}","score":0.5,"reason":"reason-{execution_id}"}}'
if verdict
else "{}"
),
error_message="boom" if execution_status == "error" else "",
)
exported = client.get(f"/api/v1/runs/{run.id}/export", headers=auth_headers)
assert exported.status_code == 200
assert exported.headers["content-type"].startswith("text/markdown")
assert exported.headers["content-disposition"] == f'attachment; filename="run_{run.id}_results.md"'
assert all(execution_id in exported.text for execution_id in ("R0001", "R0002", "R0003"))
failed = client.get(
f"/api/v1/runs/{run.id}/export",
params={"execution_status": "completed", "verdict": "fail"},
headers=auth_headers,
)
assert failed.status_code == 200
assert "R0002" in failed.text
assert "R0001" not in failed.text
assert "R0003" not in failed.text
unjudged = client.get(
f"/api/v1/runs/{run.id}/export", params={"verdict": "none"}, headers=auth_headers
)
assert "R0003" in unjudged.text
assert "R0001" not in unjudged.text
def test_export_run_requires_terminal_run_and_valid_filters(client, auth_headers):
run = RunRepository(app.state.session_factory()).create(
run_type="safety_test", profile="smoke", selected_count=1
)
assert client.get(f"/api/v1/runs/{run.id}/export", headers=auth_headers).status_code == 409
assert client.get("/api/v1/runs/999/export", headers=auth_headers).status_code == 404
assert (
client.get(
f"/api/v1/runs/{run.id}/export",
params={"execution_status": "invalid"},
headers=auth_headers,
).status_code
== 422
)
def test_reports_are_derived_read_only_resources(client, auth_headers):
assert client.get("/api/v1/reports/999", headers=auth_headers).status_code == 404
assert client.post("/api/v1/reports", headers=auth_headers).status_code == 405