Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions homeassistant/components/group/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
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,
Expand Down Expand Up @@ -173,14 +175,23 @@ def __init__(

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,
)
Expand All @@ -194,3 +205,15 @@ def async_update_group_state(self) -> None:
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
175 changes: 161 additions & 14 deletions tests/components/group/test_notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
DOMAIN as NOTIFY_DOMAIN,
SERVICE_SEND_MESSAGE,
NotifyEntity,
NotifyEntityFeature,
)
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import (
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand All @@ -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",
Expand All @@ -384,15 +401,145 @@ 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"
)

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 entity2.send_message_mock_calls.call_count == 1
assert entity2.send_message_mock_calls.call_args == call(
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:
"""Test sending a message to a notify group."""
Expand Down
Loading