test_cn_holiday_sync.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """cn_holiday sync / read unit tests"""
  2. from datetime import date, datetime
  3. from unittest.mock import MagicMock, patch
  4. from app.services.cn_holiday import is_rest_day, list_day_flags
  5. from app.services.cn_holiday_sync import (
  6. _is_rest_from_type,
  7. is_year_calendar_complete,
  8. map_ailcc_day_row,
  9. seconds_until_next_local_midnight,
  10. sync_cn_holidays,
  11. )
  12. def test_type_rest_flags():
  13. assert _is_rest_from_type(1) == 1
  14. assert _is_rest_from_type(2) == 1
  15. assert _is_rest_from_type(3) == 1
  16. assert _is_rest_from_type(0) == 0
  17. assert _is_rest_from_type(4) == 0
  18. def test_map_row():
  19. row = map_ailcc_day_row(
  20. "2026-10-01",
  21. holiday={"holiday": True, "name": "国庆节(休)", "date": "2026-10-01"},
  22. type_info={"type": 2, "name": "国庆节(休)", "week": 4},
  23. )
  24. assert row["day_date"] == date(2026, 10, 1)
  25. assert row["day_type"] == 2
  26. assert row["is_rest_day"] == 1
  27. assert row["day_name"] == "国庆节(休)"
  28. assert row["week"] == 4
  29. assert row["raw_json"]
  30. def test_map_row_workday_makeup():
  31. row = map_ailcc_day_row(
  32. "2026-10-10",
  33. holiday=None,
  34. type_info={"type": 4, "name": "国庆节(班)", "week": 6},
  35. )
  36. assert row["day_type"] == 4
  37. assert row["is_rest_day"] == 0
  38. def test_map_row_infer_weekend_when_type_missing():
  39. # 2026-10-03 is Saturday
  40. row = map_ailcc_day_row("2026-10-03", holiday=None, type_info=None)
  41. assert row["day_type"] == 1
  42. assert row["is_rest_day"] == 1
  43. assert row["week"] == 6
  44. def test_is_rest_day_from_row():
  45. db = MagicMock()
  46. row = MagicMock()
  47. row.is_rest_day = 1
  48. row.day_type = 2
  49. db.query.return_value.filter.return_value.first.return_value = row
  50. assert is_rest_day(db, date(2026, 10, 1)) is True
  51. def test_is_rest_day_work_type():
  52. db = MagicMock()
  53. row = MagicMock()
  54. row.is_rest_day = 0
  55. row.day_type = 4
  56. db.query.return_value.filter.return_value.first.return_value = row
  57. assert is_rest_day(db, date(2026, 10, 10)) is False
  58. def test_is_rest_day_fallback_weekend():
  59. db = MagicMock()
  60. db.query.return_value.filter.return_value.first.return_value = None
  61. # Saturday
  62. assert is_rest_day(db, date(2026, 8, 8)) is True
  63. # Friday
  64. assert is_rest_day(db, date(2026, 8, 7)) is False
  65. def test_list_day_flags_fills_gaps():
  66. db = MagicMock()
  67. row = MagicMock()
  68. row.day_date = date(2026, 10, 1)
  69. row.day_type = 2
  70. row.is_rest_day = 1
  71. row.day_name = "国庆节(休)"
  72. db.query.return_value.filter.return_value.all.return_value = [row]
  73. flags = list_day_flags(db, date(2026, 10, 1), date(2026, 10, 3))
  74. assert len(flags) == 3
  75. assert flags[0] == {
  76. "dayDate": "2026-10-01",
  77. "dayType": 2,
  78. "isRestDay": True,
  79. "dayName": "国庆节(休)",
  80. }
  81. # 2026-10-02 Friday → work fallback
  82. assert flags[1]["dayDate"] == "2026-10-02"
  83. assert flags[1]["dayType"] == 0
  84. assert flags[1]["isRestDay"] is False
  85. # 2026-10-03 Saturday → rest fallback
  86. assert flags[2]["dayDate"] == "2026-10-03"
  87. assert flags[2]["dayType"] == 1
  88. assert flags[2]["isRestDay"] is True
  89. def test_seconds_until_next_local_midnight():
  90. now = datetime(2026, 8, 10, 23, 59, 0)
  91. assert seconds_until_next_local_midnight(now) == 60
  92. noon = datetime(2026, 8, 10, 12, 0, 0)
  93. assert seconds_until_next_local_midnight(noon) == 12 * 3600
  94. def test_is_year_calendar_complete_true():
  95. session = MagicMock()
  96. # 2026 is not leap → 365
  97. session.query.return_value.filter.return_value.count.return_value = 365
  98. assert is_year_calendar_complete(session, 2026) is True
  99. def test_is_year_calendar_complete_false():
  100. session = MagicMock()
  101. session.query.return_value.filter.return_value.count.return_value = 100
  102. assert is_year_calendar_complete(session, 2026) is False
  103. def test_is_year_calendar_complete_leap():
  104. session = MagicMock()
  105. session.query.return_value.filter.return_value.count.return_value = 366
  106. assert is_year_calendar_complete(session, 2024) is True
  107. session.query.return_value.filter.return_value.count.return_value = 365
  108. assert is_year_calendar_complete(session, 2024) is False
  109. def test_sync_cn_holidays_upsert_mocked():
  110. holiday_payload = {
  111. "code": 0,
  112. "holiday": {
  113. "2026-10-01": {"holiday": True, "name": "国庆节(休)", "date": "2026-10-01"},
  114. },
  115. "type": {
  116. "2026-10-01": {"type": 2, "name": "国庆节(休)", "week": 4},
  117. "2026-10-02": {"type": 2, "name": "国庆节(休)", "week": 5},
  118. },
  119. }
  120. session = MagicMock()
  121. session.query.return_value.filter.return_value.first.return_value = None
  122. # year incomplete → still fetch
  123. session.query.return_value.filter.return_value.count.return_value = 0
  124. class Factory:
  125. def __call__(self):
  126. return session
  127. with patch("app.services.cn_holiday_sync._fetch_batch", return_value=holiday_payload), patch(
  128. "app.services.cn_holiday_sync._years_to_sync", return_value=[2026]
  129. ), patch(
  130. "app.services.cn_holiday_sync._months_for_year", return_value=[(2026, 10)]
  131. ):
  132. n = sync_cn_holidays(Factory())
  133. assert n >= 1
  134. assert session.add.call_count >= 1 or session.merge.call_count >= 1
  135. session.commit.assert_called()
  136. session.close.assert_called()
  137. def test_sync_skips_complete_year():
  138. session = MagicMock()
  139. session.query.return_value.filter.return_value.count.return_value = 365
  140. class Factory:
  141. def __call__(self):
  142. return session
  143. with patch("app.services.cn_holiday_sync._fetch_batch") as fetch, patch(
  144. "app.services.cn_holiday_sync._years_to_sync", return_value=[2026]
  145. ):
  146. n = sync_cn_holidays(Factory())
  147. assert n == 0
  148. fetch.assert_not_called()
  149. session.commit.assert_called()
  150. session.close.assert_called()