初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,112 @@
|
||||
"""辰正集成测试:重逢流事件链 + milestone 联动(设计 §6.3)。"""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.models.chat import ChatMessage, ChatSession
|
||||
from app.models.time_anniversary import TimeAnniversary
|
||||
from app.modules.runtime.events import InMemoryEventStore
|
||||
from app.modules.time_sense.modulation import ModulationInput
|
||||
from app.services.characters import get_active_character_model
|
||||
from app.services.time_sense import compute_live_snapshot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reunion_flow_emits_reunion_and_sentiment():
|
||||
"""离线 > 3 天 → time.reunion + time.sentiment(reunion_memory)。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
chat_session = ChatSession(character_id=character.id, status="active")
|
||||
session.add(chat_session)
|
||||
await session.flush()
|
||||
old_msg = ChatMessage(
|
||||
character_id=character.id,
|
||||
session_id=chat_session.id,
|
||||
role="user",
|
||||
content="5 天前的消息",
|
||||
created_at=datetime(2026, 6, 29, 4, 0, tzinfo=UTC),
|
||||
)
|
||||
session.add(old_msg)
|
||||
await session.flush()
|
||||
|
||||
store = InMemoryEventStore()
|
||||
result = await compute_live_snapshot(
|
||||
session,
|
||||
store,
|
||||
character.id,
|
||||
ModulationInput(),
|
||||
now=datetime(2026, 7, 4, 4, 0, tzinfo=UTC), # 5 天后
|
||||
)
|
||||
|
||||
assert result.snapshot is not None
|
||||
assert result.snapshot.offline.band == "reunion_impact"
|
||||
event_types = {e.type for e in result.emitted_events}
|
||||
assert "time.reunion" in event_types
|
||||
assert "time.sentiment" in event_types # reunion_memory 触发
|
||||
sentiment = next(e for e in result.emitted_events if e.type == "time.sentiment")
|
||||
assert sentiment.payload["trigger_type"] == "reunion_memory"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_event_when_active():
|
||||
"""active 档(无历史消息,last_active=now)→ 无 reunion/sentiment 事件。"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
store = InMemoryEventStore()
|
||||
result = await compute_live_snapshot(
|
||||
session,
|
||||
store,
|
||||
character.id,
|
||||
ModulationInput(),
|
||||
now=datetime(2026, 7, 4, 4, 0, tzinfo=UTC), # 上海 12:00 上午
|
||||
)
|
||||
assert result.snapshot is not None
|
||||
assert result.snapshot.offline.band == "active"
|
||||
event_types = {e.type for e in result.emitted_events}
|
||||
assert "time.reunion" not in event_types
|
||||
assert "time.sentiment" not in event_types
|
||||
assert "time.user_care" not in event_types # 白天 + 无 birthday 等信号
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_milestone_at_late_night_emits_sentiment_overlay():
|
||||
"""凌晨 + 今日命中纪念日 → active_milestones 非空 + time.sentiment(late_night_overlay)。
|
||||
|
||||
验证 anniversary 命中 + 凌晨时段联动 sentiment 的集成路径(Task 4 未覆盖)。
|
||||
"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
anni = TimeAnniversary(
|
||||
character_id=character.id,
|
||||
name="我们的相遇日",
|
||||
calendar="solar",
|
||||
date="2026-07-05", # 今日(now=18:00 UTC → 上海 02:00 本地日期 07-05)命中
|
||||
recurs_yearly=True,
|
||||
kind="anniversary",
|
||||
subject_type="relationship",
|
||||
source="manual",
|
||||
)
|
||||
session.add(anni)
|
||||
await session.flush()
|
||||
|
||||
store = InMemoryEventStore()
|
||||
result = await compute_live_snapshot(
|
||||
session,
|
||||
store,
|
||||
character.id,
|
||||
ModulationInput(),
|
||||
now=datetime(2026, 7, 4, 18, 0, tzinfo=UTC), # 上海 02:00 凌晨
|
||||
)
|
||||
|
||||
assert result.snapshot is not None
|
||||
assert result.snapshot.day_period == "凌晨"
|
||||
assert len(result.snapshot.active_milestones) >= 1
|
||||
# duration_node(0 不在 nodes)/anniversary_prewarm(next=365>7)/reunion_memory(active 档) 都不触发
|
||||
# → late_night_overlay(凌晨 + has_active_milestone)
|
||||
sentiment_events = [e for e in result.emitted_events if e.type == "time.sentiment"]
|
||||
assert len(sentiment_events) == 1
|
||||
assert sentiment_events[0].payload["trigger_type"] == "late_night_overlay"
|
||||
# active 档(无 chat 历史)→ 不触发 reunion
|
||||
assert "time.reunion" not in {e.type for e in result.emitted_events}
|
||||
Reference in new issue
Block a user