"""Integration tests for /api/scenarios versioning (ticket 03). 场景版本由系统维护(ADR-0001):仅考纲字段(cases / model_bindings / llm_config)变更时递增,元数据编辑不升版,API 不接受外部指定版本。 """ import pytest from cryptography.fernet import Fernet from httpx import ASGITransport, AsyncClient from agenteval.services.model_configs import ModelConfigService, SecretCipher from agenteval.web.app import app 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