Skip to content

Commit 4b512bf

Browse files
authored
Allow changing the ip of the C.M.I. (#112)
1 parent 650c558 commit 4b512bf

File tree

5 files changed

+188
-11
lines changed

5 files changed

+188
-11
lines changed

custom_components/ta_cmi/config_flow.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from datetime import timedelta
66
import time
77
from typing import Any
8+
from copy import deepcopy
89

910
from aiohttp import ClientSession
1011
from homeassistant import config_entries
@@ -102,7 +103,7 @@ def __init__(self) -> None:
102103
self.start_time: float = ConfigFlow.init_start_time
103104

104105
async def async_step_user(
105-
self, user_input: dict[str, Any] | None = None
106+
self, user_input: dict[str, Any] | None = None
106107
) -> FlowResult:
107108
"""Handle the initial step."""
108109
errors: dict[str, Any] = {}
@@ -144,7 +145,7 @@ async def async_step_user(
144145
)
145146

146147
async def async_step_devices(
147-
self, user_input: dict[str, Any] | None = None
148+
self, user_input: dict[str, Any] | None = None
148149
) -> FlowResult:
149150
"""Step for setup devices."""
150151
errors: dict[str, Any] = {}
@@ -231,7 +232,7 @@ def _generate_channel_types(self) -> list[str]:
231232
return [x.title() for x in DEVICE_TYPE_STRING_MAP.values()]
232233

233234
async def async_step_channel(
234-
self, user_input: dict[str, Any] | None = None
235+
self, user_input: dict[str, Any] | None = None
235236
) -> FlowResult:
236237
"""Step for setup channels."""
237238

@@ -293,7 +294,7 @@ async def async_step_finish(self) -> FlowResult:
293294
@staticmethod
294295
@callback
295296
def async_get_options_flow(
296-
config_entry: config_entries.ConfigEntry,
297+
config_entry: config_entries.ConfigEntry,
297298
) -> OptionsFlowHandler:
298299
"""Get the options flow for this handler."""
299300
return OptionsFlowHandler(config_entry)
@@ -309,6 +310,7 @@ def get_schema(config: dict[str, Any], device_count: int) -> vol.Schema:
309310

310311
return vol.Schema(
311312
{
313+
vol.Required(CONF_HOST, default=config[CONF_HOST]): cv.string,
312314
vol.Required(
313315
CONF_SCAN_INTERVAL, default=default_interval.seconds / 60
314316
): vol.All(int, vol.Range(min=device_count + 1, max=60)),
@@ -325,7 +327,7 @@ def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
325327
self.data = dict(self.config_entry.data)
326328

327329
async def async_step_init(
328-
self, user_input: dict[str, Any] | None = None
330+
self, user_input: dict[str, Any] | None = None
329331
) -> FlowResult:
330332
"""Handle options flow."""
331333

@@ -334,7 +336,28 @@ async def async_step_init(
334336
if user_input is not None and not errors:
335337
self.data[CONF_SCAN_INTERVAL] = user_input[CONF_SCAN_INTERVAL]
336338

337-
return self.async_create_entry(title="", data=self.data)
339+
if user_input[CONF_HOST] != self.data[CONF_HOST]:
340+
if not user_input[CONF_HOST].startswith("http://"):
341+
user_input[CONF_HOST] = "http://" + user_input[CONF_HOST]
342+
343+
try:
344+
tmp = deepcopy(self.data)
345+
tmp[CONF_HOST] = user_input[CONF_HOST]
346+
await validate_login(
347+
tmp, async_get_clientsession(self.hass)
348+
)
349+
except CannotConnect:
350+
errors["base"] = "cannot_connect"
351+
except InvalidAuth:
352+
errors["base"] = "invalid_auth"
353+
except Exception as err: # pylint: disable=broad-except
354+
_LOGGER.exception("Unexpected exception: %s", err)
355+
errors["base"] = "unknown"
356+
else:
357+
self.data[CONF_HOST] = user_input[CONF_HOST]
358+
return self.async_create_entry(title="", data=self.data)
359+
else:
360+
return self.async_create_entry(title="", data=self.data)
338361

339362
return self.async_show_form(
340363
step_id="init",

custom_components/ta_cmi/strings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"init": {
4747
"title": "Options",
4848
"data": {
49-
"scan_interval": "Update interval (minutes)"
49+
"scan_interval": "Update interval (minutes)",
50+
"host": "Base url of the C.M.I (http://IP)"
5051
}
5152
}
5253
},

custom_components/ta_cmi/translations/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"init": {
4747
"title": "Optionen",
4848
"data": {
49-
"scan_interval": "Aktualisierungsintervall (Minuten)"
49+
"scan_interval": "Aktualisierungsintervall (Minuten)",
50+
"host": "Basis URL der C.M.I. (http://IP)"
5051
}
5152
}
5253
},

custom_components/ta_cmi/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"init": {
4747
"title": "Options",
4848
"data": {
49-
"scan_interval": "Update interval (minutes)"
49+
"scan_interval": "Update interval (minutes)",
50+
"host": "Base url of the C.M.I (http://IP)"
5051
}
5152
}
5253
},

tests/test_config_flow.py

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@
133133
CONF_SCAN_INTERVAL: 15,
134134
}
135135

136+
DUMMY_ENTRY_CHANGE_IP: dict[str, Any] = {
137+
CONF_SCAN_INTERVAL: 15,
138+
CONF_HOST: "http://localhost2"
139+
}
140+
136141
DUMMY_CONFIG_ENTRY_UPDATED: dict[str, Any] = {
137142
CONF_HOST: "http://localhost",
138143
CONF_USERNAME: "test",
@@ -149,6 +154,22 @@
149154
],
150155
}
151156

157+
DUMMY_CONFIG_ENTRY_UPDATED_IP: dict[str, Any] = {
158+
CONF_HOST: "http://localhost2",
159+
CONF_USERNAME: "test",
160+
CONF_PASSWORD: "test",
161+
NEW_UID: True,
162+
CONF_SCAN_INTERVAL: 15,
163+
CONF_DEVICES: [
164+
{
165+
CONF_DEVICE_ID: "2",
166+
CONF_DEVICE_FETCH_MODE: "all",
167+
CONF_DEVICE_TYPE: "UVR16x2",
168+
CONF_CHANNELS: [],
169+
}
170+
],
171+
}
172+
152173
DUMMY_API = CMIAPI("", "", "")
153174

154175

@@ -526,8 +547,8 @@ async def test_step_channels_edit_more(hass: HomeAssistant) -> None:
526547

527548

528549
@pytest.mark.asyncio
529-
async def test_options_flow_init(hass: HomeAssistant) -> None:
530-
"""Test config flow options."""
550+
async def test_options_flow_init_no_ip(hass: HomeAssistant) -> None:
551+
"""Test config flow options with no ip change."""
531552

532553
config_entry = MockConfigEntry(
533554
domain=DOMAIN,
@@ -552,3 +573,133 @@ async def test_options_flow_init(hass: HomeAssistant) -> None:
552573

553574
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
554575
assert dict(config_entry.options) == DUMMY_CONFIG_ENTRY_UPDATED
576+
577+
@pytest.mark.asyncio
578+
async def test_options_flow_init(hass: HomeAssistant) -> None:
579+
"""Test config flow options with ip change."""
580+
config_entry = MockConfigEntry(
581+
domain=DOMAIN,
582+
title="C.M.I",
583+
data=DUMMY_CONFIG_ENTRY,
584+
)
585+
config_entry.add_to_hass(hass)
586+
587+
with patch("custom_components.ta_cmi.async_setup_entry", return_value=True), patch(
588+
"ta_cmi.cmi_api.CMIAPI._make_request_no_json",
589+
return_value="2;",
590+
), patch(
591+
"ta_cmi.cmi_api.CMIAPI._make_request_get", return_value=DUMMY_DEVICE_API_DATA
592+
), patch(
593+
"asyncio.sleep", wraps=sleep_mock
594+
):
595+
result = await hass.config_entries.options.async_init(config_entry.entry_id)
596+
597+
await hass.config_entries.async_setup(config_entry.entry_id)
598+
await hass.async_block_till_done()
599+
600+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
601+
assert result["step_id"] == "init"
602+
603+
result = await hass.config_entries.options.async_configure(
604+
result["flow_id"],
605+
user_input=DUMMY_ENTRY_CHANGE_IP,
606+
)
607+
608+
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
609+
assert dict(config_entry.options) == DUMMY_CONFIG_ENTRY_UPDATED_IP
610+
611+
@pytest.mark.asyncio
612+
async def test_options_flow_ip_change_invalid_auth(hass: HomeAssistant) -> None:
613+
"""Test config flow options with ip change and invalid auth."""
614+
config_entry = MockConfigEntry(
615+
domain=DOMAIN,
616+
title="C.M.I",
617+
data=DUMMY_CONFIG_ENTRY,
618+
)
619+
config_entry.add_to_hass(hass)
620+
621+
with patch("custom_components.ta_cmi.async_setup_entry", return_value=True), patch(
622+
"ta_cmi.cmi_api.CMIAPI._make_request_no_json",
623+
side_effect=InvalidCredentialsError("Invalid API key"),
624+
):
625+
result = await hass.config_entries.options.async_init(config_entry.entry_id)
626+
627+
await hass.config_entries.async_setup(config_entry.entry_id)
628+
await hass.async_block_till_done()
629+
630+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
631+
assert result["step_id"] == "init"
632+
633+
result = await hass.config_entries.options.async_configure(
634+
result["flow_id"],
635+
user_input=DUMMY_ENTRY_CHANGE_IP,
636+
)
637+
638+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
639+
assert result["step_id"] == "init"
640+
assert result["errors"] == {"base": "invalid_auth"}
641+
assert dict(config_entry.options) == {}
642+
643+
@pytest.mark.asyncio
644+
async def test_options_flow_ip_change_connection_error(hass: HomeAssistant) -> None:
645+
"""Test config flow options with ip change and connection error."""
646+
config_entry = MockConfigEntry(
647+
domain=DOMAIN,
648+
title="C.M.I",
649+
data=DUMMY_CONFIG_ENTRY,
650+
)
651+
config_entry.add_to_hass(hass)
652+
653+
with patch("custom_components.ta_cmi.async_setup_entry", return_value=True), patch(
654+
"ta_cmi.cmi_api.CMIAPI._make_request_no_json",
655+
side_effect=ApiError("Could not connect to C.M.I."),
656+
):
657+
result = await hass.config_entries.options.async_init(config_entry.entry_id)
658+
659+
await hass.config_entries.async_setup(config_entry.entry_id)
660+
await hass.async_block_till_done()
661+
662+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
663+
assert result["step_id"] == "init"
664+
665+
result = await hass.config_entries.options.async_configure(
666+
result["flow_id"],
667+
user_input=DUMMY_ENTRY_CHANGE_IP,
668+
)
669+
670+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
671+
assert result["step_id"] == "init"
672+
assert result["errors"] == {"base": "cannot_connect"}
673+
assert dict(config_entry.options) == {}
674+
675+
@pytest.mark.asyncio
676+
async def test_options_flow_ip_change_unexpected_error(hass: HomeAssistant) -> None:
677+
"""Test config flow options with ip change and unexpected error."""
678+
config_entry = MockConfigEntry(
679+
domain=DOMAIN,
680+
title="C.M.I",
681+
data=DUMMY_CONFIG_ENTRY,
682+
)
683+
config_entry.add_to_hass(hass)
684+
685+
with patch("custom_components.ta_cmi.async_setup_entry", return_value=True), patch(
686+
"ta_cmi.cmi_api.CMIAPI._make_request_no_json",
687+
side_effect=Exception("DUMMY"),
688+
):
689+
result = await hass.config_entries.options.async_init(config_entry.entry_id)
690+
691+
await hass.config_entries.async_setup(config_entry.entry_id)
692+
await hass.async_block_till_done()
693+
694+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
695+
assert result["step_id"] == "init"
696+
697+
result = await hass.config_entries.options.async_configure(
698+
result["flow_id"],
699+
user_input=DUMMY_ENTRY_CHANGE_IP,
700+
)
701+
702+
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
703+
assert result["step_id"] == "init"
704+
assert result["errors"] == {"base": "unknown"}
705+
assert dict(config_entry.options) == {}

0 commit comments

Comments
 (0)