test_buttons.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  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. ForceRefreshButton,
  10. PromoteFromNextButton,
  11. TestNextGuestButton,
  12. )
  13. from custom_components.room_cal_ha.const import STORE_NEXT
  14. from custom_components.room_cal_ha.models import OverrideData, ReservationData
  15. def _button(cls):
  16. btn = object.__new__(cls)
  17. btn._coordinator = MagicMock()
  18. btn._coordinator.async_refresh = AsyncMock()
  19. btn._coordinator.async_promote_next_to_current = AsyncMock()
  20. return btn
  21. async def test_force_refresh_calls_coordinator_refresh():
  22. btn = _button(ForceRefreshButton)
  23. await btn.async_press()
  24. btn._coordinator.async_refresh.assert_awaited_once()
  25. async def test_promote_from_next_calls_coordinator_promote():
  26. btn = _button(PromoteFromNextButton)
  27. await btn.async_press()
  28. btn._coordinator.async_promote_next_to_current.assert_awaited_once()
  29. async def test_force_refresh_does_not_call_promote():
  30. btn = _button(ForceRefreshButton)
  31. await btn.async_press()
  32. btn._coordinator.async_promote_next_to_current.assert_not_called()
  33. async def test_promote_does_not_call_refresh():
  34. btn = _button(PromoteFromNextButton)
  35. await btn.async_press()
  36. btn._coordinator.async_refresh.assert_not_called()
  37. # ---------------------------------------------------------------------------
  38. # TestNextGuestButton
  39. # ---------------------------------------------------------------------------
  40. _NEXT = ReservationData(
  41. guest_name="Bob",
  42. check_in_date=date(2026, 7, 10),
  43. check_in_time=time(15, 0),
  44. check_out_date=date(2026, 7, 14),
  45. check_out_time=time(11, 0),
  46. )
  47. _FROZEN_NOW = datetime(2026, 6, 22, 14, 0, 0, tzinfo=timezone.utc)
  48. def _test_next_button(next_data=_NEXT):
  49. btn = object.__new__(TestNextGuestButton)
  50. btn.coordinator = MagicMock()
  51. btn.coordinator.store = MagicMock()
  52. btn.coordinator.store.get.return_value = next_data
  53. btn.coordinator.async_start_override = AsyncMock()
  54. return btn
  55. async def test_test_next_guest_calls_start_override():
  56. btn = _test_next_button()
  57. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  58. await btn.async_press()
  59. btn.coordinator.async_start_override.assert_awaited_once()
  60. async def test_test_next_guest_passes_override_data():
  61. btn = _test_next_button()
  62. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  63. await btn.async_press()
  64. arg = btn.coordinator.async_start_override.call_args[0][0]
  65. assert isinstance(arg, OverrideData)
  66. async def test_test_next_guest_sets_check_in_to_one_hour_ago():
  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. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  71. # now() naive = 2026-06-22 14:00 UTC; minus 1h = 13:00
  72. assert override.current.check_in_date == date(2026, 6, 22)
  73. assert override.current.check_in_time == time(13, 0)
  74. async def test_test_next_guest_preserves_other_fields():
  75. btn = _test_next_button()
  76. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  77. await btn.async_press()
  78. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  79. assert override.current.guest_name == "Bob"
  80. assert override.current.check_out_date == date(2026, 7, 14)
  81. assert override.current.check_out_time == time(11, 0)
  82. async def test_test_next_guest_override_next_is_none():
  83. btn = _test_next_button()
  84. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  85. await btn.async_press()
  86. override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
  87. assert override.next is None
  88. async def test_test_next_guest_reads_from_store():
  89. btn = _test_next_button()
  90. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  91. await btn.async_press()
  92. btn.coordinator.store.get.assert_called_with(STORE_NEXT)
  93. async def test_test_next_guest_no_next_data_does_nothing():
  94. btn = _test_next_button(next_data=None)
  95. with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
  96. await btn.async_press()
  97. btn.coordinator.async_start_override.assert_not_awaited()