from datetime import date, time, datetime, timezone from unittest.mock import AsyncMock, patch import pytest from homeassistant.core import HomeAssistant from custom_components.room_cal_ha.const import ( CONF_CALDAV_ENABLED, CONF_CALDAV_URL, CONF_CALENDAR_NAME, DOMAIN, STORE_CURRENT, STORE_NEXT, ) from custom_components.room_cal_ha.coordinator import RoomCalCoordinator from custom_components.room_cal_ha.models import ReservationData from pytest_homeassistant_custom_component.common import MockConfigEntry SAMPLE = ReservationData( guest_name="Alice", check_in_date=date(2026, 7, 1), check_in_time=time(14, 0), check_out_date=date(2026, 7, 5), check_out_time=time(12, 0), wifi_key="stone-angel-market", door_code="0472", ) CALDAV_OPTIONS = { CONF_CALDAV_ENABLED: True, CONF_CALDAV_URL: "https://cal.example.com", CONF_CALENDAR_NAME: "Room", } _ENTRY_DATA = { "room_state_name": "Room State", "current_name": "Current", "next_name": "Next", } def _caldav_entry(hass): entry = MockConfigEntry(domain=DOMAIN, data=_ENTRY_DATA, options=CALDAV_OPTIONS) entry.add_to_hass(hass) return entry @pytest.fixture async def coordinator(hass: HomeAssistant, mock_entry): mock_entry.add_to_hass(hass) coord = RoomCalCoordinator(hass, mock_entry) await coord.async_setup() return coord async def test_caldav_disabled_serves_empty(coordinator: RoomCalCoordinator): await coordinator.async_refresh() assert coordinator.data.current == ReservationData() assert coordinator.data.next == ReservationData() assert coordinator.data.current_last_refresh is None assert coordinator.data.next_last_refresh is None async def test_caldav_fetch_writes_store_and_timestamps(hass: HomeAssistant): entry = _caldav_entry(hass) coord = RoomCalCoordinator(hass, entry) await coord.async_setup() with patch.object(coord, "_fetch_caldav", return_value=(SAMPLE, None)): await coord.async_refresh() assert coord.data.current.guest_name == "Alice" assert coord.data.current.door_code == "0472" assert coord.data.next == ReservationData() assert coord.data.current_last_refresh is not None assert coord.data.next_last_refresh is not None async def test_caldav_empty_result_clears_store(hass: HomeAssistant): entry = _caldav_entry(hass) coord = RoomCalCoordinator(hass, entry) await coord.async_setup() await coord.store.async_set(STORE_CURRENT, SAMPLE) with patch.object(coord, "_fetch_caldav", return_value=(None, None)): await coord.async_refresh() assert coord.data.current == ReservationData() assert coord.store.get(STORE_CURRENT) is None async def test_caldav_failure_preserves_store(hass: HomeAssistant): entry = _caldav_entry(hass) coord = RoomCalCoordinator(hass, entry) await coord.async_setup() await coord.store.async_set(STORE_CURRENT, SAMPLE) with patch.object(coord, "_fetch_caldav", side_effect=Exception("network error")): await coord.async_refresh() assert coord.data.current.guest_name == "Alice" assert coord.data.current_last_refresh is None async def test_caldav_auth_error_triggers_reauth(hass: HomeAssistant): import caldav.lib.error as caldav_error entry = _caldav_entry(hass) coord = RoomCalCoordinator(hass, entry) await coord.async_setup() with ( patch.object(coord, "_fetch_caldav", side_effect=caldav_error.AuthorizationError), patch.object(entry, "async_start_reauth") as mock_reauth, ): await coord.async_refresh() mock_reauth.assert_called_once_with(hass) async def test_thin_update_overlays_fields(coordinator: RoomCalCoordinator): await coordinator.store.async_set(STORE_CURRENT, SAMPLE) await coordinator.async_refresh() await coordinator.async_apply_update(STORE_CURRENT, {"wifi_ssid": "GuestNet", "door_code": "9999"}) assert coordinator.data.current.wifi_ssid == "GuestNet" assert coordinator.data.current.door_code == "9999" # Untouched fields preserved assert coordinator.data.current.guest_name == "Alice" assert coordinator.data.current.wifi_key == "stone-angel-market" async def test_thin_update_updates_last_refresh(coordinator: RoomCalCoordinator): await coordinator.store.async_set(STORE_CURRENT, SAMPLE) await coordinator.async_refresh() assert coordinator.data.current_last_refresh is None await coordinator.async_apply_update(STORE_CURRENT, {"door_code": "1234"}) assert coordinator.data.current_last_refresh is not None async def test_store_persists_across_reload(hass: HomeAssistant, mock_entry): mock_entry.add_to_hass(hass) coord = RoomCalCoordinator(hass, mock_entry) await coord.async_setup() await coord.store.async_set(STORE_CURRENT, SAMPLE) # New coordinator instance simulates HA restart coord2 = RoomCalCoordinator(hass, mock_entry) await coord2.async_setup() loaded = coord2.store.get(STORE_CURRENT) assert loaded is not None assert loaded.guest_name == "Alice" assert loaded.door_code == "0472"