test_scheduler.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import pytest
  2. from datetime import datetime, timezone, timedelta
  3. from scheduler import is_recurring_schedule_due, is_onetime_schedule_due
  4. class TestRecurringScheduleDue:
  5. def test_new_schedule_is_due(self):
  6. """A newly created schedule with no last_run should be due."""
  7. schedule = {
  8. "cron": "* * * * *",
  9. "created_at": (datetime.now(timezone.utc) - timedelta(minutes=2)).isoformat(),
  10. }
  11. current_time = datetime.now(timezone.utc)
  12. assert is_recurring_schedule_due(schedule, current_time) is True
  13. def test_just_ran_not_due(self):
  14. """A schedule that just ran should not be due yet."""
  15. schedule = {
  16. "cron": "*/5 * * * *",
  17. "created_at": (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat(),
  18. "last_run": datetime.now(timezone.utc).isoformat(),
  19. }
  20. current_time = datetime.now(timezone.utc)
  21. assert is_recurring_schedule_due(schedule, current_time) is False
  22. def test_five_minutes_ago_is_due(self):
  23. """A schedule with last_run 5+ minutes ago should be due (cron every 5 min)."""
  24. schedule = {
  25. "cron": "*/5 * * * *",
  26. "created_at": (datetime.now(timezone.utc) - timedelta(hours=1)).isoformat(),
  27. "last_run": (datetime.now(timezone.utc) - timedelta(minutes=6)).isoformat(),
  28. }
  29. current_time = datetime.now(timezone.utc)
  30. assert is_recurring_schedule_due(schedule, current_time) is True
  31. def test_daily_cron_not_due(self):
  32. """Daily schedule that ran today should not be due."""
  33. schedule = {
  34. "cron": "0 9 * * *", # Every day at 9am
  35. "created_at": (datetime.now(timezone.utc) - timedelta(days=2)).isoformat(),
  36. "last_run": datetime.now(timezone.utc).isoformat(),
  37. }
  38. current_time = datetime.now(timezone.utc)
  39. assert is_recurring_schedule_due(schedule, current_time) is False
  40. def test_timezone_aware_comparison(self):
  41. """Timezone-aware datetimes should be handled correctly."""
  42. schedule = {
  43. "cron": "*/5 * * * *",
  44. "created_at": datetime.now(timezone.utc).isoformat(),
  45. }
  46. current_time = datetime.now(timezone.utc)
  47. # Should not raise timezone comparison errors
  48. is_recurring_schedule_due(schedule, current_time)
  49. class TestOneTimeScheduleDue:
  50. def test_past_schedule_is_due(self):
  51. """Schedule in the past should be due for execution."""
  52. past_time = datetime.now(timezone.utc) - timedelta(minutes=5)
  53. schedule = {
  54. "execute_at": past_time.isoformat(),
  55. "executed": False,
  56. }
  57. current_time = datetime.now(timezone.utc)
  58. assert is_onetime_schedule_due(schedule, current_time) is True
  59. def test_future_schedule_not_due(self):
  60. """Schedule in the future should not be due."""
  61. future_time = datetime.now(timezone.utc) + timedelta(minutes=5)
  62. schedule = {
  63. "execute_at": future_time.isoformat(),
  64. }
  65. current_time = datetime.now(timezone.utc)
  66. assert is_onetime_schedule_due(schedule, current_time) is False
  67. def test_executed_schedule_not_due(self):
  68. """Already executed schedule should not be due (legacy check)."""
  69. past_time = datetime.now(timezone.utc) - timedelta(minutes=5)
  70. schedule = {
  71. "execute_at": past_time.isoformat(),
  72. "executed": True,
  73. }
  74. current_time = datetime.now(timezone.utc)
  75. assert is_onetime_schedule_due(schedule, current_time) is False
  76. def test_timezone_est(self):
  77. """Schedule with EST timezone should be handled correctly."""
  78. # 2 hours ago EST
  79. past_time = datetime.now(timezone.utc) - timedelta(hours=2)
  80. schedule = {
  81. "execute_at": past_time.isoformat(),
  82. }
  83. current_time = datetime.now(timezone.utc)
  84. assert is_onetime_schedule_due(schedule, current_time) is True
  85. def test_timezone_naive_defaults_utc(self):
  86. """Timezone-naive timestamps should be treated as UTC."""
  87. past_time = datetime.now(timezone.utc) - timedelta(minutes=5)
  88. schedule = {
  89. "execute_at": past_time.replace(tzinfo=None).isoformat(),
  90. }
  91. current_time = datetime.now(timezone.utc)
  92. assert is_onetime_schedule_due(schedule, current_time) is True
  93. def test_exact_time_is_due(self):
  94. """Schedule at exactly current time should be due."""
  95. current_time = datetime.now(timezone.utc)
  96. schedule = {
  97. "execute_at": current_time.isoformat(),
  98. }
  99. assert is_onetime_schedule_due(schedule, current_time) is True