Add Re-Auth Flow to vesync (#137398)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/139252/head
cdnninja 2025-02-25 05:48:31 -07:00 committed by GitHub
parent 2509353221
commit befed910da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 17 deletions

View File

@ -12,6 +12,7 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import entity_registry as er
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)
if not login:
_LOGGER.error("Unable to login to the VeSync server")
return False
raise ConfigEntryAuthFailed
hass.data[DOMAIN] = {}
hass.data[DOMAIN][VS_MANAGER] = manager

View File

@ -1,5 +1,6 @@
"""Config flow utilities."""
from collections.abc import Mapping
from typing import Any
from pyvesync import VeSync
@ -57,3 +58,36 @@ class VeSyncFlowHandler(ConfigFlow, domain=DOMAIN):
title=username,
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"},
)

View File

@ -7,13 +7,22 @@
"username": "[%key:common::config_flow::data::email%]",
"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": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"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": {

View File

@ -48,3 +48,59 @@ async def test_config_flow_user_input(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"][CONF_USERNAME] == "user"
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"

View File

@ -2,7 +2,6 @@
from unittest.mock import Mock, patch
import pytest
from pyvesync import VeSync
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,
config_entry: ConfigEntry,
manager: VeSync,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup does not create config entry when not logged in."""
manager.login = Mock(return_value=False)
with (
patch.object(hass.config_entries, "async_forward_entry_setups") as setups_mock,
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 not await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert manager.login.call_count == 1
assert DOMAIN not in hass.data
assert "Unable to login to the VeSync server" in caplog.text
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert config_entry.state is ConfigEntryState.SETUP_ERROR
assert not hass.data.get(DOMAIN)
async def test_async_setup_entry__no_devices(