Skip to content

Commit 872fef1

Browse files
authored
Add reconfigure flow to SFR Box (home-assistant#157711)
1 parent c866dc9 commit 872fef1

File tree

4 files changed

+213
-9
lines changed

4 files changed

+213
-9
lines changed

homeassistant/components/sfr_box/config_flow.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
from sfrbox_api.exceptions import SFRBoxAuthenticationError, SFRBoxError
1111
import voluptuous as vol
1212

13-
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
13+
from homeassistant.config_entries import (
14+
SOURCE_REAUTH,
15+
SOURCE_RECONFIGURE,
16+
ConfigFlow,
17+
ConfigFlowResult,
18+
)
1419
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
1520
from homeassistant.helpers import selector
1621
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -63,12 +68,19 @@ async def async_step_user(
6368
if TYPE_CHECKING:
6469
assert system_info is not None
6570
await self.async_set_unique_id(system_info.mac_addr)
66-
self._abort_if_unique_id_configured()
71+
if self.source == SOURCE_RECONFIGURE:
72+
self._abort_if_unique_id_mismatch()
73+
else:
74+
self._abort_if_unique_id_configured()
6775
self._box = box
6876
self._config.update(user_input)
6977
return await self.async_step_choose_auth()
7078

71-
data_schema = self.add_suggested_values_to_schema(DATA_SCHEMA, user_input)
79+
suggested_values: Mapping[str, Any] | None = user_input
80+
if suggested_values is None and self.source == SOURCE_RECONFIGURE:
81+
suggested_values = self._get_reconfigure_entry().data
82+
data_schema = self.add_suggested_values_to_schema(DATA_SCHEMA, suggested_values)
83+
7284
return self.async_show_form(
7385
step_id="user",
7486
data_schema=data_schema,
@@ -107,11 +119,18 @@ async def async_step_auth(
107119
self._get_reauth_entry(), data_updates=user_input
108120
)
109121
self._config.update(user_input)
122+
if self.source == SOURCE_RECONFIGURE:
123+
return self.async_update_reload_and_abort(
124+
self._get_reconfigure_entry(), data=self._config
125+
)
110126
return self.async_create_entry(title="SFR Box", data=self._config)
111127

112128
suggested_values: Mapping[str, Any] | None = user_input
113-
if self.source == SOURCE_REAUTH and not suggested_values:
114-
suggested_values = self._get_reauth_entry().data
129+
if suggested_values is None:
130+
if self.source == SOURCE_REAUTH:
131+
suggested_values = self._get_reauth_entry().data
132+
elif self.source == SOURCE_RECONFIGURE:
133+
suggested_values = self._get_reconfigure_entry().data
115134

116135
data_schema = self.add_suggested_values_to_schema(AUTH_SCHEMA, suggested_values)
117136
return self.async_show_form(
@@ -122,6 +141,10 @@ async def async_step_skip_auth(
122141
self, user_input: dict[str, str] | None = None
123142
) -> ConfigFlowResult:
124143
"""Skip authentication."""
144+
if self.source == SOURCE_RECONFIGURE:
145+
return self.async_update_reload_and_abort(
146+
self._get_reconfigure_entry(), data=self._config
147+
)
125148
return self.async_create_entry(title="SFR Box", data=self._config)
126149

127150
async def async_step_reauth(
@@ -132,3 +155,9 @@ async def async_step_reauth(
132155
ip=entry_data[CONF_HOST], client=async_get_clientsession(self.hass)
133156
)
134157
return await self.async_step_auth()
158+
159+
async def async_step_reconfigure(
160+
self, user_input: Mapping[str, Any]
161+
) -> ConfigFlowResult:
162+
"""Handle reconfiguration."""
163+
return await self.async_step_user()

homeassistant/components/sfr_box/quality_scale.yaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ rules:
5858
status: todo
5959
comment: not yet documented
6060
icon-translations: done
61-
reconfiguration-flow:
62-
status: todo
63-
comment: Need to be able to manually change the IP address
61+
reconfiguration-flow: done
6462
dynamic-devices: done
6563
discovery-update-info:
6664
status: todo

homeassistant/components/sfr_box/strings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
"config": {
33
"abort": {
44
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
5-
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
5+
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
6+
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
7+
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
68
},
79
"error": {
810
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",

tests/components/sfr_box/test_config_flow.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,178 @@ async def test_reauth(hass: HomeAssistant, config_entry_with_auth: ConfigEntry)
352352

353353
assert result.get("type") is FlowResultType.ABORT
354354
assert result.get("reason") == "reauth_successful"
355+
356+
357+
async def test_reconfigure_host(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
358+
"""Test reconfigure host on a simple (no-auth) entry."""
359+
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
360+
361+
result = await config_entry.start_reconfigure_flow(hass)
362+
363+
assert result.get("type") is FlowResultType.FORM
364+
assert result.get("step_id") == "user"
365+
assert result.get("errors") == {}
366+
367+
assert config_entry.data[CONF_HOST] == "192.168.0.1"
368+
with patch(
369+
"homeassistant.components.sfr_box.config_flow.SFRBox.system_get_info",
370+
return_value=SystemInfo(
371+
**(
372+
await async_load_json_object_fixture(
373+
hass, "system_getInfo.json", DOMAIN
374+
)
375+
)
376+
),
377+
):
378+
result = await hass.config_entries.flow.async_configure(
379+
result["flow_id"],
380+
user_input={
381+
CONF_HOST: "192.168.0.100",
382+
},
383+
)
384+
385+
assert result.get("type") is FlowResultType.MENU
386+
assert result.get("step_id") == "choose_auth"
387+
388+
result = await hass.config_entries.flow.async_configure(
389+
result["flow_id"],
390+
{"next_step_id": "skip_auth"},
391+
)
392+
393+
assert result.get("type") is FlowResultType.ABORT
394+
assert result.get("reason") == "reconfigure_successful"
395+
assert config_entry.data == {CONF_HOST: "192.168.0.100"}
396+
397+
398+
async def test_reconfigure_add_auth(
399+
hass: HomeAssistant, config_entry: ConfigEntry
400+
) -> None:
401+
"""Test reconfigure able to add authentication on a simple (no-auth) entry."""
402+
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
403+
404+
result = await config_entry.start_reconfigure_flow(hass)
405+
406+
assert result.get("type") is FlowResultType.FORM
407+
assert result.get("step_id") == "user"
408+
assert result.get("errors") == {}
409+
410+
assert CONF_USERNAME not in config_entry.data
411+
with patch(
412+
"homeassistant.components.sfr_box.config_flow.SFRBox.system_get_info",
413+
return_value=SystemInfo(
414+
**(
415+
await async_load_json_object_fixture(
416+
hass, "system_getInfo.json", DOMAIN
417+
)
418+
)
419+
),
420+
):
421+
result = await hass.config_entries.flow.async_configure(
422+
result["flow_id"],
423+
user_input={
424+
CONF_HOST: "192.168.0.1",
425+
},
426+
)
427+
428+
assert result.get("type") is FlowResultType.MENU
429+
assert result.get("step_id") == "choose_auth"
430+
431+
result = await hass.config_entries.flow.async_configure(
432+
result["flow_id"],
433+
{"next_step_id": "auth"},
434+
)
435+
436+
with patch("homeassistant.components.sfr_box.config_flow.SFRBox.authenticate"):
437+
result = await hass.config_entries.flow.async_configure(
438+
result["flow_id"],
439+
user_input={
440+
CONF_USERNAME: "admin",
441+
CONF_PASSWORD: "valid",
442+
},
443+
)
444+
445+
assert result.get("type") is FlowResultType.ABORT
446+
assert result.get("reason") == "reconfigure_successful"
447+
assert config_entry.data == {
448+
CONF_HOST: "192.168.0.1",
449+
CONF_USERNAME: "admin",
450+
CONF_PASSWORD: "valid",
451+
}
452+
453+
454+
async def test_reconfigure_clear_auth(
455+
hass: HomeAssistant, config_entry_with_auth: ConfigEntry
456+
) -> None:
457+
"""Test reconfigure clears authentication on an entry with auth."""
458+
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
459+
460+
result = await config_entry_with_auth.start_reconfigure_flow(hass)
461+
462+
assert result.get("type") is FlowResultType.FORM
463+
assert result.get("step_id") == "user"
464+
assert result.get("errors") == {}
465+
466+
assert config_entry_with_auth.data[CONF_USERNAME] == "admin"
467+
with patch(
468+
"homeassistant.components.sfr_box.config_flow.SFRBox.system_get_info",
469+
return_value=SystemInfo(
470+
**(
471+
await async_load_json_object_fixture(
472+
hass, "system_getInfo.json", DOMAIN
473+
)
474+
| {"mac_addr": "e4:5d:51:00:11:23"}
475+
)
476+
),
477+
):
478+
result = await hass.config_entries.flow.async_configure(
479+
result["flow_id"],
480+
user_input={
481+
CONF_HOST: "192.168.0.1",
482+
},
483+
)
484+
485+
assert result.get("type") is FlowResultType.MENU
486+
assert result.get("step_id") == "choose_auth"
487+
488+
result = await hass.config_entries.flow.async_configure(
489+
result["flow_id"],
490+
{"next_step_id": "skip_auth"},
491+
)
492+
493+
assert result.get("type") is FlowResultType.ABORT
494+
assert result.get("reason") == "reconfigure_successful"
495+
assert CONF_USERNAME not in config_entry_with_auth.data
496+
497+
498+
async def test_reconfigure_mismatch(
499+
hass: HomeAssistant, config_entry_with_auth: ConfigEntry
500+
) -> None:
501+
"""Test reconfigure fails if the unique ID (=MAC) does not match."""
502+
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
503+
504+
result = await config_entry_with_auth.start_reconfigure_flow(hass)
505+
506+
assert result.get("type") is FlowResultType.FORM
507+
assert result.get("step_id") == "user"
508+
assert result.get("errors") == {}
509+
510+
assert config_entry_with_auth.data[CONF_USERNAME] == "admin"
511+
with patch(
512+
"homeassistant.components.sfr_box.config_flow.SFRBox.system_get_info",
513+
return_value=SystemInfo(
514+
**(
515+
await async_load_json_object_fixture(
516+
hass, "system_getInfo.json", DOMAIN
517+
)
518+
)
519+
),
520+
):
521+
result = await hass.config_entries.flow.async_configure(
522+
result["flow_id"],
523+
user_input={
524+
CONF_HOST: "192.168.0.1",
525+
},
526+
)
527+
528+
assert result.get("type") is FlowResultType.ABORT
529+
assert result.get("reason") == "unique_id_mismatch"

0 commit comments

Comments
 (0)