| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- """Unit tests for ForceRefreshButton, OverrideNextButton, and OverrideEmptyButton.
- Uses object.__new__ to bypass HA entity setup — valid because async_press
- only calls coordinator methods, not HA lifecycle hooks.
- """
- from datetime import date, time, datetime, timezone
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from custom_components.room_cal_ha.button import (
- ForceRefreshButton,
- OverrideEmptyButton,
- OverrideNextButton,
- )
- from custom_components.room_cal_ha.const import STORE_NEXT
- from custom_components.room_cal_ha.models import OverrideData, ReservationData
- def _button(cls):
- btn = object.__new__(cls)
- btn._coordinator = MagicMock()
- btn._coordinator.async_refresh = AsyncMock()
- btn._coordinator.async_promote_next_to_active = AsyncMock()
- btn._coordinator.async_clear_active = AsyncMock()
- return btn
- async def test_force_refresh_calls_coordinator_refresh():
- btn = _button(ForceRefreshButton)
- await btn.async_press()
- btn._coordinator.async_refresh.assert_awaited_once()
- async def test_force_refresh_does_not_call_promote():
- btn = _button(ForceRefreshButton)
- await btn.async_press()
- btn._coordinator.async_promote_next_to_active.assert_not_called()
- # ---------------------------------------------------------------------------
- # OverrideNextButton
- # ---------------------------------------------------------------------------
- _NEXT = ReservationData(
- guest_name="Bob",
- check_in_date=date(2026, 7, 10),
- check_in_time=time(15, 0),
- check_out_date=date(2026, 7, 14),
- check_out_time=time(11, 0),
- )
- _FROZEN_NOW = datetime(2026, 6, 22, 14, 0, 0, tzinfo=timezone.utc)
- def _override_next_button(next_data=_NEXT):
- btn = object.__new__(OverrideNextButton)
- btn.coordinator = MagicMock()
- btn.coordinator.store = MagicMock()
- btn.coordinator.store.get.return_value = next_data
- btn.coordinator.async_start_override = AsyncMock()
- return btn
- async def test_override_next_calls_start_override():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- btn.coordinator.async_start_override.assert_awaited_once()
- async def test_override_next_passes_override_data():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- arg = btn.coordinator.async_start_override.call_args[0][0]
- assert isinstance(arg, OverrideData)
- async def test_override_next_sets_check_in_to_one_hour_ago():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
- # now() naive = 2026-06-22 14:00 UTC; minus 1h = 13:00
- assert override.active.check_in_date == date(2026, 6, 22)
- assert override.active.check_in_time == time(13, 0)
- async def test_override_next_preserves_other_fields():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
- assert override.active.guest_name == "Bob"
- assert override.active.check_out_date == date(2026, 7, 14)
- assert override.active.check_out_time == time(11, 0)
- async def test_override_next_end_at_matches_its_own_checkout():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- kwargs = btn.coordinator.async_start_override.call_args[1]
- assert kwargs["end_at"] == datetime(2026, 7, 14, 11, 0, 0)
- async def test_override_next_no_end_at_when_no_checkout():
- btn = _override_next_button(next_data=ReservationData(guest_name="Bob", check_in_date=date(2026, 7, 10), check_in_time=time(15, 0)))
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- kwargs = btn.coordinator.async_start_override.call_args[1]
- assert kwargs["end_at"] is None
- async def test_override_next_reads_from_store():
- btn = _override_next_button()
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- btn.coordinator.store.get.assert_called_with(STORE_NEXT)
- async def test_override_next_no_next_data_does_nothing():
- btn = _override_next_button(next_data=None)
- with patch("custom_components.room_cal_ha.button.dt_util.now", return_value=_FROZEN_NOW):
- await btn.async_press()
- btn.coordinator.async_start_override.assert_not_awaited()
- # ---------------------------------------------------------------------------
- # OverrideEmptyButton
- # ---------------------------------------------------------------------------
- _OUTGOING = 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),
- )
- def _override_empty_button(active=_OUTGOING):
- btn = object.__new__(OverrideEmptyButton)
- btn.coordinator = MagicMock()
- btn.coordinator.data = MagicMock(active=active) if active is not None else None
- btn.coordinator.async_start_override = AsyncMock()
- return btn
- async def test_override_empty_calls_start_override():
- btn = _override_empty_button()
- await btn.async_press()
- btn.coordinator.async_start_override.assert_awaited_once()
- async def test_override_empty_passes_empty_reservation_data():
- btn = _override_empty_button()
- await btn.async_press()
- override: OverrideData = btn.coordinator.async_start_override.call_args[0][0]
- assert isinstance(override, OverrideData)
- assert override.active == ReservationData()
- async def test_override_empty_end_at_matches_outgoing_checkout():
- btn = _override_empty_button()
- await btn.async_press()
- kwargs = btn.coordinator.async_start_override.call_args[1]
- assert kwargs["end_at"] == datetime(2026, 7, 5, 12, 0, 0)
- async def test_override_empty_no_end_at_when_outgoing_has_no_checkout():
- btn = _override_empty_button(active=ReservationData(guest_name="Alice"))
- await btn.async_press()
- kwargs = btn.coordinator.async_start_override.call_args[1]
- assert kwargs["end_at"] is None
- async def test_override_empty_no_end_at_when_no_active_data():
- btn = _override_empty_button(active=None)
- btn.coordinator.data = None
- await btn.async_press()
- kwargs = btn.coordinator.async_start_override.call_args[1]
- assert kwargs["end_at"] is None
|