diff --git a/homeassistant/components/gatus/binary_sensor.py b/homeassistant/components/gatus/binary_sensor.py index a2c2f17a6aa5..73abc4b6d7b6 100644 --- a/homeassistant/components/gatus/binary_sensor.py +++ b/homeassistant/components/gatus/binary_sensor.py @@ -6,7 +6,7 @@ from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .coordinator import GatusConfigEntry, GatusDataUpdateCoordinator @@ -23,10 +23,22 @@ async def async_setup_entry( """Set up the Gatus binary sensor platform.""" coordinator = entry.runtime_data - async_add_entities( - GatusEndpointBinarySensor(coordinator, entry, endpoint_key) - for endpoint_key in coordinator.data - ) + known_endpoints: set[str] = set() + + @callback + def _check_endpoints() -> None: + current_endpoints = set(coordinator.data) + new_endpoints = current_endpoints - known_endpoints + if new_endpoints: + known_endpoints.update(new_endpoints) + async_add_entities( + GatusEndpointBinarySensor(coordinator, entry, endpoint_key) + for endpoint_key in new_endpoints + ) + known_endpoints.intersection_update(current_endpoints) + + _check_endpoints() + entry.async_on_unload(coordinator.async_add_listener(_check_endpoints)) class GatusEndpointBinarySensor(GatusEndpointEntity, BinarySensorEntity): diff --git a/homeassistant/components/gatus/quality_scale.yaml b/homeassistant/components/gatus/quality_scale.yaml index b6362d171a9b..560e712ed6c6 100644 --- a/homeassistant/components/gatus/quality_scale.yaml +++ b/homeassistant/components/gatus/quality_scale.yaml @@ -63,7 +63,7 @@ rules: docs-supported-functions: done docs-troubleshooting: done docs-use-cases: done - dynamic-devices: todo + dynamic-devices: done entity-category: done entity-device-class: done entity-disabled-by-default: diff --git a/tests/components/gatus/test_binary_sensor.py b/tests/components/gatus/test_binary_sensor.py index 1fd6a7433aa1..bc2565915cbc 100644 --- a/tests/components/gatus/test_binary_sensor.py +++ b/tests/components/gatus/test_binary_sensor.py @@ -164,3 +164,72 @@ async def test_binary_sensor_missing_status( state = hass.states.get("binary_sensor.backend_service") assert state is not None assert state.state == "off" + + +async def test_binary_sensor_dynamic_endpoints( + hass: HomeAssistant, + mock_gatus_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test that new endpoints returned by the Gatus API at runtime dynamically create entities.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("binary_sensor.core_backend_service") is not None + assert hass.states.get("binary_sensor.core_new_service") is None + + mock_gatus_client.get_endpoints_statuses.return_value = [ + EndpointStatus( + key="backend_service", + name="Backend Service", + group="Core", + results=[Result(success=True, status=200)], + ), + EndpointStatus( + key="new_service", + name="New Service", + group="Core", + results=[Result(success=True, status=200)], + ), + ] + + freezer.tick(30) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.core_new_service") is not None + + +async def test_binary_sensor_readded_endpoint( + hass: HomeAssistant, + mock_gatus_client: AsyncMock, + mock_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, + entity_registry: er.EntityRegistry, +) -> None: + """Test that a removed endpoint can be successfully re-added and has its entity recreated.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("binary_sensor.core_backend_service") is not None + + mock_gatus_client.get_endpoints_statuses.return_value = [] + freezer.tick(30) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + entity_registry.async_remove("binary_sensor.core_backend_service") + assert hass.states.get("binary_sensor.core_backend_service") is None + + mock_gatus_client.get_endpoints_statuses.return_value = [ + EndpointStatus( + key="backend_service", + name="Backend Service", + group="Core", + results=[Result(success=True, status=200)], + ) + ] + freezer.tick(30) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.core_backend_service") is not None