1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-25 05:26:47 +00:00

Fix bug in group notify entities when title is missing (#157171)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
hanwg
2025-12-03 16:44:01 +08:00
committed by Franck Nijhof
parent 738fb59efa
commit a00b50c195
2 changed files with 189 additions and 19 deletions

View File

@@ -18,10 +18,12 @@ from homeassistant.components.notify import (
SERVICE_SEND_MESSAGE,
BaseNotificationService,
NotifyEntity,
NotifyEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
CONF_ACTION,
CONF_ENTITIES,
CONF_SERVICE,
@@ -173,14 +175,23 @@ class NotifyGroup(GroupEntity, NotifyEntity):
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message to all members of the group."""
data = {
ATTR_MESSAGE: message,
ATTR_ENTITY_ID: self._entity_ids,
}
# add title only if supported and provided
if (
title is not None
and self._attr_supported_features & NotifyEntityFeature.TITLE
):
data[ATTR_TITLE] = title
await self.hass.services.async_call(
NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
{
ATTR_MESSAGE: message,
ATTR_TITLE: title,
ATTR_ENTITY_ID: self._entity_ids,
},
data,
blocking=True,
context=self._context,
)
@@ -194,3 +205,15 @@ class NotifyGroup(GroupEntity, NotifyEntity):
for entity_id in self._entity_ids
if (state := self.hass.states.get(entity_id)) is not None
)
# Support title if all members support it
self._attr_supported_features |= NotifyEntityFeature.TITLE
for entity_id in self._entity_ids:
state = self.hass.states.get(entity_id)
if (
state is None
or not state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
& NotifyEntityFeature.TITLE
):
self._attr_supported_features &= ~NotifyEntityFeature.TITLE
break

View File

@@ -16,6 +16,7 @@ from homeassistant.components.notify import (
DOMAIN as NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
NotifyEntity,
NotifyEntityFeature,
)
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import (
@@ -298,9 +299,11 @@ def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
class MockNotifyEntity(MockEntity, NotifyEntity):
"""Mock Email notifier entity to use in tests."""
def __init__(self, **values: Any) -> None:
def __init__(self, *, is_title_supported: bool, **values: Any) -> None:
"""Initialize the mock entity."""
super().__init__(**values)
if is_title_supported:
self._attr_supported_features = NotifyEntityFeature.TITLE
self.send_message_mock_calls = MagicMock()
async def async_send_message(self, message: str, title: str | None = None) -> None:
@@ -330,11 +333,21 @@ async def help_async_unload_entry(
@pytest.fixture
async def mock_notifiers(
hass: HomeAssistant, config_flow_fixture: None
) -> list[NotifyEntity]:
) -> list[MockNotifyEntity]:
"""Set up the notify entities."""
entity = MockNotifyEntity(name="test", entity_id="notify.test")
entity2 = MockNotifyEntity(name="test2", entity_id="notify.test2")
entities = [entity, entity2]
entity_title_1 = MockNotifyEntity(
is_title_supported=True, name="has_title_1", entity_id="notify.has_title_1"
)
entity_title_2 = MockNotifyEntity(
is_title_supported=True, name="has_title_2", entity_id="notify.has_title_2"
)
entity_no_title_1 = MockNotifyEntity(
is_title_supported=False, name="no_title_1", entity_id="notify.no_title_1"
)
entity_no_title_2 = MockNotifyEntity(
is_title_supported=False, name="no_title_2", entity_id="notify.no_title_2"
)
entities = [entity_title_1, entity_title_2, entity_no_title_1, entity_no_title_2]
test_entry = MockConfigEntry(domain="test")
test_entry.add_to_hass(hass)
mock_integration(
@@ -352,19 +365,23 @@ async def mock_notifiers(
async def test_notify_entity_group(
hass: HomeAssistant, mock_notifiers: list[NotifyEntity]
hass: HomeAssistant, mock_notifiers: list[MockNotifyEntity]
) -> None:
"""Test sending a message to a notify group."""
entity, entity2 = mock_notifiers
assert entity.send_message_mock_calls.call_count == 0
assert entity2.send_message_mock_calls.call_count == 0
entity_title_1, entity_title_2, entity_no_title_1, entity_no_title_2 = (
mock_notifiers
)
for mock_notifier in mock_notifiers:
assert mock_notifier.send_message_mock_calls.call_count == 0
# test group containing 1 member with title supported
config_entry = MockConfigEntry(
domain=DOMAIN,
options={
"group_type": "notify",
"name": "Test Group",
"entities": ["notify.test", "notify.test2"],
"entities": ["notify.has_title_1"],
"hide_members": True,
},
title="Test Group",
@@ -384,14 +401,144 @@ async def test_notify_entity_group(
blocking=True,
)
assert entity.send_message_mock_calls.call_count == 1
assert entity.send_message_mock_calls.call_args == call(
assert entity_title_1.send_message_mock_calls.call_count == 1
assert entity_title_1.send_message_mock_calls.call_args == call(
"Hello", title="Test notification"
)
assert entity2.send_message_mock_calls.call_count == 1
assert entity2.send_message_mock_calls.call_args == call(
for mock_notifier in mock_notifiers:
mock_notifier.send_message_mock_calls.reset_mock()
# test group containing 1 member with title supported but no title provided
await hass.services.async_call(
NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
{
ATTR_MESSAGE: "Hello",
ATTR_ENTITY_ID: "notify.test_group",
},
blocking=True,
)
assert entity_title_1.send_message_mock_calls.call_count == 1
assert entity_title_1.send_message_mock_calls.call_args == call("Hello", title=None)
for mock_notifier in mock_notifiers:
mock_notifier.send_message_mock_calls.reset_mock()
# test group containing 2 members with title supported
config_entry = MockConfigEntry(
domain=DOMAIN,
options={
"group_type": "notify",
"name": "Test Group 2",
"entities": ["notify.has_title_1", "notify.has_title_2"],
"hide_members": True,
},
title="Test Group 2",
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
{
ATTR_MESSAGE: "Hello",
ATTR_TITLE: "Test notification",
ATTR_ENTITY_ID: "notify.test_group_2",
},
blocking=True,
)
assert entity_title_1.send_message_mock_calls.call_count == 1
assert entity_title_1.send_message_mock_calls.call_args == call(
"Hello", title="Test notification"
)
assert entity_title_2.send_message_mock_calls.call_count == 1
assert entity_title_2.send_message_mock_calls.call_args == call(
"Hello", title="Test notification"
)
for mock_notifier in mock_notifiers:
mock_notifier.send_message_mock_calls.reset_mock()
# test group containing 2 members: 1 title supported and 1 not supported
# title is not supported since not all members support it
config_entry = MockConfigEntry(
domain=DOMAIN,
options={
"group_type": "notify",
"name": "Test Group",
"entities": ["notify.has_title_1", "notify.no_title_1"],
"hide_members": True,
},
title="Test Group 3",
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
{
ATTR_MESSAGE: "Hello",
ATTR_TITLE: "Test notification",
ATTR_ENTITY_ID: "notify.test_group_3",
},
blocking=True,
)
assert entity_title_1.send_message_mock_calls.call_count == 1
assert entity_title_1.send_message_mock_calls.call_args == call("Hello", title=None)
assert entity_no_title_1.send_message_mock_calls.call_count == 1
assert entity_no_title_1.send_message_mock_calls.call_args == call(
"Hello", title=None
)
for mock_notifier in mock_notifiers:
mock_notifier.send_message_mock_calls.reset_mock()
# test group containing 2 members: both not supporting title
config_entry = MockConfigEntry(
domain=DOMAIN,
options={
"group_type": "notify",
"name": "Test Group",
"entities": ["notify.no_title_1", "notify.no_title_2"],
"hide_members": True,
},
title="Test Group 4",
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await hass.services.async_call(
NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
{
ATTR_MESSAGE: "Hello",
ATTR_TITLE: "Test notification",
ATTR_ENTITY_ID: "notify.test_group_4",
},
blocking=True,
)
assert entity_no_title_1.send_message_mock_calls.call_count == 1
assert entity_no_title_1.send_message_mock_calls.call_args == call(
"Hello", title=None
)
assert entity_no_title_2.send_message_mock_calls.call_count == 1
assert entity_no_title_2.send_message_mock_calls.call_args == call(
"Hello", title=None
)
async def test_state_reporting(hass: HomeAssistant) -> None: