Skip to content

Commit e4aadd6

Browse files
authored
Add reconfigure flow to Duck DNS (home-assistant#157948)
1 parent a47255c commit e4aadd6

File tree

4 files changed

+134
-50
lines changed

4 files changed

+134
-50
lines changed

homeassistant/components/duckdns/config_flow.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import voluptuous as vol
99

1010
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
11-
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN
11+
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN, CONF_NAME
1212
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1313
from homeassistant.helpers.selector import (
1414
TextSelector,
@@ -31,6 +31,8 @@
3131
}
3232
)
3333

34+
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
35+
3436

3537
class DuckDnsConfigFlow(ConfigFlow, domain=DOMAIN):
3638
"""Handle a config flow for Duck DNS."""
@@ -79,3 +81,37 @@ async def async_step_import(self, import_info: dict[str, Any]) -> ConfigFlowResu
7981

8082
deprecate_yaml_issue(self.hass, import_success=True)
8183
return result
84+
85+
async def async_step_reconfigure(
86+
self, user_input: dict[str, Any] | None = None
87+
) -> ConfigFlowResult:
88+
"""Handle reconfigure flow."""
89+
errors: dict[str, str] = {}
90+
91+
entry = self._get_reconfigure_entry()
92+
93+
if user_input is not None:
94+
session = async_get_clientsession(self.hass)
95+
try:
96+
if not await _update_duckdns(
97+
session,
98+
entry.data[CONF_DOMAIN],
99+
user_input[CONF_ACCESS_TOKEN],
100+
):
101+
errors["base"] = "update_failed"
102+
except Exception:
103+
_LOGGER.exception("Unexpected exception")
104+
errors["base"] = "unknown"
105+
106+
if not errors:
107+
return self.async_update_reload_and_abort(
108+
entry,
109+
data_updates=user_input,
110+
)
111+
112+
return self.async_show_form(
113+
step_id="reconfigure",
114+
data_schema=STEP_RECONFIGURE_DATA_SCHEMA,
115+
errors=errors,
116+
description_placeholders={CONF_NAME: entry.title},
117+
)

homeassistant/components/duckdns/strings.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
{
22
"config": {
33
"abort": {
4-
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
4+
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
5+
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
56
},
67
"error": {
78
"unknown": "[%key:common::config_flow::error::unknown%]",
89
"update_failed": "Updating Duck DNS failed"
910
},
1011
"step": {
12+
"reconfigure": {
13+
"data": {
14+
"access_token": "[%key:component::duckdns::config::step::user::data::access_token%]"
15+
},
16+
"data_description": {
17+
"access_token": "[%key:component::duckdns::config::step::user::data_description::access_token%]"
18+
},
19+
"title": "Re-configure {name}"
20+
},
1121
"user": {
1222
"data": {
1323
"access_token": "Token",

tests/components/duckdns/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
TEST_SUBDOMAIN = "homeassistant"
1414
TEST_TOKEN = "123e4567-e89b-12d3-a456-426614174000"
15+
NEW_TOKEN = "11111111-2222-3333-4444-55555555"
1516

1617

1718
@pytest.fixture

tests/components/duckdns/test_config_flow.py

Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
import pytest
66

7-
from homeassistant.components.duckdns.const import DOMAIN
7+
from homeassistant.components.duckdns import DOMAIN
88
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
99
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN
1010
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
1111
from homeassistant.data_entry_flow import FlowResultType
1212
from homeassistant.helpers import issue_registry as ir
1313
from homeassistant.setup import async_setup_component
1414

15-
from .conftest import TEST_SUBDOMAIN, TEST_TOKEN
15+
from .conftest import NEW_TOKEN, TEST_SUBDOMAIN, TEST_TOKEN
1616

1717
from tests.common import MockConfigEntry
1818

@@ -72,58 +72,26 @@ async def test_form_already_configured(
7272
assert result["reason"] == "already_configured"
7373

7474

75-
async def test_form_unknown_exception(
75+
@pytest.mark.parametrize(
76+
("side_effect", "text_error"),
77+
[
78+
([ValueError, True], "unknown"),
79+
([False, True], "update_failed"),
80+
],
81+
)
82+
async def test_form_errors(
7683
hass: HomeAssistant,
7784
mock_setup_entry: AsyncMock,
7885
mock_update_duckdns: AsyncMock,
86+
side_effect: list[Exception | bool],
87+
text_error: str,
7988
) -> None:
80-
"""Test we handle unknown exception."""
89+
"""Test we handle errors."""
8190
result = await hass.config_entries.flow.async_init(
8291
DOMAIN, context={"source": SOURCE_USER}
8392
)
84-
mock_update_duckdns.side_effect = ValueError
85-
86-
result = await hass.config_entries.flow.async_configure(
87-
result["flow_id"],
88-
{
89-
CONF_DOMAIN: TEST_SUBDOMAIN,
90-
CONF_ACCESS_TOKEN: TEST_TOKEN,
91-
},
92-
)
93-
94-
assert result["type"] is FlowResultType.FORM
95-
assert result["errors"] == {"base": "unknown"}
96-
97-
mock_update_duckdns.side_effect = None
98-
99-
result = await hass.config_entries.flow.async_configure(
100-
result["flow_id"],
101-
{
102-
CONF_DOMAIN: TEST_SUBDOMAIN,
103-
CONF_ACCESS_TOKEN: TEST_TOKEN,
104-
},
105-
)
93+
mock_update_duckdns.side_effect = side_effect
10694

107-
assert result["type"] is FlowResultType.CREATE_ENTRY
108-
assert result["title"] == f"{TEST_SUBDOMAIN}.duckdns.org"
109-
assert result["data"] == {
110-
CONF_DOMAIN: TEST_SUBDOMAIN,
111-
CONF_ACCESS_TOKEN: TEST_TOKEN,
112-
}
113-
assert len(mock_setup_entry.mock_calls) == 1
114-
115-
116-
async def test_form_update_failed(
117-
hass: HomeAssistant,
118-
mock_setup_entry: AsyncMock,
119-
mock_update_duckdns: AsyncMock,
120-
) -> None:
121-
"""Test we handle cannot connect error."""
122-
result = await hass.config_entries.flow.async_init(
123-
DOMAIN, context={"source": SOURCE_USER}
124-
)
125-
126-
mock_update_duckdns.return_value = False
12795
result = await hass.config_entries.flow.async_configure(
12896
result["flow_id"],
12997
{
@@ -133,9 +101,8 @@ async def test_form_update_failed(
133101
)
134102

135103
assert result["type"] is FlowResultType.FORM
136-
assert result["errors"] == {"base": "update_failed"}
104+
assert result["errors"] == {"base": text_error}
137105

138-
mock_update_duckdns.return_value = True
139106
result = await hass.config_entries.flow.async_configure(
140107
result["flow_id"],
141108
{
@@ -253,3 +220,73 @@ async def test_init_import_flow(
253220
)
254221
assert len(mock_setup_entry.mock_calls) == 1
255222
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
223+
224+
225+
@pytest.mark.usefixtures("mock_update_duckdns")
226+
async def test_flow_reconfigure(
227+
hass: HomeAssistant,
228+
mock_setup_entry: AsyncMock,
229+
config_entry: MockConfigEntry,
230+
) -> None:
231+
"""Test reconfigure flow."""
232+
233+
config_entry.add_to_hass(hass)
234+
result = await config_entry.start_reconfigure_flow(hass)
235+
236+
assert result["type"] is FlowResultType.FORM
237+
assert result["step_id"] == "reconfigure"
238+
239+
result = await hass.config_entries.flow.async_configure(
240+
result["flow_id"],
241+
{CONF_ACCESS_TOKEN: NEW_TOKEN},
242+
)
243+
244+
await hass.async_block_till_done()
245+
246+
assert result["type"] is FlowResultType.ABORT
247+
assert result["reason"] == "reconfigure_successful"
248+
assert config_entry.data[CONF_ACCESS_TOKEN] == NEW_TOKEN
249+
250+
251+
@pytest.mark.parametrize(
252+
("side_effect", "text_error"),
253+
[
254+
([ValueError, True], "unknown"),
255+
([False, True], "update_failed"),
256+
],
257+
)
258+
async def test_flow_reconfigure_errors(
259+
hass: HomeAssistant,
260+
mock_setup_entry: AsyncMock,
261+
mock_update_duckdns: AsyncMock,
262+
config_entry: MockConfigEntry,
263+
side_effect: list[Exception | bool],
264+
text_error: str,
265+
) -> None:
266+
"""Test we handle errors."""
267+
268+
config_entry.add_to_hass(hass)
269+
result = await config_entry.start_reconfigure_flow(hass)
270+
271+
assert result["type"] is FlowResultType.FORM
272+
assert result["step_id"] == "reconfigure"
273+
274+
mock_update_duckdns.side_effect = side_effect
275+
276+
result = await hass.config_entries.flow.async_configure(
277+
result["flow_id"],
278+
{CONF_ACCESS_TOKEN: NEW_TOKEN},
279+
)
280+
281+
assert result["type"] is FlowResultType.FORM
282+
assert result["errors"] == {"base": text_error}
283+
284+
result = await hass.config_entries.flow.async_configure(
285+
result["flow_id"],
286+
{CONF_ACCESS_TOKEN: NEW_TOKEN},
287+
)
288+
289+
assert result["type"] is FlowResultType.ABORT
290+
assert result["reason"] == "reconfigure_successful"
291+
292+
assert config_entry.data[CONF_ACCESS_TOKEN] == NEW_TOKEN

0 commit comments

Comments
 (0)