"""时间深度与离线档位测试。""" from datetime import UTC, datetime, timedelta import pytest from pydantic import ValidationError from app.modules.time_sense.duration import ( OfflineThresholds, classify_offline_band, compute_acquaintance_duration, format_duration_cn, ) def test_acquaintance_duration_zero_days(): anchor = datetime(2026, 7, 3, tzinfo=UTC) now = anchor d = compute_acquaintance_duration(anchor, now) assert d.total_days == 0 assert "今天" in d.natural_language or "刚" in d.natural_language def test_acquaintance_duration_287_days(): anchor = datetime(2026, 7, 3, tzinfo=UTC) - timedelta(days=287) now = datetime(2026, 7, 3, tzinfo=UTC) d = compute_acquaintance_duration(anchor, now) assert d.total_days == 287 assert d.years == 0 assert d.months == 9 assert d.days == 17 # 287 % 30 = 17(按 30 天/月近似) assert "287" in d.natural_language def test_acquaintance_duration_one_year(): anchor = datetime(2025, 7, 3, tzinfo=UTC) now = datetime(2026, 7, 3, tzinfo=UTC) d = compute_acquaintance_duration(anchor, now) assert d.total_days == 365 assert d.years == 1 def test_format_duration_cn_100_days(): # 100 天 = 3 个月 10 天(按 30 天/月近似),实现走月日分支 assert format_duration_cn(100) == "3 个月 10 天(100 天)" def test_format_duration_cn_365_days(): text = format_duration_cn(365) assert "1 年" in text def test_classify_offline_band_active(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) last = now - timedelta(minutes=30) t = OfflineThresholds() result = classify_offline_band(last, now, t) assert result.band == "active" assert result.minutes == 30 def test_classify_offline_band_short_absence(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) last = now - timedelta(hours=5) result = classify_offline_band(last, now, OfflineThresholds()) assert result.band == "short_absence" def test_classify_offline_band_reunion_impact(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) last = now - timedelta(days=5) result = classify_offline_band(last, now, OfflineThresholds()) assert result.band == "reunion_impact" assert result.reunion_intensity > 0.5 def test_classify_offline_boundary_59min_is_active(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) last = now - timedelta(minutes=59) assert classify_offline_band(last, now, OfflineThresholds()).band == "active" def test_classify_offline_boundary_60min_is_short_absence(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) last = now - timedelta(minutes=60) assert classify_offline_band(last, now, OfflineThresholds()).band == "short_absence" def test_offline_thresholds_violates_monotonicity(): with pytest.raises(ValidationError): OfflineThresholds(active_minutes=100, short_absence_minutes=50) def test_reunion_intensity_clamped_05_to_1(): now = datetime(2026, 7, 3, 12, 0, tzinfo=UTC) # 刚过 reunion_impact 阈值(4320 分钟 + 1 分钟) last = now - timedelta(minutes=4321) intensity = classify_offline_band(last, now, OfflineThresholds()).reunion_intensity assert 0.5 <= intensity <= 1.0 # 超大间隔:30 天 = 43200 分钟,应趋近 1.0 last_far = now - timedelta(minutes=43200) intensity_far = classify_offline_band(last_far, now, OfflineThresholds()).reunion_intensity assert intensity_far > 0.9