Skip to content

Commit a00b50c

Browse files
hanwgMartinHjelmare
authored andcommitted
Fix bug in group notify entities when title is missing (home-assistant#157171)
Co-authored-by: Martin Hjelmare <[email protected]>
1 parent 738fb59 commit a00b50c

File tree

2 files changed

+189
-19
lines changed

2 files changed

+189
-19
lines changed

homeassistant/components/group/notify.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
SERVICE_SEND_MESSAGE,
1919
BaseNotificationService,
2020
NotifyEntity,
21+
NotifyEntityFeature,
2122
)
2223
from homeassistant.config_entries import ConfigEntry
2324
from homeassistant.const import (
2425
ATTR_ENTITY_ID,
26+
ATTR_SUPPORTED_FEATURES,
2527
CONF_ACTION,
2628
CONF_ENTITIES,
2729
CONF_SERVICE,
@@ -173,14 +175,23 @@ def __init__(
173175

174176
async def async_send_message(self, message: str, title: str | None = None) -> None:
175177
"""Send a message to all members of the group."""
178+
179+
data = {
180+
ATTR_MESSAGE: message,
181+
ATTR_ENTITY_ID: self._entity_ids,
182+
}
183+
184+
# add title only if supported and provided
185+
if (
186+
title is not None
187+
and self._attr_supported_features & NotifyEntityFeature.TITLE
188+
):
189+
data[ATTR_TITLE] = title
190+
176191
await self.hass.services.async_call(
177192
NOTIFY_DOMAIN,
178193
SERVICE_SEND_MESSAGE,
179-
{
180-
ATTR_MESSAGE: message,
181-
ATTR_TITLE: title,
182-
ATTR_ENTITY_ID: self._entity_ids,
183-
},
194+
data,
184195
blocking=True,
185196
context=self._context,
186197
)
@@ -194,3 +205,15 @@ def async_update_group_state(self) -> None:
194205
for entity_id in self._entity_ids
195206
if (state := self.hass.states.get(entity_id)) is not None
196207
)
208+
209+
# Support title if all members support it
210+
self._attr_supported_features |= NotifyEntityFeature.TITLE
211+
for entity_id in self._entity_ids:
212+
state = self.hass.states.get(entity_id)
213+
if (
214+
state is None
215+
or not state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
216+
& NotifyEntityFeature.TITLE
217+
):
218+
self._attr_supported_features &= ~NotifyEntityFeature.TITLE
219+
break

tests/components/group/test_notify.py

Lines changed: 161 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DOMAIN as NOTIFY_DOMAIN,
1717
SERVICE_SEND_MESSAGE,
1818
NotifyEntity,
19+
NotifyEntityFeature,
1920
)
2021
from homeassistant.config_entries import ConfigEntry, ConfigFlow
2122
from homeassistant.const import (
@@ -298,9 +299,11 @@ def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
298299
class MockNotifyEntity(MockEntity, NotifyEntity):
299300
"""Mock Email notifier entity to use in tests."""
300301

301-
def __init__(self, **values: Any) -> None:
302+
def __init__(self, *, is_title_supported: bool, **values: Any) -> None:
302303
"""Initialize the mock entity."""
303304
super().__init__(**values)
305+
if is_title_supported:
306+
self._attr_supported_features = NotifyEntityFeature.TITLE
304307
self.send_message_mock_calls = MagicMock()
305308

306309
async def async_send_message(self, message: str, title: str | None = None) -> None:
@@ -330,11 +333,21 @@ async def help_async_unload_entry(
330333
@pytest.fixture
331334
async def mock_notifiers(
332335
hass: HomeAssistant, config_flow_fixture: None
333-
) -> list[NotifyEntity]:
336+
) -> list[MockNotifyEntity]:
334337
"""Set up the notify entities."""
335-
entity = MockNotifyEntity(name="test", entity_id="notify.test")
336-
entity2 = MockNotifyEntity(name="test2", entity_id="notify.test2")
337-
entities = [entity, entity2]
338+
entity_title_1 = MockNotifyEntity(
339+
is_title_supported=True, name="has_title_1", entity_id="notify.has_title_1"
340+
)
341+
entity_title_2 = MockNotifyEntity(
342+
is_title_supported=True, name="has_title_2", entity_id="notify.has_title_2"
343+
)
344+
entity_no_title_1 = MockNotifyEntity(
345+
is_title_supported=False, name="no_title_1", entity_id="notify.no_title_1"
346+
)
347+
entity_no_title_2 = MockNotifyEntity(
348+
is_title_supported=False, name="no_title_2", entity_id="notify.no_title_2"
349+
)
350+
entities = [entity_title_1, entity_title_2, entity_no_title_1, entity_no_title_2]
338351
test_entry = MockConfigEntry(domain="test")
339352
test_entry.add_to_hass(hass)
340353
mock_integration(
@@ -352,19 +365,23 @@ async def mock_notifiers(
352365

353366

354367
async def test_notify_entity_group(
355-
hass: HomeAssistant, mock_notifiers: list[NotifyEntity]
368+
hass: HomeAssistant, mock_notifiers: list[MockNotifyEntity]
356369
) -> None:
357370
"""Test sending a message to a notify group."""
358-
entity, entity2 = mock_notifiers
359-
assert entity.send_message_mock_calls.call_count == 0
360-
assert entity2.send_message_mock_calls.call_count == 0
371+
entity_title_1, entity_title_2, entity_no_title_1, entity_no_title_2 = (
372+
mock_notifiers
373+
)
374+
for mock_notifier in mock_notifiers:
375+
assert mock_notifier.send_message_mock_calls.call_count == 0
376+
377+
# test group containing 1 member with title supported
361378

362379
config_entry = MockConfigEntry(
363380
domain=DOMAIN,
364381
options={
365382
"group_type": "notify",
366383
"name": "Test Group",
367-
"entities": ["notify.test", "notify.test2"],
384+
"entities": ["notify.has_title_1"],
368385
"hide_members": True,
369386
},
370387
title="Test Group",
@@ -384,15 +401,145 @@ async def test_notify_entity_group(
384401
blocking=True,
385402
)
386403

387-
assert entity.send_message_mock_calls.call_count == 1
388-
assert entity.send_message_mock_calls.call_args == call(
404+
assert entity_title_1.send_message_mock_calls.call_count == 1
405+
assert entity_title_1.send_message_mock_calls.call_args == call(
406+
"Hello", title="Test notification"
407+
)
408+
409+
for mock_notifier in mock_notifiers:
410+
mock_notifier.send_message_mock_calls.reset_mock()
411+
412+
# test group containing 1 member with title supported but no title provided
413+
414+
await hass.services.async_call(
415+
NOTIFY_DOMAIN,
416+
SERVICE_SEND_MESSAGE,
417+
{
418+
ATTR_MESSAGE: "Hello",
419+
ATTR_ENTITY_ID: "notify.test_group",
420+
},
421+
blocking=True,
422+
)
423+
424+
assert entity_title_1.send_message_mock_calls.call_count == 1
425+
assert entity_title_1.send_message_mock_calls.call_args == call("Hello", title=None)
426+
427+
for mock_notifier in mock_notifiers:
428+
mock_notifier.send_message_mock_calls.reset_mock()
429+
430+
# test group containing 2 members with title supported
431+
432+
config_entry = MockConfigEntry(
433+
domain=DOMAIN,
434+
options={
435+
"group_type": "notify",
436+
"name": "Test Group 2",
437+
"entities": ["notify.has_title_1", "notify.has_title_2"],
438+
"hide_members": True,
439+
},
440+
title="Test Group 2",
441+
)
442+
config_entry.add_to_hass(hass)
443+
await hass.config_entries.async_setup(config_entry.entry_id)
444+
await hass.async_block_till_done()
445+
446+
await hass.services.async_call(
447+
NOTIFY_DOMAIN,
448+
SERVICE_SEND_MESSAGE,
449+
{
450+
ATTR_MESSAGE: "Hello",
451+
ATTR_TITLE: "Test notification",
452+
ATTR_ENTITY_ID: "notify.test_group_2",
453+
},
454+
blocking=True,
455+
)
456+
457+
assert entity_title_1.send_message_mock_calls.call_count == 1
458+
assert entity_title_1.send_message_mock_calls.call_args == call(
389459
"Hello", title="Test notification"
390460
)
391-
assert entity2.send_message_mock_calls.call_count == 1
392-
assert entity2.send_message_mock_calls.call_args == call(
461+
assert entity_title_2.send_message_mock_calls.call_count == 1
462+
assert entity_title_2.send_message_mock_calls.call_args == call(
393463
"Hello", title="Test notification"
394464
)
395465

466+
for mock_notifier in mock_notifiers:
467+
mock_notifier.send_message_mock_calls.reset_mock()
468+
469+
# test group containing 2 members: 1 title supported and 1 not supported
470+
# title is not supported since not all members support it
471+
472+
config_entry = MockConfigEntry(
473+
domain=DOMAIN,
474+
options={
475+
"group_type": "notify",
476+
"name": "Test Group",
477+
"entities": ["notify.has_title_1", "notify.no_title_1"],
478+
"hide_members": True,
479+
},
480+
title="Test Group 3",
481+
)
482+
config_entry.add_to_hass(hass)
483+
await hass.config_entries.async_setup(config_entry.entry_id)
484+
await hass.async_block_till_done()
485+
486+
await hass.services.async_call(
487+
NOTIFY_DOMAIN,
488+
SERVICE_SEND_MESSAGE,
489+
{
490+
ATTR_MESSAGE: "Hello",
491+
ATTR_TITLE: "Test notification",
492+
ATTR_ENTITY_ID: "notify.test_group_3",
493+
},
494+
blocking=True,
495+
)
496+
497+
assert entity_title_1.send_message_mock_calls.call_count == 1
498+
assert entity_title_1.send_message_mock_calls.call_args == call("Hello", title=None)
499+
assert entity_no_title_1.send_message_mock_calls.call_count == 1
500+
assert entity_no_title_1.send_message_mock_calls.call_args == call(
501+
"Hello", title=None
502+
)
503+
504+
for mock_notifier in mock_notifiers:
505+
mock_notifier.send_message_mock_calls.reset_mock()
506+
507+
# test group containing 2 members: both not supporting title
508+
509+
config_entry = MockConfigEntry(
510+
domain=DOMAIN,
511+
options={
512+
"group_type": "notify",
513+
"name": "Test Group",
514+
"entities": ["notify.no_title_1", "notify.no_title_2"],
515+
"hide_members": True,
516+
},
517+
title="Test Group 4",
518+
)
519+
config_entry.add_to_hass(hass)
520+
await hass.config_entries.async_setup(config_entry.entry_id)
521+
await hass.async_block_till_done()
522+
523+
await hass.services.async_call(
524+
NOTIFY_DOMAIN,
525+
SERVICE_SEND_MESSAGE,
526+
{
527+
ATTR_MESSAGE: "Hello",
528+
ATTR_TITLE: "Test notification",
529+
ATTR_ENTITY_ID: "notify.test_group_4",
530+
},
531+
blocking=True,
532+
)
533+
534+
assert entity_no_title_1.send_message_mock_calls.call_count == 1
535+
assert entity_no_title_1.send_message_mock_calls.call_args == call(
536+
"Hello", title=None
537+
)
538+
assert entity_no_title_2.send_message_mock_calls.call_count == 1
539+
assert entity_no_title_2.send_message_mock_calls.call_args == call(
540+
"Hello", title=None
541+
)
542+
396543

397544
async def test_state_reporting(hass: HomeAssistant) -> None:
398545
"""Test sending a message to a notify group."""

0 commit comments

Comments
 (0)