"""大周天内驱力生成测试(_phase_evaluation 子步骤 _generate_drive)。""" import pytest from app.application.big_cycle import drain_pending_big_cycles, maybe_trigger_big_cycle from app.db.session import AsyncSessionLocal from app.models.memory import Memory from app.repositories import drive as drive_repo from app.services.characters import get_active_character_model _DRIVE_RESPONSE = ( '[{"question":"用户为何频繁提到项目X","source":"memory_gap",' '"expected_value":0.7,"urgency":0.5,"risk_level":"L2",' '"related_entities":["项目X"],' '"subgoals":[{"title":"询问项目X目标","description":"主动询问用户",' '"expected_information_gain":0.6,"interruption_cost":0.3,"risk_level":"L4"}]}]' ) async def _seed_memories(character_id: str, count: int) -> None: async with AsyncSessionLocal() as session: for i in range(count): session.add( Memory( character_id=character_id, type="user", title=f"记忆{i}", content=f"内容{i}", ) ) await session.commit() @pytest.fixture def mock_drive_chat(monkeypatch: pytest.MonkeyPatch) -> None: async def fake_chat(*_args, **_kwargs): return _DRIVE_RESPONSE monkeypatch.setattr("app.application.big_cycle._call_model", fake_chat) @pytest.mark.asyncio async def test_big_cycle_generates_curiosity_and_subgoal( mock_drive_chat, # noqa: ARG001 - pytest fixture 靠 monkeypatch 副作用生效 ) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) await _seed_memories(character.id, 20) cycle_id = await maybe_trigger_big_cycle(character.id, trigger="test") assert cycle_id is not None await drain_pending_big_cycles() async with AsyncSessionLocal() as session: curiosities, c_total = await drive_repo.list_curiosities(session, character.id) subgoals, s_total = await drive_repo.list_subgoals(session, character.id) assert c_total == 1 assert curiosities[0].question == "用户为何频繁提到项目X" assert curiosities[0].source == "memory_gap" assert curiosities[0].cycle_id == cycle_id assert s_total == 1 assert subgoals[0].status == "candidate" assert subgoals[0].curiosity_question_id == curiosities[0].id assert subgoals[0].title == "询问项目X目标" @pytest.mark.asyncio async def test_big_cycle_auto_approves_low_risk_subgoal( mock_drive_chat, # noqa: ARG001 ) -> None: """低风险次生目标(L0-L2)应在大周天中自动通过并生成 Goal。""" from sqlalchemy import select from app.models.governance import Goal # 使用低风险的子目标响应 _low_risk_response = ( '[{"question":"还有哪些未知领域","source":"world_uncertainty",' '"expected_value":0.6,"urgency":0.4,"risk_level":"L1",' '"subgoals":[{"title":"探索未知领域","description":"自动探索",' '"expected_information_gain":0.5,"interruption_cost":0.2,"risk_level":"L1"}]}]' ) async def fake_low_risk(*_args, **_kwargs): return _low_risk_response import app.application.big_cycle as bc original = bc._call_model bc._call_model = fake_low_risk async with AsyncSessionLocal() as session: character = await get_active_character_model(session) await _seed_memories(character.id, 20) cycle_id = await maybe_trigger_big_cycle(character.id, trigger="test") assert cycle_id is not None await drain_pending_big_cycles() bc._call_model = original async with AsyncSessionLocal() as session: subgoals, s_total = await drive_repo.list_subgoals(session, character.id) assert s_total == 1 assert subgoals[0].status in ("approved", "auto_execute_pending"), ( f"预期 approved 或 auto_execute_pending,实际={subgoals[0].status}" ) # 验证自动生成了 Goal async with AsyncSessionLocal() as session: result = await session.execute( select(Goal).where(Goal.character_id == character.id) ) goals = result.scalars().all() matching = [g for g in goals if g.title == "探索未知领域"] assert len(matching) == 1 assert matching[0].level == "task"