mirror of
https://github.com/home-assistant/core.git
synced 2026-02-16 19:00:45 +00:00
Compare commits
9 Commits
dev
...
fritzbox/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b4d367046 | ||
|
|
fdae65a343 | ||
|
|
a7174fe325 | ||
|
|
106558898a | ||
|
|
5c8dd9f2c3 | ||
|
|
b1d7e48a16 | ||
|
|
c687398689 | ||
|
|
3bbe9e1c3d | ||
|
|
35b767c6db |
@@ -3,14 +3,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from requests.exceptions import ConnectionError as RequestConnectionError, HTTPError
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, UnitOfTemperature
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_VERIFY_SSL,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||
|
||||
from .const import DOMAIN, LOGGER, PLATFORMS
|
||||
from .const import DEFAULT_VERIFY_SSL, DOMAIN, LOGGER, PLATFORMS
|
||||
from .coordinator import FritzboxConfigEntry, FritzboxDataUpdateCoordinator
|
||||
|
||||
|
||||
@@ -67,6 +73,49 @@ async def async_unload_entry(hass: HomeAssistant, entry: FritzboxConfigEntry) ->
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: FritzboxConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old config entry to a new format."""
|
||||
LOGGER.debug(
|
||||
"Migrating configuration from version %s.%s",
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
if config_entry.version > 1:
|
||||
# This means the user has downgraded from a future version
|
||||
return False
|
||||
|
||||
if config_entry.version == 1:
|
||||
new_data = {**config_entry.data}
|
||||
if config_entry.minor_version < 2:
|
||||
LOGGER.debug("Migrate config entry data to URL based configuration")
|
||||
if "://" not in config_entry.data[CONF_HOST]:
|
||||
host = URL().build(
|
||||
scheme="http",
|
||||
host=config_entry.data[CONF_HOST],
|
||||
)
|
||||
else:
|
||||
host = config_entry.data[CONF_HOST]
|
||||
|
||||
new_data = {
|
||||
**config_entry.data,
|
||||
CONF_HOST: str(host),
|
||||
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
|
||||
}
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry, data=new_data, version=1, minor_version=2
|
||||
)
|
||||
|
||||
LOGGER.debug(
|
||||
"Migration to configuration version %s.%s successful",
|
||||
config_entry.version,
|
||||
config_entry.minor_version,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_remove_config_entry_device(
|
||||
hass: HomeAssistant, entry: FritzboxConfigEntry, device: DeviceEntry
|
||||
) -> bool:
|
||||
|
||||
@@ -4,35 +4,54 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import ipaddress
|
||||
from typing import Any, Self
|
||||
from urllib.parse import urlparse
|
||||
from typing import TYPE_CHECKING, Any, Self
|
||||
|
||||
from pyfritzhome import Fritzhome, LoginError
|
||||
from requests.exceptions import HTTPError
|
||||
import voluptuous as vol
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.helpers.selector import (
|
||||
TextSelector,
|
||||
TextSelectorConfig,
|
||||
TextSelectorType,
|
||||
)
|
||||
from homeassistant.helpers.service_info.ssdp import (
|
||||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
ATTR_UPNP_PRESENTATION_URL,
|
||||
ATTR_UPNP_UDN,
|
||||
SsdpServiceInfo,
|
||||
)
|
||||
|
||||
from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN
|
||||
from .const import DEFAULT_URL, DEFAULT_USERNAME, DEFAULT_VERIFY_SSL, DOMAIN
|
||||
|
||||
DATA_SCHEMA_USER = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
|
||||
vol.Required(CONF_URL, default=DEFAULT_URL): TextSelector(
|
||||
config=TextSelectorConfig(type=TextSelectorType.URL)
|
||||
),
|
||||
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_PASSWORD): TextSelector(
|
||||
config=TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
vol.Required(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): bool,
|
||||
}
|
||||
)
|
||||
|
||||
DATA_SCHEMA_CONFIRM = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Required(CONF_PASSWORD): TextSelector(
|
||||
config=TextSelectorConfig(type=TextSelectorType.PASSWORD)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -46,22 +65,25 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a AVM FRITZ!SmartHome config flow."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
_name: str
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
self._host: str | None = None
|
||||
self._url: str | None = None
|
||||
self._password: str | None = None
|
||||
self._username: str | None = None
|
||||
self._verify_ssl: bool = DEFAULT_VERIFY_SSL
|
||||
|
||||
def _get_entry(self, name: str) -> ConfigFlowResult:
|
||||
return self.async_create_entry(
|
||||
title=name,
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
CONF_HOST: self._url,
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_USERNAME: self._username,
|
||||
CONF_VERIFY_SSL: self._verify_ssl,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -72,7 +94,10 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
def _try_connect(self) -> str:
|
||||
"""Try to connect and check auth."""
|
||||
fritzbox = Fritzhome(
|
||||
host=self._host, user=self._username, password=self._password
|
||||
host=self._url,
|
||||
user=self._username,
|
||||
password=self._password,
|
||||
ssl_verify=self._verify_ssl,
|
||||
)
|
||||
try:
|
||||
fritzbox.login()
|
||||
@@ -93,12 +118,13 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
||||
|
||||
self._host = user_input[CONF_HOST]
|
||||
self._name = str(user_input[CONF_HOST])
|
||||
self._url = user_input[CONF_URL]
|
||||
self._verify_ssl = user_input[CONF_VERIFY_SSL]
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
self._username = user_input[CONF_USERNAME]
|
||||
self._name = str(self._url)
|
||||
|
||||
self._async_abort_entries_match({CONF_HOST: self._url})
|
||||
|
||||
result = await self.async_try_connect()
|
||||
|
||||
@@ -116,39 +142,41 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self, discovery_info: SsdpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by discovery."""
|
||||
host = urlparse(discovery_info.ssdp_location).hostname
|
||||
assert isinstance(host, str)
|
||||
self._url = discovery_info.upnp[ATTR_UPNP_PRESENTATION_URL]
|
||||
representation_url = URL(self._url)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(representation_url.host, str)
|
||||
|
||||
if (
|
||||
ipaddress.ip_address(host).version == 6
|
||||
and ipaddress.ip_address(host).is_link_local
|
||||
ipaddress.ip_address(representation_url.host).version == 6
|
||||
and ipaddress.ip_address(representation_url.host).is_link_local
|
||||
):
|
||||
return self.async_abort(reason="ignore_ip6_link_local")
|
||||
|
||||
if uuid := discovery_info.upnp.get(ATTR_UPNP_UDN):
|
||||
uuid = uuid.removeprefix("uuid:")
|
||||
await self.async_set_unique_id(uuid)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: host})
|
||||
self._abort_if_unique_id_configured({CONF_HOST: self._url})
|
||||
|
||||
self._host = host
|
||||
if self.hass.config_entries.flow.async_has_matching_flow(self):
|
||||
return self.async_abort(reason="already_in_progress")
|
||||
|
||||
# update old and user-configured config entries
|
||||
for entry in self._async_current_entries(include_ignore=False):
|
||||
if entry.data[CONF_HOST] == host:
|
||||
if entry.data[CONF_HOST] == self._url:
|
||||
if uuid and not entry.unique_id:
|
||||
self.hass.config_entries.async_update_entry(entry, unique_id=uuid)
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
self._name = str(discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME) or host)
|
||||
self._name = str(discovery_info.upnp.get(ATTR_UPNP_FRIENDLY_NAME) or self._url)
|
||||
|
||||
self.context["title_placeholders"] = {"name": self._name}
|
||||
return await self.async_step_confirm()
|
||||
|
||||
def is_matching(self, other_flow: Self) -> bool:
|
||||
"""Return True if other_flow is matching this flow."""
|
||||
return other_flow._host == self._host # noqa: SLF001
|
||||
return other_flow._url == self._url # noqa: SLF001
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -178,7 +206,8 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Trigger a reauthentication flow."""
|
||||
self._host = entry_data[CONF_HOST]
|
||||
self._url = entry_data[CONF_HOST]
|
||||
self._verify_ssl = entry_data[CONF_VERIFY_SSL]
|
||||
self._name = str(entry_data[CONF_HOST])
|
||||
self._username = entry_data[CONF_USERNAME]
|
||||
|
||||
@@ -199,8 +228,7 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if result == RESULT_SUCCESS:
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(),
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
data_updates={
|
||||
CONF_PASSWORD: self._password,
|
||||
CONF_USERNAME: self._username,
|
||||
},
|
||||
@@ -226,31 +254,37 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
errors = {}
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
|
||||
self._url = reconfigure_entry.data[CONF_HOST]
|
||||
self._verify_ssl = reconfigure_entry.data[CONF_VERIFY_SSL]
|
||||
self._username = reconfigure_entry.data[CONF_USERNAME]
|
||||
self._password = reconfigure_entry.data[CONF_PASSWORD]
|
||||
|
||||
if user_input is not None:
|
||||
self._host = user_input[CONF_HOST]
|
||||
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
self._username = reconfigure_entry.data[CONF_USERNAME]
|
||||
self._password = reconfigure_entry.data[CONF_PASSWORD]
|
||||
self._url = user_input[CONF_URL]
|
||||
self._verify_ssl = user_input[CONF_VERIFY_SSL]
|
||||
|
||||
result = await self.async_try_connect()
|
||||
|
||||
if result == RESULT_SUCCESS:
|
||||
return self.async_update_reload_and_abort(
|
||||
reconfigure_entry,
|
||||
data_updates={CONF_HOST: self._host},
|
||||
data_updates={
|
||||
CONF_HOST: self._url,
|
||||
CONF_VERIFY_SSL: self._verify_ssl,
|
||||
},
|
||||
)
|
||||
errors["base"] = result
|
||||
|
||||
host = self._get_reconfigure_entry().data[CONF_HOST]
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOST, default=host): str,
|
||||
vol.Required(CONF_URL, default=self._url): str,
|
||||
vol.Required(CONF_VERIFY_SSL, default=self._verify_ssl): bool,
|
||||
}
|
||||
),
|
||||
description_placeholders={"name": host},
|
||||
description_placeholders={"name": self._url},
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -15,8 +15,9 @@ ATTR_STATE_WINDOW_OPEN: Final = "window_open"
|
||||
COLOR_MODE: Final = "1"
|
||||
COLOR_TEMP_MODE: Final = "4"
|
||||
|
||||
DEFAULT_HOST: Final = "fritz.box"
|
||||
DEFAULT_URL: Final = "http://fritz.box"
|
||||
DEFAULT_USERNAME: Final = "admin"
|
||||
DEFAULT_VERIFY_SSL: Final = True
|
||||
|
||||
DOMAIN: Final = "fritzbox"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from pyfritzhome.devicetypes import FritzhomeTemplate, FritzhomeTrigger
|
||||
from requests.exceptions import ConnectionError as RequestConnectionError, HTTPError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_VERIFY_SSL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
@@ -63,6 +63,7 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat
|
||||
host=self.config_entry.data[CONF_HOST],
|
||||
user=self.config_entry.data[CONF_USERNAME],
|
||||
password=self.config_entry.data[CONF_PASSWORD],
|
||||
ssl_verify=self.config_entry.data[CONF_VERIFY_SSL],
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"common": {
|
||||
"data_desc_ssl_verify": "Whether to verify the SSL certificate when SSL encryption is used to connect to your FRITZ!Box.",
|
||||
"data_desc_url": "The URL of your FRITZ!Box."
|
||||
},
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
@@ -31,21 +35,25 @@
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
"url": "[%key:common::config_flow::data::url%]",
|
||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your FRITZ!Box router."
|
||||
"url": "[%key:component::fritzbox::common::data_desc_url%]",
|
||||
"verify_ssl": "[%key:component::fritzbox::common::data_desc_ssl_verify%]"
|
||||
},
|
||||
"description": "Update your configuration information for {name}."
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
"url": "[%key:common::config_flow::data::url%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your FRITZ!Box router."
|
||||
"url": "[%key:component::fritzbox::common::data_desc_url%]",
|
||||
"verify_ssl": "[%key:component::fritzbox::common::data_desc_ssl_verify%]"
|
||||
},
|
||||
"description": "Enter your FRITZ!Box information."
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ async def setup_config_entry(
|
||||
) -> MockConfigEntry:
|
||||
"""Do setup of a MockConfigEntry."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=data,
|
||||
unique_id=unique_id,
|
||||
domain=DOMAIN, data=data, unique_id=unique_id, version=1, minor_version=2
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
if device is not None and fritz is not None:
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
"""Constants for fritzbox tests."""
|
||||
|
||||
from homeassistant.components.fritzbox.const import DOMAIN
|
||||
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICES,
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
|
||||
MOCK_CONFIG = {
|
||||
DOMAIN: {
|
||||
CONF_DEVICES: [
|
||||
{
|
||||
CONF_HOST: "10.0.0.1",
|
||||
CONF_HOST: "http://10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: False,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import dataclasses
|
||||
from unittest import mock
|
||||
from unittest.mock import Mock, patch
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pyfritzhome import LoginError
|
||||
import pytest
|
||||
@@ -11,11 +10,19 @@ from requests.exceptions import HTTPError
|
||||
|
||||
from homeassistant.components.fritzbox.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER
|
||||
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICES,
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_URL,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers.service_info.ssdp import (
|
||||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
ATTR_UPNP_PRESENTATION_URL,
|
||||
ATTR_UPNP_UDN,
|
||||
SsdpServiceInfo,
|
||||
)
|
||||
@@ -24,33 +31,41 @@ from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]
|
||||
MOCK_USER_DATA = {
|
||||
CONF_URL: "http://10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: False,
|
||||
}
|
||||
MOCK_SSDP_DATA = {
|
||||
"ip4_valid": SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="https://10.0.0.1:12345/test",
|
||||
ssdp_location="http://10.0.0.1:49000/fboxdesc.xml",
|
||||
upnp={
|
||||
ATTR_UPNP_FRIENDLY_NAME: CONF_FAKE_NAME,
|
||||
ATTR_UPNP_UDN: "uuid:only-a-test",
|
||||
ATTR_UPNP_PRESENTATION_URL: "http://10.0.0.1",
|
||||
},
|
||||
),
|
||||
"ip6_valid": SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="https://[1234::1]:12345/test",
|
||||
ssdp_location="http://[1234::1]:49000/fboxdesc.xml",
|
||||
upnp={
|
||||
ATTR_UPNP_FRIENDLY_NAME: CONF_FAKE_NAME,
|
||||
ATTR_UPNP_UDN: "uuid:only-a-test",
|
||||
ATTR_UPNP_PRESENTATION_URL: "http://[1234::1]",
|
||||
},
|
||||
),
|
||||
"ip6_invalid": SsdpServiceInfo(
|
||||
ssdp_usn="mock_usn",
|
||||
ssdp_st="mock_st",
|
||||
ssdp_location="https://[fe80::1%1]:12345/test",
|
||||
ssdp_location="http://[fe80::1%1]:49000/fboxdesc.xml",
|
||||
upnp={
|
||||
ATTR_UPNP_FRIENDLY_NAME: CONF_FAKE_NAME,
|
||||
ATTR_UPNP_UDN: "uuid:only-a-test",
|
||||
ATTR_UPNP_PRESENTATION_URL: "https://[fe80::1%1]",
|
||||
},
|
||||
),
|
||||
}
|
||||
@@ -78,10 +93,11 @@ async def test_user(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
result["flow_id"], user_input=MOCK_USER_DATA
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "10.0.0.1"
|
||||
assert result["data"][CONF_HOST] == "10.0.0.1"
|
||||
assert result["title"] == "http://10.0.0.1"
|
||||
assert result["data"][CONF_HOST] == "http://10.0.0.1"
|
||||
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
||||
assert result["data"][CONF_USERNAME] == "fake_user"
|
||||
assert result["data"][CONF_VERIFY_SSL] is False
|
||||
assert not result["result"].unique_id
|
||||
|
||||
|
||||
@@ -125,7 +141,12 @@ async def test_user_already_configured(hass: HomeAssistant, fritz: Mock) -> None
|
||||
|
||||
async def test_reauth_success(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
"""Test starting a reauthentication flow."""
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
result = await mock_config.start_reauth_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@@ -149,7 +170,12 @@ async def test_reauth_auth_failed(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
"""Test starting a reauthentication flow with authentication failure."""
|
||||
fritz().login.side_effect = LoginError("Boom")
|
||||
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
result = await mock_config.start_reauth_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@@ -172,7 +198,12 @@ async def test_reauth_not_successful(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
"""Test starting a reauthentication flow but no connection found."""
|
||||
fritz().login.side_effect = OSError("Boom")
|
||||
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
result = await mock_config.start_reauth_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
@@ -192,51 +223,50 @@ async def test_reauth_not_successful(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
|
||||
async def test_reconfigure_success(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
"""Test starting a reconfigure flow."""
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
assert mock_config.data[CONF_HOST] == "10.0.0.1"
|
||||
assert mock_config.data[CONF_USERNAME] == "fake_user"
|
||||
assert mock_config.data[CONF_PASSWORD] == "fake_pass"
|
||||
|
||||
result = await mock_config.start_reconfigure_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_HOST: "new_host",
|
||||
},
|
||||
user_input={CONF_URL: "https://new_host:8443", CONF_VERIFY_SSL: True},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
assert mock_config.data[CONF_HOST] == "new_host"
|
||||
assert mock_config.data[CONF_HOST] == "https://new_host:8443"
|
||||
assert mock_config.data[CONF_USERNAME] == "fake_user"
|
||||
assert mock_config.data[CONF_PASSWORD] == "fake_pass"
|
||||
assert mock_config.data[CONF_VERIFY_SSL] is True
|
||||
|
||||
|
||||
async def test_reconfigure_failed(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
"""Test starting a reconfigure flow with failure."""
|
||||
fritz().login.side_effect = [OSError("Boom"), None]
|
||||
|
||||
mock_config = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_DATA)
|
||||
mock_config = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
mock_config.add_to_hass(hass)
|
||||
|
||||
assert mock_config.data[CONF_HOST] == "10.0.0.1"
|
||||
assert mock_config.data[CONF_USERNAME] == "fake_user"
|
||||
assert mock_config.data[CONF_PASSWORD] == "fake_pass"
|
||||
|
||||
result = await mock_config.start_reconfigure_flow(hass)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_HOST: "new_host",
|
||||
},
|
||||
user_input={CONF_URL: "https://new_host:8443", CONF_VERIFY_SSL: True},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "reconfigure"
|
||||
@@ -244,16 +274,15 @@ async def test_reconfigure_failed(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_HOST: "new_host",
|
||||
},
|
||||
user_input={CONF_URL: "https://new_host:8443", CONF_VERIFY_SSL: True},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
assert mock_config.data[CONF_HOST] == "new_host"
|
||||
assert mock_config.data[CONF_HOST] == "https://new_host:8443"
|
||||
assert mock_config.data[CONF_USERNAME] == "fake_user"
|
||||
assert mock_config.data[CONF_PASSWORD] == "fake_pass"
|
||||
assert mock_config.data[CONF_VERIFY_SSL] is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -287,9 +316,10 @@ async def test_ssdp(
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == CONF_FAKE_NAME
|
||||
assert result["data"][CONF_HOST] == urlparse(test_data.ssdp_location).hostname
|
||||
assert result["data"][CONF_HOST] == test_data.upnp[ATTR_UPNP_PRESENTATION_URL]
|
||||
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
||||
assert result["data"][CONF_USERNAME] == "fake_user"
|
||||
assert result["data"][CONF_VERIFY_SSL] is True
|
||||
assert result["result"].unique_id == "only-a-test"
|
||||
|
||||
|
||||
@@ -309,10 +339,11 @@ async def test_ssdp_no_friendly_name(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
user_input={CONF_PASSWORD: "fake_pass", CONF_USERNAME: "fake_user"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "10.0.0.1"
|
||||
assert result["data"][CONF_HOST] == "10.0.0.1"
|
||||
assert result["title"] == "http://10.0.0.1"
|
||||
assert result["data"][CONF_HOST] == "http://10.0.0.1"
|
||||
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
||||
assert result["data"][CONF_USERNAME] == "fake_user"
|
||||
assert result["data"][CONF_VERIFY_SSL] is True
|
||||
assert result["result"].unique_id == "only-a-test"
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
STATE_UNAVAILABLE,
|
||||
UnitOfTemperature,
|
||||
@@ -39,12 +40,18 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
entries = hass.config_entries.async_entries()
|
||||
assert entries
|
||||
assert len(entries) == 1
|
||||
assert entries[0].data[CONF_HOST] == "10.0.0.1"
|
||||
assert entries[0].data[CONF_HOST] == "http://10.0.0.1"
|
||||
assert entries[0].data[CONF_PASSWORD] == "fake_pass"
|
||||
assert entries[0].data[CONF_USERNAME] == "fake_user"
|
||||
assert entries[0].data[CONF_VERIFY_SSL] is False
|
||||
assert fritz.call_count == 1
|
||||
assert fritz.call_args_list == [
|
||||
call(host="10.0.0.1", password="fake_pass", user="fake_user")
|
||||
call(
|
||||
host="http://10.0.0.1",
|
||||
password="fake_pass",
|
||||
user="fake_user",
|
||||
ssl_verify=False,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -86,6 +93,8 @@ async def test_update_unique_id(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
unique_id="any",
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@@ -145,6 +154,8 @@ async def test_update_unique_id_no_change(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
unique_id="any",
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@@ -170,6 +181,8 @@ async def test_unload_remove(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
unique_id=entity_id,
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@@ -209,6 +222,8 @@ async def test_logout_on_stop(hass: HomeAssistant, fritz: Mock) -> None:
|
||||
domain=DOMAIN,
|
||||
data=MOCK_CONFIG[DOMAIN][CONF_DEVICES][0],
|
||||
unique_id=entity_id,
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
@@ -281,6 +296,8 @@ async def test_raise_config_entry_not_ready_when_offline(hass: HomeAssistant) ->
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "any", **MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]},
|
||||
unique_id="any",
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
@@ -302,6 +319,8 @@ async def test_raise_config_entry_error_when_login_fail(hass: HomeAssistant) ->
|
||||
domain=DOMAIN,
|
||||
data={CONF_HOST: "any", **MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]},
|
||||
unique_id="any",
|
||||
version=1,
|
||||
minor_version=2,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
@@ -315,3 +334,99 @@ async def test_raise_config_entry_error_when_login_fail(hass: HomeAssistant) ->
|
||||
entries = hass.config_entries.async_entries()
|
||||
config_entry = entries[0]
|
||||
assert config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("old_data", "new_data"),
|
||||
[
|
||||
(
|
||||
{
|
||||
CONF_HOST: "10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
},
|
||||
{
|
||||
CONF_HOST: "http://10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_HOST: "https://10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
},
|
||||
{
|
||||
CONF_HOST: "https://10.0.0.1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_HOST: "1234::1",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
},
|
||||
{
|
||||
CONF_HOST: "http://[1234::1]",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_HOST: "http://[1234::1]",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
},
|
||||
{
|
||||
CONF_HOST: "http://[1234::1]",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
),
|
||||
(
|
||||
{
|
||||
CONF_HOST: "https://[1234::1]",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
},
|
||||
{
|
||||
CONF_HOST: "https://[1234::1]",
|
||||
CONF_PASSWORD: "fake_pass",
|
||||
CONF_USERNAME: "fake_user",
|
||||
CONF_VERIFY_SSL: True,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_migrate_entry(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
fritz: Mock,
|
||||
old_data: dict,
|
||||
new_data: dict,
|
||||
) -> None:
|
||||
"""Test migrate config entry."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=old_data,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.fritzbox.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
assert entry.version == 1
|
||||
assert entry.minor_version == 2
|
||||
assert entry.data == new_data
|
||||
|
||||
Reference in New Issue
Block a user