test_coordinator.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from datetime import date, time, datetime, timezone
  2. from unittest.mock import AsyncMock, patch
  3. import pytest
  4. from homeassistant.core import HomeAssistant
  5. from custom_components.room_cal_ha.const import (
  6. CONF_CALDAV_ENABLED,
  7. CONF_CALDAV_URL,
  8. CONF_CALENDAR_NAME,
  9. DOMAIN,
  10. STORE_CURRENT,
  11. STORE_NEXT,
  12. )
  13. from custom_components.room_cal_ha.coordinator import RoomCalCoordinator
  14. from custom_components.room_cal_ha.models import ReservationData
  15. from pytest_homeassistant_custom_component.common import MockConfigEntry
  16. SAMPLE = ReservationData(
  17. guest_name="Alice",
  18. check_in_date=date(2026, 7, 1),
  19. check_in_time=time(14, 0),
  20. check_out_date=date(2026, 7, 5),
  21. check_out_time=time(12, 0),
  22. wifi_key="stone-angel-market",
  23. door_code="0472",
  24. )
  25. CALDAV_OPTIONS = {
  26. CONF_CALDAV_ENABLED: True,
  27. CONF_CALDAV_URL: "https://cal.example.com",
  28. CONF_CALENDAR_NAME: "Room",
  29. }
  30. _ENTRY_DATA = {
  31. "room_state_name": "Room State",
  32. "current_name": "Current",
  33. "next_name": "Next",
  34. }
  35. def _caldav_entry(hass):
  36. entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, options=CALDAV_OPTIONS)
  37. entry.add_to_hass(hass)
  38. return entry
  39. @pytest.fixture
  40. async def coordinator(hass: HomeAssistant, mock_entry):
  41. mock_entry.add_to_hass(hass)
  42. coord = RoomCalCoordinator(hass, mock_entry)
  43. await coord.async_setup()
  44. return coord
  45. async def test_caldav_disabled_serves_empty(coordinator: RoomCalCoordinator):
  46. await coordinator.async_refresh()
  47. assert coordinator.data.current == ReservationData()
  48. assert coordinator.data.next == ReservationData()
  49. assert coordinator.data.current_last_refresh is None
  50. assert coordinator.data.next_last_refresh is None
  51. async def test_caldav_fetch_writes_store_and_timestamps(hass: HomeAssistant):
  52. entry = _caldav_entry(hass)
  53. coord = RoomCalCoordinator(hass, entry)
  54. await coord.async_setup()
  55. with patch.object(coord, "_fetch_caldav", return_value=(SAMPLE, None)):
  56. await coord.async_refresh()
  57. assert coord.data.current.guest_name == "Alice"
  58. assert coord.data.current.door_code == "0472"
  59. assert coord.data.next == ReservationData()
  60. assert coord.data.current_last_refresh is not None
  61. assert coord.data.next_last_refresh is not None
  62. async def test_caldav_empty_result_clears_store(hass: HomeAssistant):
  63. entry = _caldav_entry(hass)
  64. coord = RoomCalCoordinator(hass, entry)
  65. await coord.async_setup()
  66. await coord.store.async_set(STORE_CURRENT, SAMPLE)
  67. with patch.object(coord, "_fetch_caldav", return_value=(None, None)):
  68. await coord.async_refresh()
  69. assert coord.data.current == ReservationData()
  70. assert coord.store.get(STORE_CURRENT) is None
  71. async def test_caldav_failure_preserves_store(hass: HomeAssistant):
  72. entry = _caldav_entry(hass)
  73. coord = RoomCalCoordinator(hass, entry)
  74. await coord.async_setup()
  75. await coord.store.async_set(STORE_CURRENT, SAMPLE)
  76. with patch.object(coord, "_fetch_caldav", side_effect=Exception("network error")):
  77. await coord.async_refresh()
  78. assert coord.data.current.guest_name == "Alice"
  79. assert coord.data.current_last_refresh is None
  80. async def test_caldav_auth_error_triggers_reauth(hass: HomeAssistant):
  81. import caldav.lib.error as caldav_error
  82. entry = _caldav_entry(hass)
  83. coord = RoomCalCoordinator(hass, entry)
  84. await coord.async_setup()
  85. with (
  86. patch.object(coord, "_fetch_caldav", side_effect=caldav_error.AuthorizationError),
  87. patch.object(entry, "async_start_reauth") as mock_reauth,
  88. ):
  89. await coord.async_refresh()
  90. mock_reauth.assert_called_once_with(hass)
  91. async def test_thin_update_overlays_fields(coordinator: RoomCalCoordinator):
  92. await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
  93. await coordinator.async_refresh()
  94. await coordinator.async_apply_update(STORE_CURRENT, {"wifi_ssid": "GuestNet", "door_code": "9999"})
  95. assert coordinator.data.current.wifi_ssid == "GuestNet"
  96. assert coordinator.data.current.door_code == "9999"
  97. # Untouched fields preserved
  98. assert coordinator.data.current.guest_name == "Alice"
  99. assert coordinator.data.current.wifi_key == "stone-angel-market"
  100. async def test_thin_update_updates_last_refresh(coordinator: RoomCalCoordinator):
  101. await coordinator.store.async_set(STORE_CURRENT, SAMPLE)
  102. await coordinator.async_refresh()
  103. assert coordinator.data.current_last_refresh is None
  104. await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "1234"})
  105. assert coordinator.data.current_last_refresh is not None
  106. async def test_store_persists_across_reload(hass: HomeAssistant, mock_entry):
  107. mock_entry.add_to_hass(hass)
  108. coord = RoomCalCoordinator(hass, mock_entry)
  109. await coord.async_setup()
  110. await coord.store.async_set(STORE_CURRENT, SAMPLE)
  111. # New coordinator instance simulates HA restart
  112. coord2 = RoomCalCoordinator(hass, mock_entry)
  113. await coord2.async_setup()
  114. loaded = coord2.store.get(STORE_CURRENT)
  115. assert loaded is not None
  116. assert loaded.guest_name == "Alice"
  117. assert loaded.door_code == "0472"