Bläddra i källkod

Remove API Status and API Last Checked sensors

ZiB 4 månader sedan
förälder
incheckning
17cfd08dbb

+ 2 - 43
custom_components/room_cal_ha/backends/api.py

@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 import logging
-from datetime import date, datetime, timedelta
+from datetime import date, timedelta
 from typing import TYPE_CHECKING
 
 from homeassistant.helpers.aiohttp_client import async_get_clientsession
 
 from ..const import CONF_API_BASE_URL, CONF_API_TOKEN
-from ..models import ApiStatusData, ReservationData
+from ..models import ReservationData
 from .base import BackendAuthError, BackendConnectError, ReservationBackend
 
 if TYPE_CHECKING:
@@ -66,47 +66,6 @@ class RoomCalApiBackend(ReservationBackend):
 
     return _parse_reservation(current_raw), _parse_reservation(next_raw)
 
-  async def async_fetch_status(self) -> ApiStatusData:
-    session = async_get_clientsession(self._hass)
-    url = f"{self._base_url()}/status"
-    try:
-      async with session.get(url, headers=self._headers()) as resp:
-        if resp.status in (401, 403):
-          return ApiStatusData(status="auth_error")
-        body = await resp.json()
-        stale = resp.status != 200
-    except Exception as exc:
-      _LOGGER.debug("Failed to fetch API status: %s", exc)
-      return ApiStatusData(status="unknown")
-
-    checked_at: datetime | None = None
-    if raw := body.get("checked_at"):
-      try:
-        checked_at = datetime.fromisoformat(raw)
-      except ValueError:
-        pass
-
-    return ApiStatusData(
-      status=body.get("status", "unknown"),
-      checks=body.get("checks", {}),
-      checked_at=checked_at,
-      stale=stale,
-    )
-
-  async def async_fetch_services(self) -> dict[str, str]:
-    session = async_get_clientsession(self._hass)
-    url = f"{self._base_url()}/services"
-    try:
-      async with session.get(url, headers=self._headers()) as resp:
-        if resp.status in (401, 403):
-          return {}
-        resp.raise_for_status()
-        body = await resp.json()
-        return body if isinstance(body, dict) else {}
-    except Exception as exc:
-      _LOGGER.debug("Failed to fetch API services: %s", exc)
-      return {}
-
 
 def _parse_reservation(data: dict | None) -> ReservationData | None:
   if data is None:

+ 4 - 14
custom_components/room_cal_ha/coordinator.py

@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import asyncio
 import logging
 from dataclasses import dataclass
 from datetime import datetime, timedelta
@@ -24,7 +23,7 @@ from .const import (
   SYNC_MODE_API,
   SYNC_MODE_CALDAV,
 )
-from .models import ApiStatusData, OverrideData, ReservationData
+from .models import OverrideData, ReservationData
 from dataclasses import fields as dataclass_fields
 from .store import ReservationStore
 
@@ -37,7 +36,6 @@ class CoordinatorData:
   next: ReservationData
   current_last_refresh: datetime | None
   next_last_refresh: datetime | None
-  api_status: ApiStatusData | None = None
   override_active: bool = False
 
 
@@ -69,7 +67,6 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
     backend_current: ReservationData | None = None
     backend_next: ReservationData | None = None
     backend_fetched = False
-    api_status: ApiStatusData | None = None
 
     sync_mode = self.entry.options.get(CONF_SYNC_MODE, "")
 
@@ -94,11 +91,6 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         raise UpdateFailed("API authorization failed; re-auth required")
       except BackendConnectError as exc:
         _LOGGER.warning("API fetch failed, using cached data: %s", exc)
-      api_status, services = await asyncio.gather(
-        api_backend.async_fetch_status(),
-        api_backend.async_fetch_services(),
-      )
-      api_status.services = services
 
     now = dt_util.utcnow()
 
@@ -119,7 +111,6 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         next=displayed_next,
         current_last_refresh=self._current_last_refresh,
         next_last_refresh=self._next_last_refresh,
-        api_status=api_status,
         override_active=True,
       )
 
@@ -130,7 +121,6 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
       next=store_next,
       current_last_refresh=self._current_last_refresh,
       next_last_refresh=self._next_last_refresh,
-      api_status=api_status,
     )
 
   def _reschedule_mode_sync(self, current: ReservationData, nxt: ReservationData) -> None:
@@ -303,7 +293,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
       next=displayed_next,
       current_last_refresh=self._current_last_refresh,
       next_last_refresh=self._next_last_refresh,
-      api_status=self.data.api_status if self.data else None,
+
       override_active=True,
     ))
     if isinstance(override.current.wifi_ssid, str) and isinstance(override.current.wifi_key, str):
@@ -354,7 +344,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         next=displayed_next,
         current_last_refresh=self._current_last_refresh,
         next_last_refresh=self._next_last_refresh,
-        api_status=self.data.api_status if self.data else None,
+  
         override_active=True,
       ))
       return
@@ -377,7 +367,7 @@ class RoomCalCoordinator(DataUpdateCoordinator[CoordinatorData]):
         next=displayed_next,
         current_last_refresh=self._current_last_refresh,
         next_last_refresh=self._next_last_refresh,
-        api_status=self.data.api_status if self.data else None,
+  
         override_active=True,
       ))
     else:

+ 2 - 11
custom_components/room_cal_ha/models.py

@@ -1,7 +1,7 @@
 from __future__ import annotations
 
-from dataclasses import dataclass, field, fields
-from datetime import date, datetime, time
+from dataclasses import dataclass, fields
+from datetime import date, time
 from typing import Any
 
 
@@ -11,15 +11,6 @@ class OverrideData:
   next: "ReservationData | None" = None
 
 
-@dataclass
-class ApiStatusData:
-  status: str = "unknown"
-  checks: dict = field(default_factory=dict)
-  checked_at: datetime | None = None
-  stale: bool = False
-  services: dict = field(default_factory=dict)
-
-
 @dataclass
 class ReservationData:
   guest_name: str | None = None

+ 1 - 72
custom_components/room_cal_ha/sensor.py

@@ -12,14 +12,12 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
 
 from .const import (
   CONF_ROOM_NAME,
-  CONF_SYNC_MODE,
   DOMAIN,
   STORE_CURRENT,
   STORE_NEXT,
-  SYNC_MODE_API,
 )
 from .coordinator import CoordinatorData, RoomCalCoordinator
-from .models import ApiStatusData, ReservationData
+from .models import ReservationData
 
 
 async def async_setup_entry(
@@ -54,16 +52,6 @@ async def async_setup_entry(
       LastRefreshSensor(coordinator, entry, slot, device_info),
     ]
 
-  if entry.options.get(CONF_SYNC_MODE) == SYNC_MODE_API:
-    room_state_device = DeviceInfo(
-      identifiers={(DOMAIN, f"{entry.entry_id}_room_state")},
-      name=f"{room_name} Room State",
-    )
-    entities += [
-      ApiStatusSensor(coordinator, entry, room_state_device),
-      ApiCheckedAtSensor(coordinator, entry, room_state_device),
-    ]
-
   async_add_entities(entities)
 
 
@@ -220,62 +208,3 @@ class LastRefreshSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
       if self._slot == STORE_CURRENT
       else data.next_last_refresh
     )
-
-
-class ApiStatusSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
-  _attr_has_entity_name = True
-  _attr_name = "API Status"
-  _attr_icon = "mdi:api"
-
-  def __init__(
-    self,
-    coordinator: RoomCalCoordinator,
-    entry: ConfigEntry,
-    device_info: DeviceInfo,
-  ) -> None:
-    super().__init__(coordinator)
-    self._attr_unique_id = f"{entry.entry_id}_api_status"
-    self._attr_device_info = device_info
-
-  def _status(self) -> ApiStatusData | None:
-    return self.coordinator.data.api_status
-
-  @property
-  def native_value(self) -> str | None:
-    s = self._status()
-    return s.status if s is not None else None
-
-  @property
-  def extra_state_attributes(self) -> dict | None:
-    s = self._status()
-    if s is None:
-      return None
-    attrs: dict = {"stale": s.stale}
-    if s.checked_at is not None:
-      attrs["checked_at"] = s.checked_at.isoformat()
-    attrs.update(s.checks)
-    if s.services:
-      attrs["services"] = s.services
-    return attrs
-
-
-class ApiCheckedAtSensor(CoordinatorEntity[RoomCalCoordinator], SensorEntity):
-  _attr_has_entity_name = True
-  _attr_name = "API Last Checked"
-  _attr_device_class = SensorDeviceClass.TIMESTAMP
-  _attr_icon = "mdi:clock-check-outline"
-
-  def __init__(
-    self,
-    coordinator: RoomCalCoordinator,
-    entry: ConfigEntry,
-    device_info: DeviceInfo,
-  ) -> None:
-    super().__init__(coordinator)
-    self._attr_unique_id = f"{entry.entry_id}_api_checked_at"
-    self._attr_device_info = device_info
-
-  @property
-  def native_value(self) -> datetime | None:
-    s = self.coordinator.data.api_status
-    return s.checked_at if s is not None else None

+ 1 - 73
tests/test_api_backend.py

@@ -1,6 +1,6 @@
 """Unit tests for RoomCalApiBackend."""
 from contextlib import asynccontextmanager
-from datetime import date, datetime, timedelta, timezone
+from datetime import date, timedelta
 from unittest.mock import AsyncMock, MagicMock, patch
 
 import pytest
@@ -229,75 +229,3 @@ async def test_fetch_500_raises_backend_connect_error(hass: HomeAssistant):
     pytest.raises(BackendConnectError),
   ):
     await backend.async_fetch()
-
-
-# ---------------------------------------------------------------------------
-# async_fetch_status
-# ---------------------------------------------------------------------------
-
-async def test_fetch_status_ok(hass: HomeAssistant):
-  ts = "2026-06-22T10:00:00+00:00"
-  body = {
-    "status": "ok",
-    "checks": {
-      "caldav_connection": {"ok": True},
-      "calendar": {"ok": True},
-      "credentials": {},
-    },
-    "checked_at": ts,
-  }
-  session = _mock_session(200, body)
-  backend = RoomCalApiBackend(hass, _make_entry())
-
-  with patch("custom_components.room_cal_ha.backends.api.async_get_clientsession", return_value=session):
-    status = await backend.async_fetch_status()
-
-  assert status.status == "ok"
-  assert status.checks["caldav_connection"]["ok"] is True
-  assert status.checked_at == datetime.fromisoformat(ts)
-  assert status.stale is False
-
-
-async def test_fetch_status_stale_on_non_200(hass: HomeAssistant):
-  body = {"status": "error", "checks": {}, "checked_at": "2026-06-22T10:00:00+00:00"}
-  session = _mock_session(503, body)
-  # 503 is not in 401/403 so it won't raise BackendAuthError; raise_for_status is NOT called in async_fetch_status
-  backend = RoomCalApiBackend(hass, _make_entry())
-
-  with patch("custom_components.room_cal_ha.backends.api.async_get_clientsession", return_value=session):
-    status = await backend.async_fetch_status()
-
-  assert status.status == "error"
-  assert status.stale is True
-
-
-async def test_fetch_status_401_returns_auth_error_status(hass: HomeAssistant):
-  session = _mock_session(401, {})
-  backend = RoomCalApiBackend(hass, _make_entry())
-
-  with patch("custom_components.room_cal_ha.backends.api.async_get_clientsession", return_value=session):
-    status = await backend.async_fetch_status()
-
-  assert status.status == "auth_error"
-
-
-async def test_fetch_status_network_error_returns_unknown(hass: HomeAssistant):
-  session = _mock_session_raises(Exception("timeout"))
-  backend = RoomCalApiBackend(hass, _make_entry())
-
-  with patch("custom_components.room_cal_ha.backends.api.async_get_clientsession", return_value=session):
-    status = await backend.async_fetch_status()
-
-  assert status.status == "unknown"
-
-
-async def test_fetch_status_bad_checked_at_doesnt_crash(hass: HomeAssistant):
-  body = {"status": "ok", "checks": {}, "checked_at": "not-a-date"}
-  session = _mock_session(200, body)
-  backend = RoomCalApiBackend(hass, _make_entry())
-
-  with patch("custom_components.room_cal_ha.backends.api.async_get_clientsession", return_value=session):
-    status = await backend.async_fetch_status()
-
-  assert status.status == "ok"
-  assert status.checked_at is None

+ 3 - 20
tests/test_coordinator.py

@@ -9,7 +9,6 @@ from pytest_homeassistant_custom_component.common import MockConfigEntry
 from custom_components.room_cal_ha.backends import BackendAuthError, BackendConnectError
 from custom_components.room_cal_ha.const import (
   CONF_API_BASE_URL,
-  CONF_API_TOKEN,
   CONF_CALDAV_URL,
   CONF_CALENDAR_NAME,
   CONF_SYNC_MODE,
@@ -21,7 +20,7 @@ from custom_components.room_cal_ha.const import (
   SYNC_MODE_MANUAL,
 )
 from custom_components.room_cal_ha.coordinator import RoomCalCoordinator
-from custom_components.room_cal_ha.models import ApiStatusData, OverrideData, ReservationData
+from custom_components.room_cal_ha.models import OverrideData, ReservationData
 
 
 SAMPLE = ReservationData(
@@ -65,7 +64,6 @@ async def test_manual_mode_serves_empty(coordinator: RoomCalCoordinator):
   assert coordinator.data.next == ReservationData()
   assert coordinator.data.current_last_refresh is None
   assert coordinator.data.next_last_refresh is None
-  assert coordinator.data.api_status is None
 
 
 # ---------------------------------------------------------------------------
@@ -135,13 +133,7 @@ async def test_caldav_auth_error_triggers_reauth(hass: HomeAssistant):
 # API mode
 # ---------------------------------------------------------------------------
 
-SAMPLE_STATUS = ApiStatusData(
-  status="ok",
-  checks={"caldav_connection": {"ok": True}, "calendar": {"ok": True}},
-)
-
-
-async def test_api_fetch_writes_store_and_status(hass: HomeAssistant):
+async def test_api_fetch_writes_store(hass: HomeAssistant):
   entry = _make_entry(hass, {CONF_SYNC_MODE: SYNC_MODE_API, CONF_API_BASE_URL: "https://api.example.com"})
   coord = RoomCalCoordinator(hass, entry)
   await coord.async_setup()
@@ -149,32 +141,25 @@ async def test_api_fetch_writes_store_and_status(hass: HomeAssistant):
   with patch("custom_components.room_cal_ha.coordinator.RoomCalApiBackend") as MockBackend:
     instance = MockBackend.return_value
     instance.async_fetch = AsyncMock(return_value=(SAMPLE, None))
-    instance.async_fetch_status = AsyncMock(return_value=SAMPLE_STATUS)
-    instance.async_fetch_services = AsyncMock(return_value={})
     await coord.async_refresh()
 
   assert coord.data.current.guest_name == "Alice"
-  assert coord.data.api_status.status == "ok"
   assert coord.data.current_last_refresh is not None
 
 
-async def test_api_connect_error_preserves_store_and_still_fetches_status(hass: HomeAssistant):
+async def test_api_connect_error_preserves_store(hass: HomeAssistant):
   entry = _make_entry(hass, {CONF_SYNC_MODE: SYNC_MODE_API, CONF_API_BASE_URL: "https://api.example.com"})
   coord = RoomCalCoordinator(hass, entry)
   await coord.async_setup()
   await coord.store.async_set(STORE_CURRENT, SAMPLE)
 
-  degraded = ApiStatusData(status="error")
   with patch("custom_components.room_cal_ha.coordinator.RoomCalApiBackend") as MockBackend:
     instance = MockBackend.return_value
     instance.async_fetch = AsyncMock(side_effect=BackendConnectError("timeout"))
-    instance.async_fetch_status = AsyncMock(return_value=degraded)
-    instance.async_fetch_services = AsyncMock(return_value={})
     await coord.async_refresh()
 
   assert coord.data.current.guest_name == "Alice"
   assert coord.data.current_last_refresh is None
-  assert coord.data.api_status.status == "error"
 
 
 async def test_api_auth_error_triggers_reauth(hass: HomeAssistant):
@@ -188,8 +173,6 @@ async def test_api_auth_error_triggers_reauth(hass: HomeAssistant):
   ):
     instance = MockBackend.return_value
     instance.async_fetch = AsyncMock(side_effect=BackendAuthError)
-    instance.async_fetch_status = AsyncMock(return_value=ApiStatusData(status="auth_error"))
-    instance.async_fetch_services = AsyncMock(return_value={})
     await coord.async_refresh()
 
   mock_reauth.assert_called_once_with(hass)

+ 2 - 27
tests/test_models.py

@@ -1,8 +1,8 @@
-from datetime import date, datetime, time, timezone
+from datetime import date, time
 
 import pytest
 
-from custom_components.room_cal_ha.models import ApiStatusData, OverrideData, ReservationData
+from custom_components.room_cal_ha.models import OverrideData, ReservationData
 
 
 # ---------------------------------------------------------------------------
@@ -149,31 +149,6 @@ def test_default_values():
   assert res.door_code is None
 
 
-# ---------------------------------------------------------------------------
-# ApiStatusData — defaults
-# ---------------------------------------------------------------------------
-
-def test_api_status_defaults():
-  s = ApiStatusData()
-  assert s.status == "unknown"
-  assert s.checks == {}
-  assert s.checked_at is None
-  assert s.stale is False
-
-
-def test_api_status_with_data():
-  ts = datetime(2026, 6, 22, 10, 0, tzinfo=timezone.utc)
-  s = ApiStatusData(
-    status="ok",
-    checks={"caldav_connection": {"ok": True}},
-    checked_at=ts,
-    stale=False,
-  )
-  assert s.status == "ok"
-  assert s.checks["caldav_connection"]["ok"] is True
-  assert s.checked_at == ts
-
-
 # ---------------------------------------------------------------------------
 # OverrideData
 # ---------------------------------------------------------------------------

+ 0 - 3
tests/test_reauth_flow.py

@@ -162,8 +162,6 @@ async def test_api_reauth_connect_error_shows_cannot_connect(hass: HomeAssistant
 
 
 async def test_api_reauth_success_updates_token(hass: HomeAssistant, api_entry):
-  from custom_components.room_cal_ha.models import ApiStatusData
-
   result = await _start_reauth(hass, api_entry)
 
   with (
@@ -172,7 +170,6 @@ async def test_api_reauth_success_updates_token(hass: HomeAssistant, api_entry):
   ):
     instance = MockBackend.return_value
     instance.async_fetch = AsyncMock(return_value=(None, None))
-    instance.async_fetch_status = AsyncMock(return_value=ApiStatusData(status="ok"))
     result = await hass.config_entries.flow.async_configure(
       result["flow_id"],
       user_input={CONF_API_TOKEN: "fresh-token"},

+ 1 - 82
tests/test_sensors.py

@@ -9,10 +9,8 @@ from unittest.mock import MagicMock
 import pytest
 
 from custom_components.room_cal_ha.coordinator import CoordinatorData
-from custom_components.room_cal_ha.models import ApiStatusData, ReservationData
+from custom_components.room_cal_ha.models import ReservationData
 from custom_components.room_cal_ha.sensor import (
-  ApiCheckedAtSensor,
-  ApiStatusSensor,
   GuestNameSensor,
   LastRefreshSensor,
   ReservationDateTimeSensor,
@@ -32,7 +30,6 @@ def _coordinator(
   nxt=None,
   current_ts=None,
   next_ts=None,
-  api_status=None,
 ):
   coord = MagicMock()
   coord.data = CoordinatorData(
@@ -40,7 +37,6 @@ def _coordinator(
     next=nxt or ReservationData(),
     current_last_refresh=current_ts,
     next_last_refresh=next_ts,
-    api_status=api_status,
   )
   return coord
 
@@ -247,80 +243,3 @@ def test_last_refresh_sensor_none_before_any_fetch():
   assert s.native_value is None
 
 
-# ---------------------------------------------------------------------------
-# ApiStatusSensor
-# ---------------------------------------------------------------------------
-
-def test_api_status_sensor_native_value():
-  status = ApiStatusData(status="ok")
-  coord = _coordinator(api_status=status)
-  s = object.__new__(ApiStatusSensor)
-  s.coordinator = coord
-  assert s.native_value == "ok"
-
-
-def test_api_status_sensor_none_when_no_status():
-  coord = _coordinator(api_status=None)
-  s = object.__new__(ApiStatusSensor)
-  s.coordinator = coord
-  assert s.native_value is None
-
-
-def test_api_status_sensor_attrs_include_checks_and_stale():
-  checks = {"caldav_connection": {"ok": True}, "calendar": {"ok": False, "reason": "not found"}}
-  status = ApiStatusData(status="degraded", checks=checks, stale=True)
-  coord = _coordinator(api_status=status)
-  s = object.__new__(ApiStatusSensor)
-  s.coordinator = coord
-
-  attrs = s.extra_state_attributes
-  assert attrs["caldav_connection"] == {"ok": True}
-  assert attrs["calendar"] == {"ok": False, "reason": "not found"}
-  assert attrs["stale"] is True
-
-
-def test_api_status_sensor_attrs_include_checked_at_when_present():
-  ts = datetime(2026, 6, 22, 10, 0, tzinfo=timezone.utc)
-  status = ApiStatusData(status="ok", checked_at=ts)
-  coord = _coordinator(api_status=status)
-  s = object.__new__(ApiStatusSensor)
-  s.coordinator = coord
-
-  attrs = s.extra_state_attributes
-  assert "checked_at" in attrs
-  assert attrs["checked_at"] == ts.isoformat()
-
-
-def test_api_status_sensor_attrs_none_when_no_status():
-  coord = _coordinator(api_status=None)
-  s = object.__new__(ApiStatusSensor)
-  s.coordinator = coord
-  assert s.extra_state_attributes is None
-
-
-# ---------------------------------------------------------------------------
-# ApiCheckedAtSensor
-# ---------------------------------------------------------------------------
-
-def test_api_checked_at_sensor_returns_timestamp():
-  ts = datetime(2026, 6, 22, 10, 0, tzinfo=timezone.utc)
-  status = ApiStatusData(checked_at=ts)
-  coord = _coordinator(api_status=status)
-  s = object.__new__(ApiCheckedAtSensor)
-  s.coordinator = coord
-  assert s.native_value == ts
-
-
-def test_api_checked_at_sensor_none_when_no_status():
-  coord = _coordinator(api_status=None)
-  s = object.__new__(ApiCheckedAtSensor)
-  s.coordinator = coord
-  assert s.native_value is None
-
-
-def test_api_checked_at_sensor_none_when_checked_at_not_set():
-  status = ApiStatusData(status="starting")
-  coord = _coordinator(api_status=status)
-  s = object.__new__(ApiCheckedAtSensor)
-  s.coordinator = coord
-  assert s.native_value is None