Skip to content

Commit b19c8ac

Browse files
authored
Feat/reconfigure flow (#52)
* Fix SmaEvChargerConfigFlow * Add reconfigure flow * Add tests for reconfigure flow
1 parent 4bef2a0 commit b19c8ac

5 files changed

Lines changed: 119 additions & 88 deletions

File tree

custom_components/smaev/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import logging
66
from dataclasses import dataclass
7-
from typing import TYPE_CHECKING
87

98
import pysmaev.core
109
import pysmaev.exceptions
@@ -77,12 +76,9 @@ async def async_setup_entry(
7776
except pysmaev.exceptions.SmaEvChargerAuthenticationError as exc:
7877
raise ConfigEntryAuthFailed from exc
7978

80-
if TYPE_CHECKING:
81-
assert entry.unique_id
82-
8379
device_info = DeviceInfo(
8480
configuration_url=url,
85-
identifiers={(DOMAIN, entry.unique_id)},
81+
identifiers={(DOMAIN, smaev_device_info["serial"])},
8682
manufacturer=smaev_device_info["manufacturer"],
8783
model=smaev_device_info["model"],
8884
name=smaev_device_info["name"],

custom_components/smaev/config_flow.py

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import logging
6-
from collections.abc import Mapping
76
from typing import Any
87

98
import homeassistant.helpers.config_validation as cv
@@ -38,9 +37,7 @@
3837
)
3938

4039

41-
async def validate_input(
42-
hass: HomeAssistant, data: dict[str, Any]
43-
) -> tuple[dict[str, Any], dict[str, str]]:
40+
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, str]:
4441
"""Validate the user input allows us to connect."""
4542
session = async_get_clientsession(hass, verify_ssl=data[CONF_VERIFY_SSL])
4643

@@ -51,7 +48,6 @@ async def validate_input(
5148
)
5249

5350
errors: dict[str, str] = {}
54-
device_info: dict[str, str] = {}
5551
try:
5652
await evcharger.open()
5753
except pysmaev.exceptions.SmaEvChargerConnectionError:
@@ -61,51 +57,71 @@ async def validate_input(
6157
except Exception: # pylint: disable=broad-except
6258
_LOGGER.exception("Unexpected exception")
6359
errors[CONF_BASE] = "unknown"
64-
else:
65-
device_info = await evcharger.device_info()
6660

6761
await evcharger.close()
68-
return device_info, errors
62+
return errors
6963

7064

7165
class SmaEvChargerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
7266
"""Handle a config flow for SMA EV Charger."""
7367

7468
VERSION = 1
69+
MINOR_VERSION = 0
70+
71+
_config_data: dict[str, str]
72+
_reconfigure_data: dict[str, str]
7573

7674
async def async_step_user(
7775
self, user_input: dict[str, Any] | None = None
7876
) -> ConfigFlowResult:
7977
"""Handle the initial step."""
80-
if user_input is None:
81-
return self.async_show_form(
82-
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
83-
)
84-
85-
device_info, errors = await validate_input(self.hass, user_input)
86-
87-
if errors:
88-
return self.async_show_form(
89-
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
78+
self._config_data = {}
79+
errors: dict[str, str] = {}
80+
81+
if user_input is not None:
82+
self._async_abort_entries_match(
83+
{
84+
CONF_HOST: user_input[CONF_HOST],
85+
}
9086
)
9187

92-
await self.async_set_unique_id(device_info["serial"])
93-
self._abort_if_unique_id_configured(updates=user_input)
94-
return self.async_create_entry(title=user_input[CONF_HOST], data=user_input)
88+
errors |= await validate_input(self.hass, user_input)
89+
if not errors:
90+
self._config_data.update(user_input)
91+
return self.async_create_entry(
92+
title=user_input[CONF_HOST], data=self._config_data
93+
)
9594

96-
async def async_step_reauth(
97-
self, user_input: Mapping[str, Any]
98-
) -> ConfigFlowResult:
99-
"""Perform reauth upon an API authentication error."""
100-
return await self.async_step_reauth_confirm()
95+
return self.async_show_form(
96+
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
97+
)
10198

102-
async def async_step_reauth_confirm(
99+
async def async_step_reconfigure(
103100
self, user_input: dict[str, Any] | None = None
104101
) -> ConfigFlowResult:
105-
"""Dialog that informs the user that reauth is required."""
106-
if user_input is None:
107-
return self.async_show_form(
108-
step_id="reauth_confirm",
109-
data_schema=vol.Schema({}),
102+
"""Handle the reconfigure step."""
103+
errors: dict[str, str] = {}
104+
reconfigure_entry = self._get_reconfigure_entry()
105+
self._reconfigure_data = reconfigure_entry.data.copy()
106+
107+
if user_input is not None:
108+
self._async_abort_entries_match(
109+
{
110+
CONF_HOST: user_input[CONF_HOST],
111+
}
110112
)
111-
return await self.async_step_user()
113+
114+
self._reconfigure_data = user_input
115+
errors |= await validate_input(self.hass, user_input)
116+
if not errors:
117+
return self.async_update_reload_and_abort(
118+
self._get_reconfigure_entry(), data=self._reconfigure_data
119+
)
120+
121+
return self.async_show_form(
122+
step_id="reconfigure",
123+
data_schema=self.add_suggested_values_to_schema(
124+
STEP_USER_DATA_SCHEMA, self._reconfigure_data
125+
),
126+
errors=errors,
127+
)

custom_components/smaev/translations/de.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@
1212
"title": "Richte den SMA EV Charger ein",
1313
"description": "Gib deine SMA EV Charger-Geräteinformation ein."
1414
},
15-
"reauth_confirm": {
16-
"title": "Integration erneut authentifizieren",
17-
"description": "Die SMA EV Charger-Integration muss dein Konto erneut authentifizieren"
15+
"reconfigure": {
16+
"data": {
17+
"host": "Host",
18+
"username": "Benutzername",
19+
"password": "Passwort",
20+
"ssl": "Verwende ein SSL-Zertifikat",
21+
"verify_ssl": "SSL-Zertifikat überprüfen"
22+
},
23+
"title": "SMA EV Charger neu konfigurieren",
24+
"description": "Aktualisiere deine SMA EV Charger-Geräteinformationen."
1825
}
1926
},
2027
"error": {
@@ -24,7 +31,7 @@
2431
},
2532
"abort": {
2633
"already_configured": "Gerät ist bereits konfiguriert",
27-
"reauth_successful": "Die erneute Authentifizierung war erfolgreich"
34+
"reconfigure_successful": "Die Neukonfiguration war erfolgreich"
2835
}
2936
},
3037
"options": {

custom_components/smaev/translations/en.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@
1212
"title": "Set up SMA EV Charger",
1313
"description": "Enter your SMA EV Charger device information."
1414
},
15-
"reauth_confirm": {
16-
"title": "Reauthenticate integration",
17-
"description": "The SMA EV Charger integration needs to re-authenticate your account"
15+
"reconfigure": {
16+
"data": {
17+
"host": "Host",
18+
"username": "Username",
19+
"password": "Password",
20+
"ssl": "Use an SSL certificate",
21+
"verify_ssl": "Verify SSL certificate"
22+
},
23+
"title": "Reconfigure SMA EV Charger",
24+
"description": "Update your SMA EV Charger device information."
1825
}
1926
},
2027
"error": {
@@ -24,7 +31,7 @@
2431
},
2532
"abort": {
2633
"already_configured": "Device is already configured",
27-
"reauth_successful": "Re-authentication was successful"
34+
"reconfigure_successful": "Reconfigure successful."
2835
}
2936
},
3037
"options": {

tests/test_config_flow.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from custom_components import smaev
1313
from custom_components.smaev.config_flow import SmaEvChargerConfigFlow, validate_input
1414

15-
from .conftest import CONFIG_DATA, DEVICE_INFO, MockSmaEvCharger
15+
from .conftest import CONFIG_DATA, MockConfigEntry, MockSmaEvCharger
1616

1717

1818
async def test_show_form(hass: HomeAssistant) -> None:
@@ -76,53 +76,58 @@ async def test_step_user_error(hass, error, errors):
7676
assert result["errors"] == errors
7777

7878

79-
# @patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
80-
# async def test_options_flow(hass, entry):
81-
# """Test config flow options."""
82-
# entry.add_to_hass(hass)
83-
# result = await hass.config_entries.options.async_init(entry.entry_id)
84-
85-
# assert result["type"] == data_entry_flow.FlowResultType.FORM
86-
# assert result["step_id"] == "init"
87-
88-
# user_input = CONFIG_DATA.copy()
79+
@patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
80+
async def test_step_reconfigure(hass: HomeAssistant, entry: MockSmaEvCharger):
81+
"""Test for reconfigure step."""
82+
entry.add_to_hass(hass)
83+
old_entry_data = entry.data.copy()
8984

90-
# result = await hass.config_entries.options.async_configure(
91-
# result["flow_id"], user_input=user_input
92-
# )
85+
result = await entry.start_reconfigure_flow(hass)
86+
assert result["type"] == data_entry_flow.FlowResultType.FORM
87+
assert result["step_id"] == "reconfigure"
9388

94-
# assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
95-
# assert all(value == entry.data[key] for key, value in user_input.items())
89+
result = await hass.config_entries.flow.async_configure(
90+
result["flow_id"], user_input=CONFIG_DATA.copy()
91+
)
92+
assert result["type"] == data_entry_flow.FlowResultType.ABORT
93+
assert result["reason"] == "reconfigure_successful"
9694

95+
entry = hass.config_entries.async_get_entry(entry.entry_id)
96+
assert entry.data == {**old_entry_data, **CONFIG_DATA}
9797

98-
# @pytest.mark.parametrize(
99-
# ("error", "errors"),
100-
# [
101-
# (pysmaev.exceptions.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
102-
# (
103-
# pysmaev.exceptions.SmaEvChargerAuthenticationError,
104-
# {CONF_BASE: "invalid_auth"},
105-
# ),
106-
# (Exception, {CONF_BASE: "unknown"}),
107-
# ],
108-
# )
109-
# async def test_options_flow_errors(hass, entry, error, errors):
110-
# """Test config flow options."""
111-
# entry.add_to_hass(hass)
112-
# result = await hass.config_entries.options.async_init(entry.entry_id)
11398

114-
# assert result["type"] == data_entry_flow.FlowResultType.FORM
115-
# assert result["step_id"] == "init"
99+
@pytest.mark.parametrize(
100+
("error", "errors"),
101+
[
102+
(pysmaev.exceptions.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
103+
(
104+
pysmaev.exceptions.SmaEvChargerAuthenticationError,
105+
{CONF_BASE: "invalid_auth"},
106+
),
107+
(Exception, {CONF_BASE: "unknown"}),
108+
],
109+
)
110+
async def test_step_reconfigure_error(
111+
hass: HomeAssistant,
112+
entry: MockConfigEntry,
113+
error: type[Exception],
114+
errors: dict[str, str],
115+
) -> None:
116+
"""Test for error in reconfigure step is handled correctly."""
117+
entry.add_to_hass(hass)
116118

117-
# user_input = CONFIG_DATA.copy()
119+
result = await entry.start_reconfigure_flow(hass)
120+
assert result["type"] == data_entry_flow.FlowResultType.FORM
121+
assert result["step_id"] == "reconfigure"
118122

119-
# with patch("pysmaev.core.SmaEvCharger.open", side_effect=error):
120-
# result = await hass.config_entries.options.async_configure(
121-
# result["flow_id"], user_input=user_input
122-
# )
123+
with patch("pysmaev.core.SmaEvCharger.open", side_effect=error):
124+
result = await hass.config_entries.flow.async_configure(
125+
result["flow_id"],
126+
CONFIG_DATA.copy(),
127+
)
123128

124-
# assert result["type"] == data_entry_flow.FlowResultType.FORM
125-
# assert result["errors"] == errors
129+
assert result["type"] == data_entry_flow.FlowResultType.FORM
130+
assert result["errors"] == errors
126131

127132

128133
async def test_validate_connection(hass: HomeAssistant):
@@ -134,7 +139,7 @@ async def test_validate_connection(hass: HomeAssistant):
134139

135140
assert mock.open.is_called
136141
assert mock.device_info.is_called
137-
assert result == (DEVICE_INFO, {})
142+
assert result == {}
138143

139144

140145
@pytest.mark.parametrize(
@@ -155,4 +160,4 @@ async def test_validate_connection_raises_error(hass: HomeAssistant, error, erro
155160
with patch("pysmaev.core.SmaEvCharger.open", side_effect=error):
156161
result = await validate_input(hass, data=data)
157162

158-
assert result == ({}, errors)
163+
assert result == errors

0 commit comments

Comments
 (0)