- ruff --fix 自动修正 44 项:移除未用 import(pytest 等)、import 块排序归一(I001) - 手工修复剩余 5 项:test_cascade.py 两处未用赋值(F841);test_s2_rules_and_logic.py 中部 import 移至文件顶部(E402 ×3) - 无行为变更:全量 492 项测试通过
128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
"""Integration tests for /api/scenarios versioning (ticket 03).
|
||
|
||
场景版本由系统维护(ADR-0001):仅考纲字段(cases / model_bindings /
|
||
llm_config)变更时递增,元数据编辑不升版,API 不接受外部指定版本。
|
||
"""
|
||
|
||
import pytest
|
||
from agenteval.services.model_configs import ModelConfigService, SecretCipher
|
||
from agenteval.web.app import app
|
||
from cryptography.fernet import Fernet
|
||
from httpx import ASGITransport, AsyncClient
|
||
|
||
pytestmark = pytest.mark.anyio
|
||
|
||
|
||
@pytest.fixture()
|
||
def scenario_client(db_session, monkeypatch):
|
||
from agenteval.web import app as app_module
|
||
|
||
monkeypatch.setattr(app_module, "init_db", lambda: None)
|
||
|
||
from agenteval.web.deps import get_db
|
||
|
||
def _test_get_db():
|
||
try:
|
||
yield db_session
|
||
finally:
|
||
pass
|
||
|
||
app.dependency_overrides[get_db] = _test_get_db
|
||
yield db_session
|
||
app.dependency_overrides.clear()
|
||
|
||
|
||
def _client() -> AsyncClient:
|
||
return AsyncClient(transport=ASGITransport(app=app), base_url="http://test")
|
||
|
||
|
||
_BODY = {
|
||
"name": "版本测试场景",
|
||
"description": "初始",
|
||
"tags": ["v"],
|
||
"cases": [{"id": "c1", "type": "single", "messages": ["hi"]}],
|
||
}
|
||
|
||
|
||
async def _create(client) -> dict:
|
||
resp = await client.post("/api/scenarios", json=_BODY)
|
||
assert resp.status_code == 200, resp.text
|
||
return resp.json()
|
||
|
||
|
||
async def test_create_scenario_version_starts_at_1(scenario_client):
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
assert created["version"] == 1
|
||
|
||
|
||
async def test_editing_cases_bumps_version(scenario_client):
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
body = {**_BODY, "cases": _BODY["cases"] + [{"id": "c2", "type": "single", "messages": ["yo"]}]}
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=body)
|
||
assert resp.status_code == 200
|
||
assert resp.json()["version"] == 2
|
||
|
||
|
||
async def test_editing_llm_config_bumps_version(scenario_client):
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
body = {**_BODY, "llm_config": {"model": "gpt-x"}}
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=body)
|
||
assert resp.json()["version"] == 2
|
||
|
||
|
||
async def test_editing_model_bindings_bumps_version(scenario_client, db_session):
|
||
service = ModelConfigService(db_session, SecretCipher(Fernet.generate_key().decode("ascii")))
|
||
config = service.create(
|
||
name="judge", provider="openai_compatible", capability="chat",
|
||
endpoint_url="https://m.example.com/v1/chat/completions",
|
||
model_name="m", api_key="k", enabled=True, is_default=False, description="",
|
||
)
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
body = {**_BODY, "model_bindings": {"judge": config.id}}
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=body)
|
||
assert resp.status_code == 200, resp.text
|
||
assert resp.json()["version"] == 2
|
||
|
||
|
||
async def test_metadata_edit_does_not_bump_version(scenario_client):
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
body = {**_BODY, "name": "改名了", "description": "新描述", "tags": ["x", "y"]}
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=body)
|
||
assert resp.status_code == 200
|
||
assert resp.json()["version"] == 1
|
||
assert resp.json()["name"] == "改名了"
|
||
|
||
|
||
async def test_resaving_identical_syllabus_does_not_bump_version(scenario_client):
|
||
"""原样重存(考纲逐字节相同)不升版——序列化形态回归护栏。"""
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=_BODY)
|
||
assert resp.status_code == 200
|
||
assert resp.json()["version"] == 1
|
||
|
||
|
||
async def test_external_version_is_ignored(scenario_client):
|
||
async with _client() as client:
|
||
resp = await client.post("/api/scenarios", json={**_BODY, "version": 99})
|
||
assert resp.json()["version"] == 1
|
||
|
||
created = resp.json()
|
||
body = {**_BODY, "version": 42} # 元数据未变、考纲未变 → 版本保持 1
|
||
resp = await client.put(f"/api/scenarios/{created['id']}", json=body)
|
||
assert resp.json()["version"] == 1
|
||
|
||
|
||
async def test_list_and_get_return_version(scenario_client):
|
||
async with _client() as client:
|
||
created = await _create(client)
|
||
listed = (await client.get("/api/scenarios")).json()
|
||
assert all("version" in s for s in listed)
|
||
got = (await client.get(f"/api/scenarios/{created['id']}")).json()
|
||
assert got["version"] == 1
|