"""灵核情绪状态机端到端集成测试:Dispatcher + handle_time_event + DB。 验证一炁订阅基础设施(计划 5)+ 灵核情绪状态机(设计 §11)全链路: EventStore.append(time.reunion) → Dispatcher 分发 → handle_time_event → DB 情绪状态更新。 这是 main.py lifespan ``dispatcher.register("time.*", handle_time_event)`` 的端到端验证。 """ from __future__ import annotations import asyncio import pytest from app.db.session import AsyncSessionLocal from app.models.character import Character from app.models.personality import PersonalityVersion from app.modules.runtime.events import Event, InMemoryEventStore from app.repositories.personality_emotion import get_state from app.services.characters import get_active_character_model from app.services.dispatcher import Dispatcher from app.services.personality_emotion import handle_time_event @pytest.mark.asyncio async def test_dispatcher_drives_emotion_state_update() -> None: """append time.reunion → dispatcher 分发 → handle_time_event 写入 DB。""" async with AsyncSessionLocal() as session: character = await get_active_character_model(session) cid = character.id pv = PersonalityVersion( character_id=cid, identity_profile="测试身份", emotion_baseline='{"mood": "neutral", "intensity": 0.2}', ) session.add(pv) await session.flush() character.current_personality_version_id = pv.id await session.commit() store = InMemoryEventStore() dispatcher = Dispatcher(store) dispatcher.register("time.*", handle_time_event) await dispatcher.start() try: await store.append( Event( type="time.reunion", source="time_sense", payload={"reunion_intensity": 0.6, "offline_band": "reunion_impact"}, character_id=cid, ) ) await asyncio.sleep(0.1) # 等 pump + consumer 异步完成 finally: await dispatcher.stop() async with AsyncSessionLocal() as session: row = await get_state(session, cid) assert row is not None assert row.mood == "touched" assert abs(row.intensity - 0.8) < 0.001 # 0.2 baseline + 0.6 delta character = await session.get(Character, cid) assert character is not None assert character.current_mood_id == "touched" @pytest.mark.asyncio async def test_dispatcher_ignores_non_time_events() -> None: """非 time.* 事件不触发 handle_time_event(无副作用:无 state row 写入)。""" async with AsyncSessionLocal() as session: character = await get_active_character_model(session) cid = character.id await session.commit() store = InMemoryEventStore() dispatcher = Dispatcher(store) dispatcher.register("time.*", handle_time_event) await dispatcher.start() try: await store.append(Event(type="run.started", source="run", character_id=cid)) await asyncio.sleep(0.05) finally: await dispatcher.stop() async with AsyncSessionLocal() as session: # 非 time.* 事件 → handler 不被调用 → 无 state row assert await get_state(session, cid) is None