105 lines
3.4 KiB
Python
105 lines
3.4 KiB
Python
"""时间深度与离线档位测试。"""
|
|
|
|
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
|