diff --git a/homeassistant/components/html5/issue.py b/homeassistant/components/html5/issue.py index 66c11f5c7421..37cb84fbff7f 100644 --- a/homeassistant/components/html5/issue.py +++ b/homeassistant/components/html5/issue.py @@ -52,3 +52,33 @@ def deprecated_dismiss_action_call(hass: HomeAssistant) -> None: "new_action": "html5.dismiss_message", }, ) + + +@callback +def deprecated_event_bus(hass: HomeAssistant, event: str) -> None: + """Raise a deprecation issue for listeners on the event bus.""" + + if listeners := hass.bus.async_listeners().get(event): + async_create_issue( + hass, + DOMAIN, + f"deprecated_event_bus_{event}", + breaks_in_ha_version="2027.2.0", + is_fixable=False, + severity=IssueSeverity.WARNING, + translation_key="deprecated_event_bus", + translation_placeholders={ + "event": event, + "listeners": str(listeners), + "example_yaml": """```yaml +triggers: + - trigger: event.received + target: + entity_id: event.my_device + options: + event_type: + - received +``` +""", + }, + ) diff --git a/homeassistant/components/html5/notify.py b/homeassistant/components/html5/notify.py index 00e5762b3ddc..1e6c1b743035 100644 --- a/homeassistant/components/html5/notify.py +++ b/homeassistant/components/html5/notify.py @@ -60,7 +60,11 @@ from .const import ( SERVICE_DISMISS, ) from .entity import HTML5Entity, Registration -from .issue import deprecated_dismiss_action_call, deprecated_notify_action_call +from .issue import ( + deprecated_dismiss_action_call, + deprecated_event_bus, + deprecated_notify_action_call, +) _LOGGER = logging.getLogger(__name__) @@ -409,6 +413,9 @@ class HTML5PushCallbackView(HomeAssistantView): event_payload[ATTR_TYPE], event_payload, ) + + deprecated_event_bus(hass, event_name) + return self.json({"status": "ok", "event": event_payload[ATTR_TYPE]}) diff --git a/homeassistant/components/html5/strings.json b/homeassistant/components/html5/strings.json index 3dea89e6ead2..1bc8941c65e6 100644 --- a/homeassistant/components/html5/strings.json +++ b/homeassistant/components/html5/strings.json @@ -55,6 +55,10 @@ "description": "The action `{action}` is deprecated and will be removed in a future release.\n\nPlease update your automations and scripts to use the notify entities with the `{new_action}` action instead.", "title": "[%key:component::html5::issues::deprecated_notify_action::title%]" }, + "deprecated_event_bus": { + "description": "Detected **{listeners}** listener(s) for the event `{event}`.\n\nThe HTML5 Push Notifications integration firing events on the event bus is deprecated and this functionality will be removed in a future release.\n\nPlease update your automations and scripts to use the event entities instead.\n\n## Example automation:\n\n{example_yaml}", + "title": "Detected use of deprecated event {event}" + }, "deprecated_notify_action": { "description": "The action `{action}` is deprecated and will be removed in a future release.\n\nPlease update your automations and scripts to use the notify entities with the `{new_action_1}` or `{new_action_2}` actions instead.", "title": "Detected use of deprecated action {action}" diff --git a/tests/components/html5/test_event.py b/tests/components/html5/test_event.py index cd4be641f355..25e783df5e6b 100644 --- a/tests/components/html5/test_event.py +++ b/tests/components/html5/test_event.py @@ -9,12 +9,13 @@ from aiohttp.hdrs import AUTHORIZATION import pytest from syrupy.assertion import SnapshotAssertion +from homeassistant.components.html5.const import DOMAIN from homeassistant.components.html5.notify import ATTR_ACTION, ATTR_TAG, ATTR_TYPE from homeassistant.components.notify import ATTR_DATA, ATTR_TARGET from homeassistant.config_entries import ConfigEntryState from homeassistant.const import STATE_UNKNOWN, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import entity_registry as er, issue_registry as ir from homeassistant.setup import async_setup_component from .test_notify import SUBSCRIPTION_1 @@ -118,3 +119,82 @@ async def test_events( assert state.attributes.get("action") == event_payload.get(ATTR_ACTION) assert state.attributes.get("tag") == event_payload[ATTR_TAG] assert state.attributes.get("customKey") == event_payload[ATTR_DATA]["customKey"] + + +@pytest.mark.parametrize("event_type", ["clicked", "received", "closed"]) +@pytest.mark.usefixtures("mock_wp", "mock_jwt", "mock_vapid", "mock_uuid") +async def test_deprecation_event_bus( + hass: HomeAssistant, + config_entry: MockConfigEntry, + load_config: MagicMock, + issue_registry: ir.IssueRegistry, + hass_client: ClientSessionGenerator, + event_type: str, +) -> None: + """Test deprecation of events on the event bus.""" + load_config.return_value = {"device": SUBSCRIPTION_1} + await async_setup_component(hass, "http", {}) + + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + + config_entry.async_on_unload( + hass.bus.async_listen(f"html5_notification.{event_type}", lambda _: None) + ) + client = await hass_client() + + resp = await client.post( + "/api/notify.html5/callback", + json={"type": event_type, "tag": "12345", "target": "device"}, + headers={AUTHORIZATION: "Bearer JWT"}, + ) + + assert resp.status == HTTPStatus.OK + body = await resp.json() + assert body == {"event": event_type, "status": "ok"} + + assert issue_registry.async_get_issue( + domain=DOMAIN, + issue_id=f"deprecated_event_bus_html5_notification.{event_type}", + ) + + +@pytest.mark.parametrize("event_type", ["clicked", "received", "closed"]) +@pytest.mark.usefixtures("mock_wp", "mock_jwt", "mock_vapid", "mock_uuid") +async def test_deprecation_event_bus_no_listeners( + hass: HomeAssistant, + config_entry: MockConfigEntry, + load_config: MagicMock, + issue_registry: ir.IssueRegistry, + hass_client: ClientSessionGenerator, + event_type: str, +) -> None: + """Test no issue is created when there are no listeners.""" + load_config.return_value = {"device": SUBSCRIPTION_1} + await async_setup_component(hass, "http", {}) + + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + assert config_entry.state is ConfigEntryState.LOADED + + client = await hass_client() + + resp = await client.post( + "/api/notify.html5/callback", + json={"type": event_type, "tag": "12345", "target": "device"}, + headers={AUTHORIZATION: "Bearer JWT"}, + ) + + assert resp.status == HTTPStatus.OK + body = await resp.json() + assert body == {"event": event_type, "status": "ok"} + + assert not issue_registry.async_get_issue( + domain=DOMAIN, + issue_id=f"deprecated_event_bus_html5_notification.{event_type}", + )