Add Re-Auth Flow to vesync (#137398)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>pull/139252/head
parent
2509353221
commit
befed910da
|
@ -12,6 +12,7 @@ from homeassistant.const import (
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
|
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
|
@ -59,8 +60,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||||
login = await hass.async_add_executor_job(manager.login)
|
login = await hass.async_add_executor_job(manager.login)
|
||||||
|
|
||||||
if not login:
|
if not login:
|
||||||
_LOGGER.error("Unable to login to the VeSync server")
|
raise ConfigEntryAuthFailed
|
||||||
return False
|
|
||||||
|
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
hass.data[DOMAIN][VS_MANAGER] = manager
|
hass.data[DOMAIN][VS_MANAGER] = manager
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Config flow utilities."""
|
"""Config flow utilities."""
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyvesync import VeSync
|
from pyvesync import VeSync
|
||||||
|
@ -57,3 +58,36 @@ class VeSyncFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
title=username,
|
title=username,
|
||||||
data={CONF_USERNAME: username, CONF_PASSWORD: password},
|
data={CONF_USERNAME: username, CONF_PASSWORD: password},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_reauth(
|
||||||
|
self, entry_data: Mapping[str, Any]
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle re-authentication with vesync."""
|
||||||
|
return await self.async_step_reauth_confirm()
|
||||||
|
|
||||||
|
async def async_step_reauth_confirm(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Confirm re-authentication with vesync."""
|
||||||
|
|
||||||
|
if user_input:
|
||||||
|
username = user_input[CONF_USERNAME]
|
||||||
|
password = user_input[CONF_PASSWORD]
|
||||||
|
|
||||||
|
manager = VeSync(username, password)
|
||||||
|
login = await self.hass.async_add_executor_job(manager.login)
|
||||||
|
if login:
|
||||||
|
return self.async_update_reload_and_abort(
|
||||||
|
self._get_reauth_entry(),
|
||||||
|
data_updates={
|
||||||
|
CONF_USERNAME: username,
|
||||||
|
CONF_PASSWORD: password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="reauth_confirm",
|
||||||
|
data_schema=DATA_SCHEMA,
|
||||||
|
description_placeholders={"name": "VeSync"},
|
||||||
|
errors={"base": "invalid_auth"},
|
||||||
|
)
|
||||||
|
|
|
@ -7,13 +7,22 @@
|
||||||
"username": "[%key:common::config_flow::data::email%]",
|
"username": "[%key:common::config_flow::data::email%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"reauth_confirm": {
|
||||||
|
"title": "[%key:common::config_flow::title::reauth%]",
|
||||||
|
"description": "The vesync integration needs to re-authenticate your account",
|
||||||
|
"data": {
|
||||||
|
"username": "[%key:common::config_flow::data::email%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
|
|
@ -48,3 +48,59 @@ async def test_config_flow_user_input(hass: HomeAssistant) -> None:
|
||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["data"][CONF_USERNAME] == "user"
|
assert result["data"][CONF_USERNAME] == "user"
|
||||||
assert result["data"][CONF_PASSWORD] == "pass"
|
assert result["data"][CONF_PASSWORD] == "pass"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reauth_flow(hass: HomeAssistant) -> None:
|
||||||
|
"""Test a successful reauth flow."""
|
||||||
|
mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="test-username",
|
||||||
|
)
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await mock_entry.start_reauth_flow(hass)
|
||||||
|
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
with patch("pyvesync.vesync.VeSync.login", return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reauth_successful"
|
||||||
|
assert mock_entry.data == {
|
||||||
|
CONF_USERNAME: "new-username",
|
||||||
|
CONF_PASSWORD: "new-password",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_reauth_flow_invalid_auth(hass: HomeAssistant) -> None:
|
||||||
|
"""Test an authorization error reauth flow."""
|
||||||
|
|
||||||
|
mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="test-username",
|
||||||
|
)
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await mock_entry.start_reauth_flow(hass)
|
||||||
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
|
||||||
|
with patch("pyvesync.vesync.VeSync.login", return_value=False):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
with patch("pyvesync.vesync.VeSync.login", return_value=True):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{CONF_USERNAME: "new-username", CONF_PASSWORD: "new-password"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reauth_successful"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
import pytest
|
|
||||||
from pyvesync import VeSync
|
from pyvesync import VeSync
|
||||||
|
|
||||||
from homeassistant.components.vesync import SERVICE_UPDATE_DEVS, async_setup_entry
|
from homeassistant.components.vesync import SERVICE_UPDATE_DEVS, async_setup_entry
|
||||||
|
@ -19,25 +18,17 @@ async def test_async_setup_entry__not_login(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
manager: VeSync,
|
manager: VeSync,
|
||||||
caplog: pytest.LogCaptureFixture,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test setup does not create config entry when not logged in."""
|
"""Test setup does not create config entry when not logged in."""
|
||||||
manager.login = Mock(return_value=False)
|
manager.login = Mock(return_value=False)
|
||||||
|
|
||||||
with (
|
assert not await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock,
|
await hass.async_block_till_done()
|
||||||
patch(
|
|
||||||
"homeassistant.components.vesync.async_generate_device_list"
|
|
||||||
) as process_mock,
|
|
||||||
):
|
|
||||||
assert not await async_setup_entry(hass, config_entry)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert setups_mock.call_count == 0
|
|
||||||
assert process_mock.call_count == 0
|
|
||||||
|
|
||||||
assert manager.login.call_count == 1
|
assert manager.login.call_count == 1
|
||||||
assert DOMAIN not in hass.data
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
assert "Unable to login to the VeSync server" in caplog.text
|
assert config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
assert not hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
async def test_async_setup_entry__no_devices(
|
async def test_async_setup_entry__no_devices(
|
||||||
|
|
Loading…
Reference in New Issue