Vodafone Station device tracker (#94032)

* New integration for Vodafone Station

* coveragerc

* Add ConfigFlow,ScannerEntity,DataUpdateCoordinator

* Introduce aiovodafone lib

* heavy cleanup

* bump aiovodafone to v0.0.5

* add config_flow tests (100% coverage)

* run pre-comimit scripts again

* Remove redundant parameter SSL

* rename and cleanup

* cleanup and bug fix

* cleanup exceptions

* constructor comment review

* improve test patching

* move VodafoneStationDeviceInfo to dataclass

* intriduce home field

* dispacher cleanup

* remove extra attributes (reduces state writes)

* attempt to complete test flow

* complete flow for test_exception_connection

* add comment about unique id
pull/99215/head
Simone Chemelli 2023-08-28 15:10:23 +02:00 committed by GitHub
parent 660167cb1b
commit 1692d83063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 711 additions and 0 deletions

View File

@ -1440,6 +1440,10 @@ omit =
homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/__init__.py
homeassistant/components/vlc_telnet/media_player.py
homeassistant/components/vodafone_station/__init__.py
homeassistant/components/vodafone_station/const.py
homeassistant/components/vodafone_station/coordinator.py
homeassistant/components/vodafone_station/device_tracker.py
homeassistant/components/volkszaehler/sensor.py
homeassistant/components/volumio/__init__.py
homeassistant/components/volumio/browse_media.py

View File

@ -1368,6 +1368,8 @@ build.json @home-assistant/supervisor
/tests/components/vizio/ @raman325
/homeassistant/components/vlc_telnet/ @rodripf @MartinHjelmare
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
/homeassistant/components/vodafone_station/ @paoloantinori @chemelli74
/tests/components/vodafone_station/ @paoloantinori @chemelli74
/homeassistant/components/voip/ @balloob @synesthesiam
/tests/components/voip/ @balloob @synesthesiam
/homeassistant/components/volumio/ @OnFreund

View File

@ -0,0 +1,40 @@
"""Vodafone Station integration."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import VodafoneStationRouter
PLATFORMS = [Platform.DEVICE_TRACKER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Vodafone Station platform."""
coordinator = VodafoneStationRouter(
hass,
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],
entry.unique_id,
)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
coordinator: VodafoneStationRouter = hass.data[DOMAIN][entry.entry_id]
await coordinator.api.logout()
await coordinator.api.close()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@ -0,0 +1,127 @@
"""Config flow for Vodafone Station integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from aiovodafone import VodafoneStationApi, exceptions as aiovodafone_exceptions
import voluptuous as vol
from homeassistant import core
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult
from .const import _LOGGER, DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN
def user_form_schema(user_input: dict[str, Any] | None) -> vol.Schema:
"""Return user form schema."""
user_input = user_input or {}
return vol.Schema(
{
vol.Optional(CONF_HOST, default=DEFAULT_HOST): str,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
async def validate_input(
hass: core.HomeAssistant, data: dict[str, Any]
) -> dict[str, str]:
"""Validate the user input allows us to connect."""
api = VodafoneStationApi(data[CONF_HOST], data[CONF_USERNAME], data[CONF_PASSWORD])
try:
await api.login()
finally:
await api.logout()
await api.close()
return {"title": data[CONF_HOST]}
class VodafoneStationConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Vodafone Station."""
VERSION = 1
entry: ConfigEntry | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=user_form_schema(user_input)
)
# Use host because no serial number or mac is available to use for a unique id
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
errors = {}
try:
info = await validate_input(self.hass, user_input)
except aiovodafone_exceptions.CannotConnect:
errors["base"] = "cannot_connect"
except aiovodafone_exceptions.CannotAuthenticate:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user", data_schema=user_form_schema(user_input), errors=errors
)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle reauth flow."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert self.entry
self.context["title_placeholders"] = {"host": self.entry.data[CONF_HOST]}
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle reauth confirm."""
assert self.entry
errors = {}
if user_input is not None:
try:
await validate_input(self.hass, {**self.entry.data, **user_input})
except aiovodafone_exceptions.CannotConnect:
errors["base"] = "cannot_connect"
except aiovodafone_exceptions.CannotAuthenticate:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
self.hass.config_entries.async_update_entry(
self.entry,
data={
**self.entry.data,
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
self.hass.async_create_task(
self.hass.config_entries.async_reload(self.entry.entry_id)
)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
description_placeholders={CONF_HOST: self.entry.data[CONF_HOST]},
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
)

View File

@ -0,0 +1,11 @@
"""Vodafone Station constants."""
import logging
_LOGGER = logging.getLogger(__package__)
DOMAIN = "vodafone_station"
DEFAULT_DEVICE_NAME = "Unknown device"
DEFAULT_HOST = "192.168.1.1"
DEFAULT_USERNAME = "vodafone"
DEFAULT_SSL = True

View File

@ -0,0 +1,124 @@
"""Support for Vodafone Station."""
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Any
from aiovodafone import VodafoneStationApi, VodafoneStationDevice, exceptions
from homeassistant.components.device_tracker import DEFAULT_CONSIDER_HOME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import dt as dt_util
from .const import _LOGGER, DOMAIN
CONSIDER_HOME_SECONDS = DEFAULT_CONSIDER_HOME.total_seconds()
@dataclass(slots=True)
class VodafoneStationDeviceInfo:
"""Representation of a device connected to the Vodafone Station."""
device: VodafoneStationDevice
update_time: datetime | None
home: bool
@dataclass(slots=True)
class UpdateCoordinatorDataType:
"""Update coordinator data type."""
devices: dict[str, VodafoneStationDeviceInfo]
sensors: dict[str, Any]
class VodafoneStationRouter(DataUpdateCoordinator[UpdateCoordinatorDataType]):
"""Queries router running Vodafone Station firmware."""
def __init__(
self,
hass: HomeAssistant,
host: str,
username: str,
password: str,
config_entry_unique_id: str | None,
) -> None:
"""Initialize the scanner."""
self._host = host
self.api = VodafoneStationApi(host, username, password)
# Last resort as no MAC or S/N can be retrieved via API
self._id = config_entry_unique_id
super().__init__(
hass=hass,
logger=_LOGGER,
name=f"{DOMAIN}-{host}-coordinator",
update_interval=timedelta(seconds=30),
)
def _calculate_update_time_and_consider_home(
self, device: VodafoneStationDevice, utc_point_in_time: datetime
) -> tuple[datetime | None, bool]:
"""Return update time and consider home.
If the device is connected, return the current time and True.
If the device is not connected, return the last update time and
whether the device was considered home at that time.
If the device is not connected and there is no last update time,
return None and False.
"""
if device.connected:
return utc_point_in_time, True
if (
(data := self.data)
and (stored_device := data.devices.get(device.mac))
and (update_time := stored_device.update_time)
):
return (
update_time,
(
(utc_point_in_time - update_time).total_seconds()
< CONSIDER_HOME_SECONDS
),
)
return None, False
async def _async_update_data(self) -> UpdateCoordinatorDataType:
"""Update router data."""
_LOGGER.debug("Polling Vodafone Station host: %s", self._host)
try:
logged = await self.api.login()
except exceptions.CannotConnect as err:
_LOGGER.warning("Connection error for %s", self._host)
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
except exceptions.CannotAuthenticate as err:
raise ConfigEntryAuthFailed from err
if not logged:
raise ConfigEntryAuthFailed
utc_point_in_time = dt_util.utcnow()
data_devices = {
dev_info.mac: VodafoneStationDeviceInfo(
dev_info,
*self._calculate_update_time_and_consider_home(
dev_info, utc_point_in_time
),
)
for dev_info in (await self.api.get_all_devices()).values()
}
data_sensors = await self.api.get_user_data()
await self.api.logout()
return UpdateCoordinatorDataType(data_devices, data_sensors)
@property
def signal_device_new(self) -> str:
"""Event specific per Vodafone Station entry to signal new device."""
return f"{DOMAIN}-device-new-{self._id}"

View File

@ -0,0 +1,114 @@
"""Support for Vodafone Station routers."""
from __future__ import annotations
from aiovodafone import VodafoneStationDevice
from homeassistant.components.device_tracker import ScannerEntity, SourceType
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import _LOGGER, DOMAIN
from .coordinator import VodafoneStationDeviceInfo, VodafoneStationRouter
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up device tracker for Vodafone Station component."""
_LOGGER.debug("Start device trackers setup")
coordinator: VodafoneStationRouter = hass.data[DOMAIN][entry.entry_id]
tracked: set = set()
@callback
def async_update_router() -> None:
"""Update the values of the router."""
async_add_new_tracked_entities(coordinator, async_add_entities, tracked)
entry.async_on_unload(
async_dispatcher_connect(
hass, coordinator.signal_device_new, async_update_router
)
)
async_update_router()
@callback
def async_add_new_tracked_entities(
coordinator: VodafoneStationRouter,
async_add_entities: AddEntitiesCallback,
tracked: set[str],
) -> None:
"""Add new tracker entities from the router."""
new_tracked = []
_LOGGER.debug("Adding device trackers entities")
for mac, device_info in coordinator.data.devices.items():
if mac in tracked:
continue
_LOGGER.debug("New device tracker: %s", device_info.device.name)
new_tracked.append(VodafoneStationTracker(coordinator, device_info))
tracked.add(mac)
async_add_entities(new_tracked)
class VodafoneStationTracker(CoordinatorEntity[VodafoneStationRouter], ScannerEntity):
"""Representation of a Vodafone Station device."""
def __init__(
self, coordinator: VodafoneStationRouter, device_info: VodafoneStationDeviceInfo
) -> None:
"""Initialize a Vodafone Station device."""
super().__init__(coordinator)
self._coordinator = coordinator
device = device_info.device
mac = device.mac
self._device_mac = mac
self._attr_unique_id = mac
self._attr_name = device.name or mac.replace(":", "_")
@property
def _device_info(self) -> VodafoneStationDeviceInfo:
"""Return fresh data for the device."""
return self.coordinator.data.devices[self._device_mac]
@property
def _device(self) -> VodafoneStationDevice:
"""Return fresh data for the device."""
return self.coordinator.data.devices[self._device_mac].device
@property
def is_connected(self) -> bool:
"""Return true if the device is connected to the network."""
return self._device_info.home
@property
def source_type(self) -> SourceType:
"""Return the source type."""
return SourceType.ROUTER
@property
def hostname(self) -> str | None:
"""Return the hostname of device."""
return self._attr_name
@property
def icon(self) -> str:
"""Return device icon."""
return "mdi:lan-connect" if self._device.connected else "mdi:lan-disconnect"
@property
def ip_address(self) -> str | None:
"""Return the primary ip address of the device."""
return self._device.ip_address
@property
def mac_address(self) -> str:
"""Return the mac address of the device."""
return self._device_mac

View File

@ -0,0 +1,10 @@
{
"domain": "vodafone_station",
"name": "Vodafone Station",
"codeowners": ["@paoloantinori", "@chemelli74"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/vodafone_station",
"iot_class": "local_polling",
"loggers": ["aiovodafone"],
"requirements": ["aiovodafone==0.0.6"]
}

View File

@ -0,0 +1,33 @@
{
"config": {
"flow_title": "{host}",
"step": {
"reauth_confirm": {
"description": "Please enter the correct password for host: {host}",
"data": {
"password": "[%key:common::config_flow::data::password%]"
}
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"ssl": "[%key:common::config_flow::data::ssl%]"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
}
}

View File

@ -508,6 +508,7 @@ FLOWS = {
"vilfo",
"vizio",
"vlc_telnet",
"vodafone_station",
"voip",
"volumio",
"volvooncall",

View File

@ -6202,6 +6202,12 @@
}
}
},
"vodafone_station": {
"name": "Vodafone Station",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_polling"
},
"voicerss": {
"name": "VoiceRSS",
"integration_type": "hub",

View File

@ -365,6 +365,9 @@ aiounifi==58
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
# homeassistant.components.vodafone_station
aiovodafone==0.0.6
# homeassistant.components.waqi
aiowaqi==0.2.1

View File

@ -340,6 +340,9 @@ aiounifi==58
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
# homeassistant.components.vodafone_station
aiovodafone==0.0.6
# homeassistant.components.watttime
aiowatttime==0.1.1

View File

@ -0,0 +1 @@
"""Tests for the Vodafone Station integration."""

View File

@ -0,0 +1,17 @@
"""Common stuff for Vodafone Station tests."""
from homeassistant.components.vodafone_station.const import DOMAIN
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
MOCK_CONFIG = {
DOMAIN: {
CONF_DEVICES: [
{
CONF_HOST: "fake_host",
CONF_USERNAME: "fake_username",
CONF_PASSWORD: "fake_password",
}
]
}
}
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]

View File

@ -0,0 +1,215 @@
"""Tests for Vodafone Station config flow."""
from unittest.mock import patch
from aiovodafone import exceptions as aiovodafone_exceptions
import pytest
from homeassistant.components.vodafone_station.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .const import MOCK_USER_DATA
from tests.common import MockConfigEntry
async def test_user(hass: HomeAssistant) -> None:
"""Test starting a flow by user."""
with patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.login",
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.logout",
), patch(
"homeassistant.components.vodafone_station.async_setup_entry"
) as mock_setup_entry, patch(
"requests.get"
) as mock_request_get:
mock_request_get.return_value.status_code = 200
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"][CONF_HOST] == "fake_host"
assert result["data"][CONF_USERNAME] == "fake_username"
assert result["data"][CONF_PASSWORD] == "fake_password"
assert not result["result"].unique_id
await hass.async_block_till_done()
assert mock_setup_entry.called
@pytest.mark.parametrize(
("side_effect", "error"),
[
(aiovodafone_exceptions.CannotConnect, "cannot_connect"),
(aiovodafone_exceptions.CannotAuthenticate, "invalid_auth"),
(ConnectionResetError, "unknown"),
],
)
async def test_exception_connection(hass: HomeAssistant, side_effect, error) -> None:
"""Test starting a flow by user with a connection error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
with patch(
"aiovodafone.api.VodafoneStationApi.login",
side_effect=side_effect,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_USER_DATA
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == error
# Should be recoverable after hits error
with patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
return_value={
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
},
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.login",
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.logout",
), patch(
"homeassistant.components.vodafone_station.async_setup_entry"
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_HOST: "fake_host",
CONF_USERNAME: "fake_username",
CONF_PASSWORD: "fake_password",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "fake_host"
assert result2["data"] == {
"host": "fake_host",
"username": "fake_username",
"password": "fake_password",
}
async def test_reauth_successful(hass: HomeAssistant) -> None:
"""Test starting a reauthentication flow."""
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
mock_config.add_to_hass(hass)
with patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.login",
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.logout",
), patch(
"homeassistant.components.vodafone_station.async_setup_entry"
), patch(
"requests.get"
) as mock_request_get:
mock_request_get.return_value.status_code = 200
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
data=mock_config.data,
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "other_fake_password",
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
@pytest.mark.parametrize(
("side_effect", "error"),
[
(aiovodafone_exceptions.CannotConnect, "cannot_connect"),
(aiovodafone_exceptions.CannotAuthenticate, "invalid_auth"),
(ConnectionResetError, "unknown"),
],
)
async def test_reauth_not_successful(hass: HomeAssistant, side_effect, error) -> None:
"""Test starting a reauthentication flow but no connection found."""
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
mock_config.add_to_hass(hass)
with patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.login",
side_effect=side_effect,
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.logout",
), patch(
"homeassistant.components.vodafone_station.async_setup_entry"
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH, "entry_id": mock_config.entry_id},
data=mock_config.data,
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "other_fake_password",
},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
assert result["errors"]["base"] == error
# Should be recoverable after hits error
with patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.get_all_devices",
return_value={
"wifi_user": "on|laptop|device-1|xx:xx:xx:xx:xx:xx|192.168.100.1||2.4G",
"ethernet": "laptop|device-2|yy:yy:yy:yy:yy:yy|192.168.100.2|;",
},
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.login",
), patch(
"homeassistant.components.vodafone_station.config_flow.VodafoneStationApi.logout",
), patch(
"homeassistant.components.vodafone_station.async_setup_entry"
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_PASSWORD: "fake_password",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"