"""时态快照测试。""" from datetime import UTC, datetime from app.modules.time_sense.duration import OfflineThresholds from app.modules.time_sense.snapshot import ( AnniversaryRef, LocationContext, SnapshotInput, compute_snapshot, ) def _input(**overrides): base = { "now": datetime(2026, 7, 3, 2, 42, tzinfo=UTC), "timezone": "Asia/Shanghai", "location": LocationContext(timezone="Asia/Shanghai", city="上海"), "anniversaries": [], "acquaintance_anchor": datetime(2025, 9, 19, tzinfo=UTC), "last_active_at": datetime(2026, 7, 3, 2, 0, tzinfo=UTC), "home_timezone": "Asia/Shanghai", "offline_thresholds": OfflineThresholds(), } base.update(overrides) return SnapshotInput(**base) def test_snapshot_basic_fields(): snap = compute_snapshot(_input()) assert snap.timezone == "Asia/Shanghai" # 上海 UTC+8,02:42 UTC -> 10:42 本地 assert snap.local_hour == 10 assert snap.shichen in {"辰", "巳"} # 7-11 点区间 assert snap.day_period in {"上午"} assert snap.weekday is not None def test_snapshot_acquaintance_and_offline(): snap = compute_snapshot(_input()) assert snap.acquaintance.total_days > 280 assert snap.offline.band == "active" def test_snapshot_home_tz_diff_zero(): snap = compute_snapshot(_input()) assert snap.home_tz_diff_minutes == 0 def test_snapshot_next_milestone_from_anniversary(): # 下一个相识周年:相识日 2025-09-19,到 2026-07-03 距下一周年(2026-09-19)约 78 天 anni = AnniversaryRef( id="anniv_1", name="相识纪念日", calendar="solar", date_iso="2025-09-19", recurs_yearly=True, kind="anniversary", ) snap = compute_snapshot(_input(anniversaries=[anni])) assert snap.next_milestone is not None assert snap.next_milestone.name == "相识纪念日" assert 70 < snap.next_milestone.days_until < 85 def test_snapshot_active_milestone_today(): # 相识日恰好今天(构造一个今日命中的纪念日) today = datetime(2026, 7, 3, tzinfo=UTC) anni = AnniversaryRef( id="anniv_birthday", name="小明生日", calendar="solar", date_iso="2026-07-03", recurs_yearly=False, kind="birthday", ) snap = compute_snapshot(_input(anniversaries=[anni], acquaintance_anchor=today)) assert any(m.name == "小明生日" for m in snap.active_milestones) assert snap.next_milestone is None or snap.next_milestone.name != "小明生日" def test_snapshot_lunar_present(): snap = compute_snapshot(_input()) # 农历字段非空(lunar_python 可用时) assert snap.lunar is not None assert snap.lunar.year_ganzhi == "丙午" def test_snapshot_2_29_anniversary_in_non_leap_year(): # 2/29 周年在非闰年不应崩溃,且应回退到 2/28 后能算出下一周年 anni = AnniversaryRef( id="anniv_leap", name="闰年相识", calendar="solar", date_iso="2024-02-29", recurs_yearly=True, kind="anniversary", ) snap = compute_snapshot(_input(anniversaries=[anni])) assert snap.next_milestone is not None assert snap.next_milestone.name == "闰年相识" def test_snapshot_recurring_milestone_today(): # recurs_yearly=True 且月日命中今天:应进入 active_milestones anni = AnniversaryRef( id="anniv_today", name="今日周年", calendar="solar", date_iso="2026-07-03", recurs_yearly=True, kind="anniversary", ) snap = compute_snapshot(_input(anniversaries=[anni])) assert any(m.id == "anniv_today" for m in snap.active_milestones)