"""演化候选派生 applier 测试。""" import pytest from app.db.session import AsyncSessionLocal from app.models.big_cycle import BigCycle from app.models.evolution import EvolutionCandidate from app.models.memory import Memory from app.repositories import evolution as evolution_repo from app.repositories import personality as personality_repo from app.repositories.json_utils import dumps from app.services import evolution as evolution_service from app.services.characters import get_active_character_model from app.services.personality import create_default_personality async def _setup(session) -> tuple: character = await get_active_character_model(session) if await personality_repo.get_latest_personality(session, character.id) is None: await create_default_personality(session, character.id) await session.flush() cycle = BigCycle( character_id=character.id, trigger="test", status="completed", memory_count_at_trigger=20, ) session.add(cycle) await session.flush() return character, cycle def _candidate(character_id: str, cycle_id: str, **overrides) -> EvolutionCandidate: base = { "character_id": character_id, "cycle_id": cycle_id, "type": "personality", "source": "big_cycle", "level": "L4", "scope": "character_scope", "title": "t", "proposal": "p", "delta_json": "{}", "evidence_ids_json": "[]", "confidence": 0.5, "benefit_score": 0.5, "risk_score": 0.5, "drift_score": 0.0, "auto_apply_allowed": False, "status": "pending", } base.update(overrides) return EvolutionCandidate(**base) @pytest.mark.asyncio async def test_apply_personality_candidate_derives_version() -> None: async with AsyncSessionLocal() as session: character, cycle = await _setup(session) base_version = await personality_repo.get_latest_personality(session, character.id) candidate = _candidate( character.id, cycle.id, type="personality", delta_json=dumps({"expression_style": "更简洁、更主动"}), title="表达风格演化", ) await evolution_repo.add_candidates(session, [candidate]) await session.flush() target_id = await evolution_service.apply_evolution_candidate(session, candidate) await session.commit() assert target_id is not None async with AsyncSessionLocal() as session: new_version = await personality_repo.get_personality(session, target_id) character2 = await get_active_character_model(session) assert new_version is not None assert new_version.version == base_version.version + 1 assert new_version.created_by == "big_cycle" assert "更简洁" in new_version.expression_style assert character2.current_personality_version_id == target_id @pytest.mark.asyncio async def test_apply_procedural_candidate_creates_memory() -> None: async with AsyncSessionLocal() as session: character, cycle = await _setup(session) candidate = _candidate( character.id, cycle.id, type="procedural", level="L2", delta_json=dumps({"title": "先确认关键假设", "content": "技术问题先确认前提再作答"}), ) await evolution_repo.add_candidates(session, [candidate]) await session.flush() target_id = await evolution_service.apply_evolution_candidate(session, candidate) await session.commit() assert target_id is not None async with AsyncSessionLocal() as session: mem = await session.get(Memory, target_id) assert mem is not None assert mem.type == "procedural" assert mem.title == "先确认关键假设" assert mem.source == "big_cycle" @pytest.mark.asyncio async def test_apply_unimplemented_type_returns_none() -> None: async with AsyncSessionLocal() as session: character, cycle = await _setup(session) candidate = _candidate(character.id, cycle.id, type="tool_strategy", level="L2") await evolution_repo.add_candidates(session, [candidate]) await session.flush() result = await evolution_service.apply_evolution_candidate(session, candidate) assert result is None