test_buttons.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. """Unit tests for ForceRefreshButton, PromoteFromNextButton, and TestNextGuestButton.
  2. Uses object.__new__ to bypass HA entity setup — valid because async_press
  3. only calls coordinator methods, not HA lifecycle hooks.
  4. """
  5. from datetime import date, time, datetime, timezone
  6. from unittest.mock import AsyncMock, MagicMock, patch
  7. import pytest
  8. from custom_components.room_cal_ha.button import (
  9. ClearCurrentButton,
  10. ForceRefreshButton,
  11. PromoteFromNextButton,
  12. TestNextGuestButton,
  13. )
  14. from custom_components.room_cal_ha.const import STORE_NEXT
  15. from custom_components.room_cal_ha.models import OverrideData, ReservationData
  16. def _button(cls):
  17. btn = object.__new__(cls)
  18. btn._coordinator = MagicMock()
  19. btn._coordinator.async_refresh = AsyncMock()
  20. btn._coordinator.async_promote_next_to_current = AsyncMock()
  21. btn._coordinator.async_clear_current = AsyncMock()
  22. return btn
  23. async def test_force_refresh_calls_coordinator_refresh():
  24. btn = _button(ForceRefreshButton)
  25. await btn.async_press()
  26. btn._coordinator.async_refresh.assert_awaited_once()
  27. async def test_promote_from_next_calls_coordinator_promote():
  28. btn = _button(PromoteFromNextButton)
  29. await btn.async_press()
  30. btn._coordinator.async_promote_next_to_current.assert_awaited_once()
  31. async def test_force_refresh_does_not_call_promote():
  32. btn = _button(ForceRefreshButton)
  33. await btn.async_press()
  34. btn._coordinator.async_promote_next_to_current.assert_not_called()
  35. async def test_promote_does_not_call_refresh():
  36. btn = _button(PromoteFromNextButton)
  37. await btn.async_press()
  38. btn._coordinator.async_refresh.assert_not_called()
  39. async def test_clear_current_calls_coordinator_clear():
  40. btn = _button(ClearCurrentButton)
  41. await btn.async_press()
  42. btn._coordinator.async_clear_current.assert_awaited_once()
  43. async def test_clear_current_does_not_call_refresh_or_promote():
  44. btn = _button(ClearCurrentButton)
  45. await btn.async_press()
  46. btn._coordinator.async_refresh.assert_not_called()
  47. btn._coordinator.async_promote_next_to_current.assert_not_called()
  48. # ---------------------------------------------------------------------------
  49. # TestNextGuestButton
  50. # ---------------------------------------------------------------------------
  51. _NEXT = ReservationData(
  52. guest_name="Bob",
  53. check_in_date=date(2026, 7, 10),
  54. check_in_time=time(15, 0),
  55. check_out_date=date(2026, 7, 14),
  56. check_out_time=time(11, 0),
  57. )
  58. _FROZEN_NOW = datetime(2026, 6, 22, 14, 0, 0, tzinfo=timezone.utc)
  59. def _test_next_button(next_data=_NEXT):
  60. btn = object.__new__(TestNextGuestButton)
  61. btn.coordinator = MagicMock()
  62. btn.coordinator.store = MagicMock()
  63. btn.coordinator.store.get.return_value = next_data
  64. btn.coordinator.async_start_override = AsyncMock()
  65. return btn
  66. async def test_test_next_guest_calls_start_override():
  67. btn = _test_next_button()
  68. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  69. await btn.async_press()
  70. btn.coordinator.async_start_override.assert_awaited_once()
  71. async def test_test_next_guest_passes_override_data():
  72. btn = _test_next_button()
  73. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  74. await btn.async_press()
  75. arg = btn.coordinator.async_start_override.call_args[0][0]
  76. assert isinstance(arg, OverrideData)
  77. async def test_test_next_guest_sets_check_in_to_one_hour_ago():
  78. btn = _test_next_button()
  79. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  80. await btn.async_press()
  81. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  82. # now() naive = 2026-06-22 14:00 UTC; minus 1h = 13:00
  83. assert override.current.check_in_date == date(2026, 6, 22)
  84. assert override.current.check_in_time == time(13, 0)
  85. async def test_test_next_guest_preserves_other_fields():
  86. btn = _test_next_button()
  87. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  88. await btn.async_press()
  89. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  90. assert override.current.guest_name == "Bob"
  91. assert override.current.check_out_date == date(2026, 7, 14)
  92. assert override.current.check_out_time == time(11, 0)
  93. async def test_test_next_guest_override_next_is_none():
  94. btn = _test_next_button()
  95. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  96. await btn.async_press()
  97. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  98. assert override.next is None
  99. async def test_test_next_guest_reads_from_store():
  100. btn = _test_next_button()
  101. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  102. await btn.async_press()
  103. btn.coordinator.store.get.assert_called_with(STORE_NEXT)
  104. async def test_test_next_guest_no_next_data_does_nothing():
  105. btn = _test_next_button(next_data=None)
  106. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  107. await btn.async_press()
  108. btn.coordinator.async_start_override.assert_not_awaited()