Skip to content

Commit fa6d7d5

Browse files
Diegorro98bramkragten
authored andcommitted
Fix fetch options error for Home connect (#139392)
* Handle errors when obtaining options definitions * Don't fetch program options if the program key is unknown * Test to ensure that available program endpoint is not called on unknown program
1 parent 585b950 commit fa6d7d5

File tree

3 files changed

+113
-13
lines changed

3 files changed

+113
-13
lines changed

homeassistant/components/home_connect/coordinator.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -440,13 +440,27 @@ async def get_options_definitions(
440440
self, ha_id: str, program_key: ProgramKey
441441
) -> dict[OptionKey, ProgramDefinitionOption]:
442442
"""Get options with constraints for appliance."""
443-
return {
444-
option.key: option
445-
for option in (
446-
await self.client.get_available_program(ha_id, program_key=program_key)
447-
).options
448-
or []
449-
}
443+
if program_key is ProgramKey.UNKNOWN:
444+
return {}
445+
try:
446+
return {
447+
option.key: option
448+
for option in (
449+
await self.client.get_available_program(
450+
ha_id, program_key=program_key
451+
)
452+
).options
453+
or []
454+
}
455+
except HomeConnectError as error:
456+
_LOGGER.debug(
457+
"Error fetching options for %s: %s",
458+
ha_id,
459+
error
460+
if isinstance(error, HomeConnectApiError)
461+
else type(error).__name__,
462+
)
463+
return {}
450464

451465
async def update_options(
452466
self, ha_id: str, event_key: EventKey, program_key: ProgramKey
@@ -456,8 +470,7 @@ async def update_options(
456470
events = self.data[ha_id].events
457471
options_to_notify = options.copy()
458472
options.clear()
459-
if program_key is not ProgramKey.UNKNOWN:
460-
options.update(await self.get_options_definitions(ha_id, program_key))
473+
options.update(await self.get_options_definitions(ha_id, program_key))
461474

462475
for option in options.values():
463476
option_value = option.constraints.default if option.constraints else None

tests/components/home_connect/test_coordinator.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -75,21 +75,35 @@ async def test_coordinator_update_failing_get_appliances(
7575
assert config_entry.state == ConfigEntryState.SETUP_RETRY
7676

7777

78-
async def test_coordinator_update_failing_get_settings_status(
78+
@pytest.mark.parametrize(
79+
"mock_method",
80+
[
81+
"get_settings",
82+
"get_status",
83+
"get_all_programs",
84+
"get_available_commands",
85+
"get_available_program",
86+
],
87+
)
88+
async def test_coordinator_update_failing(
89+
mock_method: str,
7990
config_entry: MockConfigEntry,
8091
integration_setup: Callable[[MagicMock], Awaitable[bool]],
8192
setup_credentials: None,
82-
client_with_exception: MagicMock,
93+
client: MagicMock,
8394
) -> None:
8495
"""Test that although is not possible to get settings and status, the config entry is loaded.
8596
8697
This is for cases where some appliances are reachable and some are not in the same configuration entry.
8798
"""
88-
# Get home appliances does pass at client_with_exception.get_home_appliances mock, so no need to mock it again
99+
setattr(client, mock_method, AsyncMock(side_effect=HomeConnectError()))
100+
89101
assert config_entry.state == ConfigEntryState.NOT_LOADED
90-
await integration_setup(client_with_exception)
102+
await integration_setup(client)
91103
assert config_entry.state == ConfigEntryState.LOADED
92104

105+
getattr(client, mock_method).assert_called()
106+
93107

94108
@pytest.mark.parametrize("appliance_ha_id", ["Dishwasher"], indirect=True)
95109
@pytest.mark.parametrize(

tests/components/home_connect/test_entity.py

+73
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
SelectedProgramNotSetError,
2424
)
2525
from aiohomeconnect.model.program import (
26+
EnumerateProgram,
2627
ProgramDefinitionConstraints,
2728
ProgramDefinitionOption,
2829
)
@@ -234,6 +235,78 @@ async def get_all_programs_with_options_mock(ha_id: str) -> ArrayOfPrograms:
234235
assert hass.states.is_state(entity_id, STATE_UNKNOWN)
235236

236237

238+
@pytest.mark.parametrize(
239+
("array_of_programs_program_arg", "event_key"),
240+
[
241+
(
242+
"active",
243+
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
244+
),
245+
(
246+
"selected",
247+
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
248+
),
249+
],
250+
)
251+
async def test_no_options_retrieval_on_unknown_program(
252+
array_of_programs_program_arg: str,
253+
event_key: EventKey,
254+
appliance_ha_id: str,
255+
hass: HomeAssistant,
256+
config_entry: MockConfigEntry,
257+
integration_setup: Callable[[MagicMock], Awaitable[bool]],
258+
setup_credentials: None,
259+
client: MagicMock,
260+
) -> None:
261+
"""Test that no options are retrieved when the program is unknown."""
262+
263+
async def get_all_programs_with_options_mock(ha_id: str) -> ArrayOfPrograms:
264+
return ArrayOfPrograms(
265+
**(
266+
{
267+
"programs": [
268+
EnumerateProgram(ProgramKey.UNKNOWN, "unknown program")
269+
],
270+
array_of_programs_program_arg: Program(
271+
ProgramKey.UNKNOWN, options=[]
272+
),
273+
}
274+
)
275+
)
276+
277+
client.get_all_programs = AsyncMock(side_effect=get_all_programs_with_options_mock)
278+
279+
assert config_entry.state == ConfigEntryState.NOT_LOADED
280+
assert await integration_setup(client)
281+
assert config_entry.state == ConfigEntryState.LOADED
282+
283+
assert client.get_available_program.call_count == 0
284+
285+
await client.add_events(
286+
[
287+
EventMessage(
288+
appliance_ha_id,
289+
EventType.NOTIFY,
290+
data=ArrayOfEvents(
291+
[
292+
Event(
293+
key=event_key,
294+
raw_key=event_key.value,
295+
timestamp=0,
296+
level="",
297+
handling="",
298+
value=ProgramKey.UNKNOWN,
299+
)
300+
]
301+
),
302+
)
303+
]
304+
)
305+
await hass.async_block_till_done()
306+
307+
assert client.get_available_program.call_count == 0
308+
309+
237310
@pytest.mark.parametrize(
238311
"event_key",
239312
[

0 commit comments

Comments
 (0)