""" P3.1-T3: kb_rot_cleaner 月度排程時機計算測試 ============================================= 驗證 _run_kb_rot_cleaner_loop 的 next_run 計算邏輯正確: - 月初 3 點前 → 當月 1 號 03:00 - 其他時間 → 下月 1 號 03:00 - 12 月 → 翌年 1 月 1 號 03:00 2026-04-27 P3.1-T3 by Claude """ from __future__ import annotations from datetime import datetime import pytest # ───────────────────────────────────────────────────────────────────────────── # 複製 main.py 中的 next_run 計算邏輯(純函式萃取,方便測試) # ───────────────────────────────────────────────────────────────────────────── def _calc_next_run(now: datetime) -> datetime: """ 計算下次 kb_rot_cleaner 執行時間(月初 03:00 台北時間) 同 main.py lifespan 中 _run_kb_rot_cleaner_loop 的邏輯。 """ if now.day == 1 and now.hour < 3: return now.replace(hour=3, minute=0, second=0, microsecond=0) elif now.month == 12: return now.replace( year=now.year + 1, month=1, day=1, hour=3, minute=0, second=0, microsecond=0, ) else: return now.replace( month=now.month + 1, day=1, hour=3, minute=0, second=0, microsecond=0, ) # ───────────────────────────────────────────────────────────────────────────── # Tests # ───────────────────────────────────────────────────────────────────────────── class TestKbRotCleanerSchedule: """月度排程時機計算測試""" def test_day1_before_3am_returns_same_day_3am(self): """月初 02:59 → 同日 03:00""" now = datetime(2026, 5, 1, 2, 59, 0) result = _calc_next_run(now) assert result == datetime(2026, 5, 1, 3, 0, 0) def test_day1_exactly_midnight_returns_same_day_3am(self): """月初 00:00 → 同日 03:00""" now = datetime(2026, 5, 1, 0, 0, 0) result = _calc_next_run(now) assert result == datetime(2026, 5, 1, 3, 0, 0) def test_day1_after_3am_returns_next_month(self): """月初 03:01 → 下月 1 號 03:00""" now = datetime(2026, 5, 1, 3, 1, 0) result = _calc_next_run(now) assert result == datetime(2026, 6, 1, 3, 0, 0) def test_mid_month_returns_next_month(self): """月中 → 下月 1 號 03:00""" now = datetime(2026, 5, 15, 12, 0, 0) result = _calc_next_run(now) assert result == datetime(2026, 6, 1, 3, 0, 0) def test_december_rolls_over_to_january(self): """12 月 → 翌年 1 月 1 號 03:00""" now = datetime(2026, 12, 15, 12, 0, 0) result = _calc_next_run(now) assert result == datetime(2027, 1, 1, 3, 0, 0) def test_december_day1_before_3am_stays_december(self): """12 月 1 日 02:00 → 同日 03:00(不跨年)""" now = datetime(2026, 12, 1, 2, 0, 0) result = _calc_next_run(now) assert result == datetime(2026, 12, 1, 3, 0, 0) def test_sleep_seconds_positive(self): """sleep_sec 必須為正數""" now = datetime(2026, 5, 15, 12, 0, 0) next_run = _calc_next_run(now) sleep_sec = (next_run - now).total_seconds() assert sleep_sec > 0 def test_next_run_always_at_hour_3(self): """所有情況下 next_run 的 hour 必須是 3""" test_cases = [ datetime(2026, 1, 1, 2, 59, 59), datetime(2026, 3, 15, 8, 0, 0), datetime(2026, 12, 31, 23, 59, 59), datetime(2026, 6, 1, 3, 30, 0), ] for now in test_cases: result = _calc_next_run(now) assert result.hour == 3, f"Expected hour=3 for now={now}, got {result}" assert result.minute == 0 assert result.second == 0 assert result.microsecond == 0 def test_next_run_always_day1(self): """所有情況下 next_run 的 day 必須是 1""" test_cases = [ datetime(2026, 1, 15, 12, 0, 0), datetime(2026, 5, 2, 0, 0, 0), datetime(2026, 12, 20, 6, 0, 0), ] for now in test_cases: result = _calc_next_run(now) assert result.day == 1, f"Expected day=1 for now={now}, got {result}" if __name__ == "__main__": pytest.main([__file__, "-v"])