Use unique_id for config_entries of HomematicIP Cloud (#31133)

* use hapid as unique_id for config_entry of HomematicIP Cloud

* Add qualita_scale to manifest

* Update config_flow

* use core interface for config_flow tests

* refactor to fail earlier

* use asynctest

* rewrite tests

* rewrite tests

* fix test

* add assert
This commit is contained in:
SukramJ 2020-01-26 14:54:33 +01:00 committed by Martin Hjelmare
parent 3f03848a07
commit 1b3c4ed4b3
15 changed files with 254 additions and 250 deletions

View file

@ -17,7 +17,6 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import comp_entity_ids from homeassistant.helpers.config_validation import comp_entity_ids
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .config_flow import configured_haps
from .const import ( from .const import (
CONF_ACCESSPOINT, CONF_ACCESSPOINT,
CONF_AUTHTOKEN, CONF_AUTHTOKEN,
@ -130,7 +129,10 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
accesspoints = config.get(DOMAIN, []) accesspoints = config.get(DOMAIN, [])
for conf in accesspoints: for conf in accesspoints:
if conf[CONF_ACCESSPOINT] not in configured_haps(hass): if conf[CONF_ACCESSPOINT] not in set(
entry.data[HMIPC_HAPID]
for entry in hass.config_entries.async_entries(DOMAIN)
):
hass.async_add_job( hass.async_add_job(
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
@ -274,7 +276,7 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
anonymize = service.data[ATTR_ANONYMIZE] anonymize = service.data[ATTR_ANONYMIZE]
for hap in hass.data[DOMAIN].values(): for hap in hass.data[DOMAIN].values():
hap_sgtin = hap.config_entry.title hap_sgtin = hap.config_entry.unique_id
if anonymize: if anonymize:
hap_sgtin = hap_sgtin[-4:] hap_sgtin = hap_sgtin[-4:]
@ -331,9 +333,17 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Set up an access point from a config entry.""" """Set up an access point from a config entry."""
# 0.104 introduced config entry unique id, this makes upgrading possible
if entry.unique_id is None:
new_data = dict(entry.data)
hass.config_entries.async_update_entry(
entry, unique_id=new_data[HMIPC_HAPID], data=new_data
)
hap = HomematicipHAP(hass, entry) hap = HomematicipHAP(hass, entry)
hapid = entry.data[HMIPC_HAPID].replace("-", "").upper() hass.data[DOMAIN][entry.unique_id] = hap
hass.data[DOMAIN][hapid] = hap
if not await hap.async_setup(): if not await hap.async_setup():
return False return False
@ -356,5 +366,5 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
hap = hass.data[DOMAIN].pop(entry.data[HMIPC_HAPID]) hap = hass.data[DOMAIN].pop(entry.unique_id)
return await hap.async_reset() return await hap.async_reset()

View file

@ -18,7 +18,7 @@ from homeassistant.const import (
) )
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID from . import DOMAIN as HMIPC_DOMAIN
from .hap import HomematicipHAP from .hap import HomematicipHAP
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -30,7 +30,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP alrm control panel from a config entry.""" """Set up the HomematicIP alrm control panel from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
async_add_entities([HomematicipAlarmControlPanel(hap)]) async_add_entities([HomematicipAlarmControlPanel(hap)])

View file

@ -41,7 +41,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .hap import HomematicipHAP from .hap import HomematicipHAP
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -79,7 +79,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP Cloud binary sensor from a config entry.""" """Set up the HomematicIP Cloud binary sensor from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, AsyncAccelerationSensor): if isinstance(device, AsyncAccelerationSensor):

View file

@ -27,7 +27,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .hap import HomematicipHAP from .hap import HomematicipHAP
HEATING_PROFILES = {"PROFILE_1": 0, "PROFILE_2": 1, "PROFILE_3": 2} HEATING_PROFILES = {"PROFILE_1": 0, "PROFILE_2": 1, "PROFILE_3": 2}
@ -47,7 +47,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP climate from a config entry.""" """Set up the HomematicIP climate from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.groups: for device in hap.home.groups:
if isinstance(device, AsyncHeatingGroup): if isinstance(device, AsyncHeatingGroup):

View file

@ -1,11 +1,9 @@
"""Config flow to configure the HomematicIP Cloud component.""" """Config flow to configure the HomematicIP Cloud component."""
from typing import Any, Dict, Set from typing import Any, Dict
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.helpers.typing import HomeAssistantType
from .const import ( from .const import (
_LOGGER, _LOGGER,
@ -18,15 +16,6 @@ from .const import (
from .hap import HomematicipAuth from .hap import HomematicipAuth
@callback
def configured_haps(hass: HomeAssistantType) -> Set[str]:
"""Return a set of the configured access points."""
return set(
entry.data[HMIPC_HAPID]
for entry in hass.config_entries.async_entries(HMIPC_DOMAIN)
)
@config_entries.HANDLERS.register(HMIPC_DOMAIN) @config_entries.HANDLERS.register(HMIPC_DOMAIN)
class HomematicipCloudFlowHandler(config_entries.ConfigFlow): class HomematicipCloudFlowHandler(config_entries.ConfigFlow):
"""Config flow for the HomematicIP Cloud component.""" """Config flow for the HomematicIP Cloud component."""
@ -48,8 +37,9 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow):
if user_input is not None: if user_input is not None:
user_input[HMIPC_HAPID] = user_input[HMIPC_HAPID].replace("-", "").upper() user_input[HMIPC_HAPID] = user_input[HMIPC_HAPID].replace("-", "").upper()
if user_input[HMIPC_HAPID] in configured_haps(self.hass):
return self.async_abort(reason="already_configured") await self.async_set_unique_id(user_input[HMIPC_HAPID])
self._abort_if_unique_id_configured()
self.auth = HomematicipAuth(self.hass, user_input) self.auth = HomematicipAuth(self.hass, user_input)
connected = await self.auth.async_setup() connected = await self.auth.async_setup()
@ -93,16 +83,14 @@ class HomematicipCloudFlowHandler(config_entries.ConfigFlow):
async def async_step_import(self, import_info) -> Dict[str, Any]: async def async_step_import(self, import_info) -> Dict[str, Any]:
"""Import a new access point as a config entry.""" """Import a new access point as a config entry."""
hapid = import_info[HMIPC_HAPID] hapid = import_info[HMIPC_HAPID].replace("-", "").upper()
authtoken = import_info[HMIPC_AUTHTOKEN] authtoken = import_info[HMIPC_AUTHTOKEN]
name = import_info[HMIPC_NAME] name = import_info[HMIPC_NAME]
hapid = hapid.replace("-", "").upper() await self.async_set_unique_id(hapid)
if hapid in configured_haps(self.hass): self._abort_if_unique_id_configured()
return self.async_abort(reason="already_configured")
_LOGGER.info("Imported authentication for %s", hapid) _LOGGER.info("Imported authentication for %s", hapid)
return self.async_create_entry( return self.async_create_entry(
title=hapid, title=hapid,
data={HMIPC_AUTHTOKEN: authtoken, HMIPC_HAPID: hapid, HMIPC_NAME: name}, data={HMIPC_AUTHTOKEN: authtoken, HMIPC_HAPID: hapid, HMIPC_NAME: name},

View file

@ -17,7 +17,7 @@ from homeassistant.components.cover import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -31,7 +31,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP cover from a config entry.""" """Set up the HomematicIP cover from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, AsyncFullFlushBlind): if isinstance(device, AsyncFullFlushBlind):

View file

@ -95,8 +95,7 @@ class HomematicipHAP:
raise ConfigEntryNotReady raise ConfigEntryNotReady
_LOGGER.info( _LOGGER.info(
"Connected to HomematicIP with HAP %s", "Connected to HomematicIP with HAP %s", self.config_entry.unique_id
self.config_entry.data.get(HMIPC_HAPID),
) )
for component in COMPONENTS: for component in COMPONENTS:
@ -193,7 +192,7 @@ class HomematicipHAP:
_LOGGER.error( _LOGGER.error(
"Error connecting to HomematicIP with HAP %s. " "Error connecting to HomematicIP with HAP %s. "
"Retrying in %d seconds", "Retrying in %d seconds",
self.config_entry.data.get(HMIPC_HAPID), self.config_entry.unique_id,
retry_delay, retry_delay,
) )

View file

@ -25,7 +25,7 @@ from homeassistant.components.light import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .hap import HomematicipHAP from .hap import HomematicipHAP
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,7 +38,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP Cloud lights from a config entry.""" """Set up the HomematicIP Cloud lights from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, AsyncBrandSwitchMeasuring): if isinstance(device, AsyncBrandSwitchMeasuring):

View file

@ -5,5 +5,6 @@
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud", "documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"requirements": ["homematicip==0.10.15"], "requirements": ["homematicip==0.10.15"],
"dependencies": [], "dependencies": [],
"codeowners": ["@SukramJ"] "codeowners": ["@SukramJ"],
"quality_scale": "platinum"
} }

View file

@ -34,7 +34,7 @@ from homeassistant.const import (
) )
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .device import ATTR_IS_GROUP, ATTR_MODEL_TYPE from .device import ATTR_IS_GROUP, ATTR_MODEL_TYPE
from .hap import HomematicipHAP from .hap import HomematicipHAP
@ -60,7 +60,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP Cloud sensors from a config entry.""" """Set up the HomematicIP Cloud sensors from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [HomematicipAccesspointStatus(hap)] entities = [HomematicipAccesspointStatus(hap)]
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)): if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)):

View file

@ -19,7 +19,7 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .device import ATTR_GROUP_MEMBER_UNREACHABLE from .device import ATTR_GROUP_MEMBER_UNREACHABLE
from .hap import HomematicipHAP from .hap import HomematicipHAP
@ -30,7 +30,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP switch from a config entry.""" """Set up the HomematicIP switch from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, AsyncBrandSwitchMeasuring): if isinstance(device, AsyncBrandSwitchMeasuring):

View file

@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericDevice
from .hap import HomematicipHAP from .hap import HomematicipHAP
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,7 +41,7 @@ async def async_setup_entry(
hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities hass: HomeAssistantType, config_entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up the HomematicIP weather sensor from a config entry.""" """Set up the HomematicIP weather sensor from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]] hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [] entities = []
for device in hap.home.devices: for device in hap.home.devices:
if isinstance(device, AsyncWeatherSensorPro): if isinstance(device, AsyncWeatherSensorPro):

View file

@ -49,6 +49,7 @@ def hmip_config_entry_fixture() -> config_entries.ConfigEntry:
version=1, version=1,
domain=HMIPC_DOMAIN, domain=HMIPC_DOMAIN,
title=HAPID, title=HAPID,
unique_id=HAPID,
data=entry_data, data=entry_data,
source="import", source="import",
connection_class=config_entries.CONN_CLASS_CLOUD_PUSH, connection_class=config_entries.CONN_CLASS_CLOUD_PUSH,

View file

@ -1,34 +1,58 @@
"""Tests for HomematicIP Cloud config flow.""" """Tests for HomematicIP Cloud config flow."""
from unittest.mock import patch from asynctest import patch
from homeassistant.components.homematicip_cloud import config_flow, const, hap as hmipc from homeassistant.components.homematicip_cloud import const
from tests.common import MockConfigEntry, mock_coro from tests.common import MockConfigEntry
async def test_flow_works(hass): async def test_flow_works(hass):
"""Test config flow works.""" """Test config flow."""
config = { config = {
const.HMIPC_HAPID: "ABC123", const.HMIPC_HAPID: "ABC123",
const.HMIPC_PIN: "123", const.HMIPC_PIN: "123",
const.HMIPC_NAME: "hmip", const.HMIPC_NAME: "hmip",
} }
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
hap = hmipc.HomematicipAuth(hass, config) with patch(
with patch.object(hap, "get_auth", return_value=mock_coro()), patch.object( "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
hmipc.HomematicipAuth, "async_checkbutton", return_value=mock_coro(True) return_value=False,
), patch.object(
hmipc.HomematicipAuth, "async_setup", return_value=mock_coro(True)
), patch.object(
hmipc.HomematicipAuth, "async_register", return_value=mock_coro(True)
): ):
hap.authtoken = "ABC" result = await hass.config_entries.flow.async_init(
result = await flow.async_step_init(user_input=config) const.DOMAIN, context={"source": "user"}, data=config
)
assert hap.authtoken == "ABC" assert result["type"] == "form"
assert result["type"] == "create_entry" assert result["step_id"] == "link"
assert result["errors"] == {"base": "press_the_button"}
flow = next(
(
flow
for flow in hass.config_entries.flow.async_progress()
if flow["flow_id"] == result["flow_id"]
)
)
assert flow["context"]["unique_id"] == "ABC123"
with patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
return_value=True,
), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
return_value=True,
), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={}
)
assert result["type"] == "create_entry"
assert result["title"] == "ABC123"
assert result["data"] == {"hapid": "ABC123", "authtoken": True, "name": "hmip"}
assert result["result"].unique_id == "ABC123"
async def test_flow_init_connection_error(hass): async def test_flow_init_connection_error(hass):
@ -38,14 +62,17 @@ async def test_flow_init_connection_error(hass):
const.HMIPC_PIN: "123", const.HMIPC_PIN: "123",
const.HMIPC_NAME: "hmip", const.HMIPC_NAME: "hmip",
} }
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
with patch.object( with patch(
hmipc.HomematicipAuth, "async_setup", return_value=mock_coro(False) "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
return_value=False,
): ):
result = await flow.async_step_init(user_input=config) result = await hass.config_entries.flow.async_init(
assert result["type"] == "form" const.DOMAIN, context={"source": "user"}, data=config
)
assert result["type"] == "form"
assert result["step_id"] == "init"
async def test_flow_link_connection_error(hass): async def test_flow_link_connection_error(hass):
@ -55,18 +82,23 @@ async def test_flow_link_connection_error(hass):
const.HMIPC_PIN: "123", const.HMIPC_PIN: "123",
const.HMIPC_NAME: "hmip", const.HMIPC_NAME: "hmip",
} }
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
with patch.object( with patch(
hmipc.HomematicipAuth, "async_setup", return_value=mock_coro(True) "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
), patch.object( return_value=True,
hmipc.HomematicipAuth, "async_checkbutton", return_value=mock_coro(True) ), patch(
), patch.object( "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
hmipc.HomematicipAuth, "async_register", return_value=mock_coro(False) return_value=True,
), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register",
return_value=False,
): ):
result = await flow.async_step_init(user_input=config) result = await hass.config_entries.flow.async_init(
assert result["type"] == "abort" const.DOMAIN, context={"source": "user"}, data=config
)
assert result["type"] == "abort"
assert result["reason"] == "connection_aborted"
async def test_flow_link_press_button(hass): async def test_flow_link_press_button(hass):
@ -76,92 +108,104 @@ async def test_flow_link_press_button(hass):
const.HMIPC_PIN: "123", const.HMIPC_PIN: "123",
const.HMIPC_NAME: "hmip", const.HMIPC_NAME: "hmip",
} }
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
with patch.object( with patch(
hmipc.HomematicipAuth, "async_setup", return_value=mock_coro(True) "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
), patch.object( return_value=False,
hmipc.HomematicipAuth, "async_checkbutton", return_value=mock_coro(False) ), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
return_value=True,
): ):
result = await flow.async_step_init(user_input=config) result = await hass.config_entries.flow.async_init(
assert result["type"] == "form" const.DOMAIN, context={"source": "user"}, data=config
assert result["errors"] == {"base": "press_the_button"} )
assert result["type"] == "form"
assert result["step_id"] == "link"
assert result["errors"] == {"base": "press_the_button"}
async def test_init_flow_show_form(hass): async def test_init_flow_show_form(hass):
"""Test config flow shows up with a form.""" """Test config flow shows up with a form."""
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
result = await flow.async_step_init(user_input=None) result = await hass.config_entries.flow.async_init(
assert result["type"] == "form" const.DOMAIN, context={"source": "user"}
)
async def test_init_flow_user_show_form(hass):
"""Test config flow shows up with a form."""
flow = config_flow.HomematicipCloudFlowHandler()
flow.hass = hass
result = await flow.async_step_user(user_input=None)
assert result["type"] == "form" assert result["type"] == "form"
assert result["step_id"] == "init"
async def test_init_already_configured(hass): async def test_init_already_configured(hass):
"""Test accesspoint is already configured.""" """Test accesspoint is already configured."""
MockConfigEntry( MockConfigEntry(domain=const.DOMAIN, unique_id="ABC123").add_to_hass(hass)
domain=const.DOMAIN, data={const.HMIPC_HAPID: "ABC123"}
).add_to_hass(hass)
config = { config = {
const.HMIPC_HAPID: "ABC123", const.HMIPC_HAPID: "ABC123",
const.HMIPC_PIN: "123", const.HMIPC_PIN: "123",
const.HMIPC_NAME: "hmip", const.HMIPC_NAME: "hmip",
} }
flow = config_flow.HomematicipCloudFlowHandler() with patch(
flow.hass = hass "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": "user"}, data=config
)
result = await flow.async_step_init(user_input=config)
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "already_configured"
async def test_import_config(hass): async def test_import_config(hass):
"""Test importing a host with an existing config file.""" """Test importing a host with an existing config file."""
flow = config_flow.HomematicipCloudFlowHandler() config = {
flow.hass = hass const.HMIPC_HAPID: "ABC123",
const.HMIPC_AUTHTOKEN: "123",
const.HMIPC_NAME: "hmip",
}
result = await flow.async_step_import( with patch(
{ "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
hmipc.HMIPC_HAPID: "ABC123", return_value=True,
hmipc.HMIPC_AUTHTOKEN: "123", ), patch(
hmipc.HMIPC_NAME: "hmip", "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
} return_value=True,
) ), patch(
"homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": "import"}, data=config
)
assert result["type"] == "create_entry" assert result["type"] == "create_entry"
assert result["title"] == "ABC123" assert result["title"] == "ABC123"
assert result["data"] == { assert result["data"] == {"authtoken": "123", "hapid": "ABC123", "name": "hmip"}
hmipc.HMIPC_HAPID: "ABC123", assert result["result"].unique_id == "ABC123"
hmipc.HMIPC_AUTHTOKEN: "123",
hmipc.HMIPC_NAME: "hmip",
}
async def test_import_existing_config(hass): async def test_import_existing_config(hass):
"""Test abort of an existing accesspoint from config.""" """Test abort of an existing accesspoint from config."""
flow = config_flow.HomematicipCloudFlowHandler() MockConfigEntry(domain=const.DOMAIN, unique_id="ABC123").add_to_hass(hass)
flow.hass = hass config = {
const.HMIPC_HAPID: "ABC123",
const.HMIPC_AUTHTOKEN: "123",
const.HMIPC_NAME: "hmip",
}
MockConfigEntry( with patch(
domain=const.DOMAIN, data={hmipc.HMIPC_HAPID: "ABC123"} "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_checkbutton",
).add_to_hass(hass) return_value=True,
), patch(
result = await flow.async_step_import( "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_setup",
{ return_value=True,
hmipc.HMIPC_HAPID: "ABC123", ), patch(
hmipc.HMIPC_AUTHTOKEN: "123", "homeassistant.components.homematicip_cloud.hap.HomematicipAuth.async_register",
hmipc.HMIPC_NAME: "hmip", return_value=True,
} ):
) result = await hass.config_entries.flow.async_init(
const.DOMAIN, context={"source": "import"}, data=config
)
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "already_configured"

View file

@ -1,154 +1,115 @@
"""Test HomematicIP Cloud setup process.""" """Test HomematicIP Cloud setup process."""
from unittest.mock import patch from asynctest import CoroutineMock, patch
from homeassistant.components import homematicip_cloud as hmipc from homeassistant.components import homematicip_cloud as hmipc
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import Mock, MockConfigEntry, mock_coro from tests.common import Mock, MockConfigEntry
async def test_config_with_accesspoint_passed_to_config_entry(hass): async def test_config_with_accesspoint_passed_to_config_entry(hass):
"""Test that config for a accesspoint are loaded via config entry.""" """Test that config for a accesspoint are loaded via config entry."""
with patch.object(hass, "config_entries") as mock_config_entries, patch.object(
hmipc, "configured_haps", return_value=[]
):
assert (
await async_setup_component(
hass,
hmipc.DOMAIN,
{
hmipc.DOMAIN: {
hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "name",
}
},
)
is True
)
# Flow started for the access point entry_config = {
assert len(mock_config_entries.flow.mock_calls) >= 2 hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "name",
}
# no config_entry exists
assert len(hass.config_entries.async_entries(hmipc.DOMAIN)) == 0
# no acccesspoint exists
assert not hass.data.get(hmipc.DOMAIN)
assert (
await async_setup_component(hass, hmipc.DOMAIN, {hmipc.DOMAIN: entry_config})
is True
)
# config_entry created for access point
config_entries = hass.config_entries.async_entries(hmipc.DOMAIN)
assert len(config_entries) == 1
assert config_entries[0].data == {
"authtoken": "123",
"hapid": "ABC123",
"name": "name",
}
# defined access_point created for config_entry
assert isinstance(hass.data[hmipc.DOMAIN]["ABC123"], hmipc.HomematicipHAP)
async def test_config_already_registered_not_passed_to_config_entry(hass): async def test_config_already_registered_not_passed_to_config_entry(hass):
"""Test that an already registered accesspoint does not get imported.""" """Test that an already registered accesspoint does not get imported."""
with patch.object(hass, "config_entries") as mock_config_entries, patch.object(
hmipc, "configured_haps", return_value=["ABC123"]
):
assert (
await async_setup_component(
hass,
hmipc.DOMAIN,
{
hmipc.DOMAIN: {
hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "name",
}
},
)
is True
)
# No flow started mock_config = {
assert not mock_config_entries.flow.mock_calls
async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = MockConfigEntry(
domain=hmipc.DOMAIN,
data={
hmipc.HMIPC_HAPID: "ABC123",
hmipc.HMIPC_AUTHTOKEN: "123",
hmipc.HMIPC_NAME: "hmip",
},
)
entry.add_to_hass(hass)
with patch.object(hmipc, "HomematicipHAP") as mock_hap:
instance = mock_hap.return_value
instance.async_setup.return_value = mock_coro(True)
instance.home.id = "1"
instance.home.modelType = "mock-type"
instance.home.name = "mock-name"
instance.home.currentAPVersion = "mock-ap-version"
assert (
await async_setup_component(
hass,
hmipc.DOMAIN,
{
hmipc.DOMAIN: {
hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "hmip",
}
},
)
is True
)
assert len(mock_hap.mock_calls) >= 2
async def test_setup_defined_accesspoint(hass):
"""Test we initiate config entry for the accesspoint."""
with patch.object(hass, "config_entries") as mock_config_entries, patch.object(
hmipc, "configured_haps", return_value=[]
):
mock_config_entries.flow.async_init.return_value = mock_coro()
assert (
await async_setup_component(
hass,
hmipc.DOMAIN,
{
hmipc.DOMAIN: {
hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "hmip",
}
},
)
is True
)
assert len(mock_config_entries.flow.mock_calls) == 1
assert mock_config_entries.flow.mock_calls[0][2]["data"] == {
hmipc.HMIPC_HAPID: "ABC123",
hmipc.HMIPC_AUTHTOKEN: "123", hmipc.HMIPC_AUTHTOKEN: "123",
hmipc.HMIPC_NAME: "hmip", hmipc.HMIPC_HAPID: "ABC123",
hmipc.HMIPC_NAME: "name",
} }
MockConfigEntry(domain=hmipc.DOMAIN, data=mock_config).add_to_hass(hass)
# one config_entry exists
config_entries = hass.config_entries.async_entries(hmipc.DOMAIN)
assert len(config_entries) == 1
assert config_entries[0].data == {
"authtoken": "123",
"hapid": "ABC123",
"name": "name",
}
# config_enty has no unique_id
assert not config_entries[0].unique_id
entry_config = {
hmipc.CONF_ACCESSPOINT: "ABC123",
hmipc.CONF_AUTHTOKEN: "123",
hmipc.CONF_NAME: "name",
}
assert (
await async_setup_component(hass, hmipc.DOMAIN, {hmipc.DOMAIN: entry_config})
is True
)
# no new config_entry created / still one config_entry
config_entries = hass.config_entries.async_entries(hmipc.DOMAIN)
assert len(config_entries) == 1
assert config_entries[0].data == {
"authtoken": "123",
"hapid": "ABC123",
"name": "name",
}
# config_enty updated with unique_id
assert config_entries[0].unique_id == "ABC123"
async def test_unload_entry(hass): async def test_unload_entry(hass):
"""Test being able to unload an entry.""" """Test being able to unload an entry."""
entry = MockConfigEntry( mock_config = {
domain=hmipc.DOMAIN, hmipc.HMIPC_AUTHTOKEN: "123",
data={ hmipc.HMIPC_HAPID: "ABC123",
hmipc.HMIPC_HAPID: "ABC123", hmipc.HMIPC_NAME: "name",
hmipc.HMIPC_AUTHTOKEN: "123", }
hmipc.HMIPC_NAME: "hmip", MockConfigEntry(domain=hmipc.DOMAIN, data=mock_config).add_to_hass(hass)
},
)
entry.add_to_hass(hass)
with patch.object(hmipc, "HomematicipHAP") as mock_hap: with patch.object(hmipc, "HomematicipHAP") as mock_hap:
instance = mock_hap.return_value instance = mock_hap.return_value
instance.async_setup.return_value = mock_coro(True) instance.async_setup = CoroutineMock(return_value=True)
instance.home.id = "1" instance.home.id = "1"
instance.home.modelType = "mock-type" instance.home.modelType = "mock-type"
instance.home.name = "mock-name" instance.home.name = "mock-name"
instance.home.currentAPVersion = "mock-ap-version" instance.home.currentAPVersion = "mock-ap-version"
instance.async_reset = CoroutineMock(return_value=True)
assert await async_setup_component(hass, hmipc.DOMAIN, {}) is True assert await async_setup_component(hass, hmipc.DOMAIN, {}) is True
assert len(mock_hap.return_value.mock_calls) >= 1 assert mock_hap.return_value.mock_calls[0][0] == "async_setup"
mock_hap.return_value.async_reset.return_value = mock_coro(True) assert hass.data[hmipc.DOMAIN]["ABC123"]
assert await hmipc.async_unload_entry(hass, entry) config_entries = hass.config_entries.async_entries(hmipc.DOMAIN)
assert len(mock_hap.return_value.async_reset.mock_calls) == 1 assert len(config_entries) == 1
await hass.config_entries.async_unload(config_entries[0].entry_id)
# entry is unloaded
assert hass.data[hmipc.DOMAIN] == {} assert hass.data[hmipc.DOMAIN] == {}