| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- """Unit tests for StaleSensor.
- Uses object.__new__ to bypass HA entity setup — valid because is_on only
- reads entry.options, coordinator.data timestamps, and dt_util.utcnow().
- """
- from datetime import datetime, timedelta, timezone
- from unittest.mock import MagicMock, patch
- import pytest
- from custom_components.room_cal_ha.binary_sensor import StaleSensor
- from custom_components.room_cal_ha.coordinator import CoordinatorData
- from custom_components.room_cal_ha.const import (
- CONF_REFRESH_INTERVAL,
- CONF_SYNC_MODE,
- DEFAULT_REFRESH_INTERVAL,
- SYNC_MODE_CALDAV,
- SYNC_MODE_MANUAL,
- )
- from custom_components.room_cal_ha.models import ReservationData
- _NOW = datetime(2026, 6, 22, 12, 0, 0, tzinfo=timezone.utc)
- def _sensor(sync_mode=SYNC_MODE_CALDAV, interval=DEFAULT_REFRESH_INTERVAL, current_ts=None, next_ts=None):
- s = object.__new__(StaleSensor)
- s._entry = MagicMock()
- s._entry.options = {CONF_SYNC_MODE: sync_mode, CONF_REFRESH_INTERVAL: interval}
- coord = MagicMock()
- coord.data = CoordinatorData(
- current=ReservationData(),
- next=ReservationData(),
- current_last_refresh=current_ts,
- next_last_refresh=next_ts,
- )
- s.coordinator = coord
- return s
- # ---------------------------------------------------------------------------
- # Manual mode — always None
- # ---------------------------------------------------------------------------
- def test_stale_sensor_manual_mode_returns_none():
- s = _sensor(sync_mode=SYNC_MODE_MANUAL)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is None
- # ---------------------------------------------------------------------------
- # CalDAV mode — fresh timestamps
- # ---------------------------------------------------------------------------
- def test_stale_sensor_fresh_timestamps_returns_false():
- recent = _NOW - timedelta(minutes=5)
- s = _sensor(current_ts=recent, next_ts=recent, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is False
- # ---------------------------------------------------------------------------
- # CalDAV mode — stale / missing timestamps
- # ---------------------------------------------------------------------------
- def test_stale_sensor_none_current_ts_returns_true():
- recent = _NOW - timedelta(minutes=5)
- s = _sensor(current_ts=None, next_ts=recent, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is True
- def test_stale_sensor_none_next_ts_returns_true():
- recent = _NOW - timedelta(minutes=5)
- s = _sensor(current_ts=recent, next_ts=None, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is True
- def test_stale_sensor_both_ts_none_returns_true():
- s = _sensor(current_ts=None, next_ts=None, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is True
- def test_stale_sensor_expired_current_ts_returns_true():
- # interval=15 → threshold=30 min; set current 31 min ago
- stale = _NOW - timedelta(minutes=31)
- fresh = _NOW - timedelta(minutes=5)
- s = _sensor(current_ts=stale, next_ts=fresh, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is True
- def test_stale_sensor_exactly_at_threshold_is_not_stale():
- # Exactly 2x interval is NOT stale (uses strict >)
- threshold = _NOW - timedelta(minutes=30)
- s = _sensor(current_ts=threshold, next_ts=threshold, interval=15)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is False
- def test_stale_sensor_custom_interval():
- # interval=5 → threshold=10 min; set both 9 min ago → fresh
- recent = _NOW - timedelta(minutes=9)
- s = _sensor(current_ts=recent, next_ts=recent, interval=5)
- with patch("custom_components.room_cal_ha.binary_sensor.dt_util.utcnow", return_value=_NOW):
- assert s.is_on is False
|