diff --git a/homeassistant/components/doorbird/device.py b/homeassistant/components/doorbird/device.py index f57e7595dbc..17067b81d9b 100644 --- a/homeassistant/components/doorbird/device.py +++ b/homeassistant/components/doorbird/device.py @@ -102,6 +102,12 @@ class ConfiguredDoorBird: """Get token for device.""" return self._token + def _get_hass_url(self) -> str: + """Get the Home Assistant URL for this device.""" + if custom_url := self.custom_url: + return custom_url + return get_url(self._hass, prefer_external=False) + async def async_register_events(self) -> None: """Register events on device.""" if not self.door_station_events: @@ -146,13 +152,7 @@ class ConfiguredDoorBird: async def _async_register_events(self) -> dict[str, Any]: """Register events on device.""" - # Override url if another is specified in the configuration - if custom_url := self.custom_url: - hass_url = custom_url - else: - # Get the URL of this server - hass_url = get_url(self._hass, prefer_external=False) - + hass_url = self._get_hass_url() http_fav = await self._async_get_http_favorites() if any( # Note that a list comp is used here to ensure all @@ -191,10 +191,14 @@ class ConfiguredDoorBird: self._get_event_name(event): event_type for event, event_type in DEFAULT_EVENT_TYPES } + hass_url = self._get_hass_url() for identifier, data in http_fav.items(): title: str | None = data.get("title") if not title or not title.startswith("Home Assistant"): continue + value: str | None = data.get("value") + if not value or not value.startswith(hass_url): + continue # Not our favorite - different HA instance or stale event = title.partition("(")[2].strip(")") if input_type := favorite_input_type.get(identifier): events.append(DoorbirdEvent(event, input_type)) diff --git a/tests/components/doorbird/conftest.py b/tests/components/doorbird/conftest.py index 0da69a98303..bcdcb49b728 100644 --- a/tests/components/doorbird/conftest.py +++ b/tests/components/doorbird/conftest.py @@ -82,6 +82,10 @@ def patch_doorbird_api_entry_points(api: MagicMock) -> Generator[DoorBird]: "homeassistant.components.doorbird.config_flow.DoorBird", return_value=api, ), + patch( + "homeassistant.components.doorbird.device.get_url", + return_value="http://127.0.0.1:8123", + ), ): yield api diff --git a/tests/components/doorbird/test_device.py b/tests/components/doorbird/test_device.py index cf3beae5e68..f1b0ecb458d 100644 --- a/tests/components/doorbird/test_device.py +++ b/tests/components/doorbird/test_device.py @@ -2,15 +2,141 @@ from copy import deepcopy from http import HTTPStatus +from typing import Any from doorbirdpy import DoorBirdScheduleEntry import pytest -from homeassistant.components.doorbird.const import CONF_EVENTS +from homeassistant.components.doorbird.const import ( + CONF_EVENTS, + DEFAULT_DOORBELL_EVENT, + DEFAULT_MOTION_EVENT, + DOMAIN, +) from homeassistant.core import HomeAssistant +from . import VALID_CONFIG from .conftest import DoorbirdMockerType +from tests.common import MockConfigEntry + + +@pytest.fixture +def doorbird_favorites_with_stale() -> dict[str, dict[str, Any]]: + """Return favorites fixture with stale favorites from another HA instance. + + Creates favorites where identifier "2" has the same event name as "0" + (mydoorbird_doorbell) but points to a different HA instance URL. + These stale favorites should be filtered out. + """ + return { + "http": { + "0": { + "title": "Home Assistant (mydoorbird_doorbell)", + "value": "http://127.0.0.1:8123/api/doorbird/mydoorbird_doorbell?token=test-token", + }, + # Stale favorite from a different HA instance - should be filtered out + "2": { + "title": "Home Assistant (mydoorbird_doorbell)", + "value": "http://old-ha-instance:8123/api/doorbird/mydoorbird_doorbell?token=old-token", + }, + "5": { + "title": "Home Assistant (mydoorbird_motion)", + "value": "http://127.0.0.1:8123/api/doorbird/mydoorbird_motion?token=test-token", + }, + } + } + + +@pytest.fixture +def doorbird_schedule_with_stale() -> list[DoorBirdScheduleEntry]: + """Return schedule fixture with outputs referencing stale favorites. + + Both param "0" and "2" map to doorbell input, but "2" is a stale favorite. + """ + schedule_data = [ + { + "input": "doorbell", + "param": "1", + "output": [ + { + "event": "http", + "param": "0", + "schedule": {"weekdays": [{"to": "107999", "from": "108000"}]}, + }, + { + "event": "http", + "param": "2", + "schedule": {"weekdays": [{"to": "107999", "from": "108000"}]}, + }, + ], + }, + { + "input": "motion", + "param": "", + "output": [ + { + "event": "http", + "param": "5", + "schedule": {"weekdays": [{"to": "107999", "from": "108000"}]}, + }, + ], + }, + ] + return DoorBirdScheduleEntry.parse_all(schedule_data) + + +async def test_stale_favorites_filtered_by_url( + hass: HomeAssistant, + doorbird_mocker: DoorbirdMockerType, + doorbird_favorites_with_stale: dict[str, dict[str, Any]], + doorbird_schedule_with_stale: list[DoorBirdScheduleEntry], +) -> None: + """Test that stale favorites from other HA instances are filtered out.""" + await doorbird_mocker( + favorites=doorbird_favorites_with_stale, + schedule=doorbird_schedule_with_stale, + ) + # Should have 2 event entities - stale favorite "2" is filtered out + # because its URL doesn't match the current HA instance + event_entities = hass.states.async_all("event") + assert len(event_entities) == 2 + + +async def test_custom_url_used_for_favorites( + hass: HomeAssistant, + doorbird_mocker: DoorbirdMockerType, +) -> None: + """Test that custom URL override is used instead of get_url.""" + custom_url = "https://my-custom-url.example.com:8443" + favorites = { + "http": { + "1": { + "title": "Home Assistant (mydoorbird_doorbell)", + "value": f"{custom_url}/api/doorbird/mydoorbird_doorbell?token=test-token", + }, + "2": { + "title": "Home Assistant (mydoorbird_motion)", + "value": f"{custom_url}/api/doorbird/mydoorbird_motion?token=test-token", + }, + } + } + config_with_custom_url = { + **VALID_CONFIG, + "hass_url_override": custom_url, + } + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="1CCAE3AAAAAA", + data=config_with_custom_url, + options={CONF_EVENTS: [DEFAULT_DOORBELL_EVENT, DEFAULT_MOTION_EVENT]}, + ) + await doorbird_mocker(entry=entry, favorites=favorites) + + # Should have 2 event entities using the custom URL + event_entities = hass.states.async_all("event") + assert len(event_entities) == 2 + async def test_no_configured_events( hass: HomeAssistant,