"""演化候选审核 API 测试。""" from collections.abc import AsyncGenerator import pytest from httpx import ASGITransport, AsyncClient from app.db.session import AsyncSessionLocal from app.main import app from app.models.big_cycle import BigCycle from app.models.evolution import EvolutionCandidate from app.repositories.json_utils import dumps from app.services.characters import get_active_character_model @pytest.fixture async def client() -> AsyncGenerator[AsyncClient, None]: transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as http_client: yield http_client async def _seed_cycle_and_candidate(character_id: str, **overrides) -> tuple[str, str]: base = { "type": "procedural", "source": "big_cycle", "level": "L2", "scope": "task_scope", "title": "确认假设", "proposal": "技术问题先确认前提", "delta_json": dumps({"title": "确认假设", "content": "先确认前提"}), "evidence_ids_json": "[]", "confidence": 0.8, "benefit_score": 0.7, "risk_score": 0.2, "drift_score": 0.0, "auto_apply_allowed": False, "status": "pending", } base.update(overrides) async with AsyncSessionLocal() as session: cycle = BigCycle( character_id=character_id, trigger="test", status="completed", memory_count_at_trigger=20, ) session.add(cycle) await session.flush() candidate = EvolutionCandidate(character_id=character_id, cycle_id=cycle.id, **base) session.add(candidate) await session.commit() return candidate.id, cycle.id @pytest.mark.asyncio async def test_list_evolution_candidates(client: AsyncClient) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) await _seed_cycle_and_candidate(character.id) await _seed_cycle_and_candidate(character.id, status="applied") resp = await client.get(f"/api/v1/characters/{character.id}/evolution-candidates") assert resp.status_code == 200 data = resp.json() assert data["total"] == 2 pending = await client.get( f"/api/v1/characters/{character.id}/evolution-candidates", params={"status": "pending"} ) assert pending.json()["total"] == 1 @pytest.mark.asyncio async def test_approve_pending_candidate_applies(client: AsyncClient) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) cand_id, _ = await _seed_cycle_and_candidate(character.id, type="procedural") resp = await client.post(f"/api/v1/evolution-candidates/{cand_id}/approve") assert resp.status_code == 200 data = resp.json() assert data["status"] == "applied" assert data["resulting_target_id"] is not None @pytest.mark.asyncio async def test_reject_pending_candidate(client: AsyncClient) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) cand_id, _ = await _seed_cycle_and_candidate(character.id) resp = await client.post(f"/api/v1/evolution-candidates/{cand_id}/reject") assert resp.status_code == 200 assert resp.json()["status"] == "rejected" @pytest.mark.asyncio async def test_approve_not_implemented_type_returns_error(client: AsyncClient) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) cand_id, _ = await _seed_cycle_and_candidate(character.id, type="tool_strategy", level="L2") resp = await client.post(f"/api/v1/evolution-candidates/{cand_id}/approve") assert resp.status_code in (400, 409, 501) assert resp.json()["error"]["code"] == "not_implemented"