mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Add Ondilo ico integration (#44728)
* First implementationof Ondilo component support * Update manifest toadd pypi pkg dependency * Update entities name and corrected refresh issue * Changed percentage unit name * Corrected merge issues * Updated coveragerc * cleaned up code and corrected config flow tests * Code cleanup and added test for exisitng entry * Changes following PR comments: - Inherit CoordinatorEntity instead of Entity - Merged pools blocking calls into one - Renamed devices vars to sensors - Check supported sensor types - Stop relying on array index position for pools - Stop relying on attribute position in dict for sensors * Corrected unit test * Reformat sensor type check
This commit is contained in:
parent
c92353088c
commit
de780c6d35
@ -625,6 +625,11 @@ omit =
|
|||||||
homeassistant/components/omnilogic/__init__.py
|
homeassistant/components/omnilogic/__init__.py
|
||||||
homeassistant/components/omnilogic/common.py
|
homeassistant/components/omnilogic/common.py
|
||||||
homeassistant/components/omnilogic/sensor.py
|
homeassistant/components/omnilogic/sensor.py
|
||||||
|
homeassistant/components/ondilo_ico/__init__.py
|
||||||
|
homeassistant/components/ondilo_ico/api.py
|
||||||
|
homeassistant/components/ondilo_ico/const.py
|
||||||
|
homeassistant/components/ondilo_ico/oauth_impl.py
|
||||||
|
homeassistant/components/ondilo_ico/sensor.py
|
||||||
homeassistant/components/onkyo/media_player.py
|
homeassistant/components/onkyo/media_player.py
|
||||||
homeassistant/components/onvif/__init__.py
|
homeassistant/components/onvif/__init__.py
|
||||||
homeassistant/components/onvif/base.py
|
homeassistant/components/onvif/base.py
|
||||||
|
@ -319,6 +319,7 @@ homeassistant/components/ohmconnect/* @robbiet480
|
|||||||
homeassistant/components/ombi/* @larssont
|
homeassistant/components/ombi/* @larssont
|
||||||
homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
||||||
homeassistant/components/onboarding/* @home-assistant/core
|
homeassistant/components/onboarding/* @home-assistant/core
|
||||||
|
homeassistant/components/ondilo_ico/* @JeromeHXP
|
||||||
homeassistant/components/onewire/* @garbled1 @epenet
|
homeassistant/components/onewire/* @garbled1 @epenet
|
||||||
homeassistant/components/onvif/* @hunterjm
|
homeassistant/components/onvif/* @hunterjm
|
||||||
homeassistant/components/openerz/* @misialq
|
homeassistant/components/openerz/* @misialq
|
||||||
|
59
homeassistant/components/ondilo_ico/__init__.py
Normal file
59
homeassistant/components/ondilo_ico/__init__.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
"""The Ondilo ICO integration."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
from . import api, config_flow
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .oauth_impl import OndiloOauth2Implementation
|
||||||
|
|
||||||
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up the Ondilo ICO component."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Ondilo ICO from a config entry."""
|
||||||
|
|
||||||
|
config_flow.OAuth2FlowHandler.async_register_implementation(
|
||||||
|
hass,
|
||||||
|
OndiloOauth2Implementation(hass),
|
||||||
|
)
|
||||||
|
|
||||||
|
implementation = (
|
||||||
|
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||||
|
hass, entry
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = api.OndiloClient(hass, entry, implementation)
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
33
homeassistant/components/ondilo_ico/api.py
Normal file
33
homeassistant/components/ondilo_ico/api.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""API for Ondilo ICO bound to Home Assistant OAuth."""
|
||||||
|
from asyncio import run_coroutine_threadsafe
|
||||||
|
|
||||||
|
from ondilo import Ondilo
|
||||||
|
|
||||||
|
from homeassistant import config_entries, core
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
|
||||||
|
class OndiloClient(Ondilo):
|
||||||
|
"""Provide Ondilo ICO authentication tied to an OAuth2 based config entry."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: core.HomeAssistant,
|
||||||
|
config_entry: config_entries.ConfigEntry,
|
||||||
|
implementation: config_entry_oauth2_flow.AbstractOAuth2Implementation,
|
||||||
|
):
|
||||||
|
"""Initialize Ondilo ICO Auth."""
|
||||||
|
self.hass = hass
|
||||||
|
self.config_entry = config_entry
|
||||||
|
self.session = config_entry_oauth2_flow.OAuth2Session(
|
||||||
|
hass, config_entry, implementation
|
||||||
|
)
|
||||||
|
super().__init__(self.session.token)
|
||||||
|
|
||||||
|
def refresh_tokens(self) -> dict:
|
||||||
|
"""Refresh and return new Ondilo ICO tokens using Home Assistant OAuth2 session."""
|
||||||
|
run_coroutine_threadsafe(
|
||||||
|
self.session.async_ensure_token_valid(), self.hass.loop
|
||||||
|
).result()
|
||||||
|
|
||||||
|
return self.session.token
|
43
homeassistant/components/ondilo_ico/config_flow.py
Normal file
43
homeassistant/components/ondilo_ico/config_flow.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""Config flow for Ondilo ICO."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .oauth_impl import OndiloOauth2Implementation
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2FlowHandler(
|
||||||
|
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||||
|
):
|
||||||
|
"""Config flow to handle Ondilo ICO OAuth2 authentication."""
|
||||||
|
|
||||||
|
DOMAIN = DOMAIN
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
await self.async_set_unique_id(DOMAIN)
|
||||||
|
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
self.async_register_implementation(
|
||||||
|
self.hass,
|
||||||
|
OndiloOauth2Implementation(self.hass),
|
||||||
|
)
|
||||||
|
|
||||||
|
return await super().async_step_user(user_input)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logger(self) -> logging.Logger:
|
||||||
|
"""Return logger."""
|
||||||
|
return logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_authorize_data(self) -> dict:
|
||||||
|
"""Extra data that needs to be appended to the authorize url."""
|
||||||
|
return {"scope": "api"}
|
8
homeassistant/components/ondilo_ico/const.py
Normal file
8
homeassistant/components/ondilo_ico/const.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""Constants for the Ondilo ICO integration."""
|
||||||
|
|
||||||
|
DOMAIN = "ondilo_ico"
|
||||||
|
|
||||||
|
OAUTH2_AUTHORIZE = "https://interop.ondilo.com/oauth2/authorize"
|
||||||
|
OAUTH2_TOKEN = "https://interop.ondilo.com/oauth2/token"
|
||||||
|
OAUTH2_CLIENTID = "customer_api"
|
||||||
|
OAUTH2_CLIENTSECRET = ""
|
18
homeassistant/components/ondilo_ico/manifest.json
Normal file
18
homeassistant/components/ondilo_ico/manifest.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"domain": "ondilo_ico",
|
||||||
|
"name": "Ondilo ICO",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/ondilo_ico",
|
||||||
|
"requirements": [
|
||||||
|
"ondilo==0.2.0"
|
||||||
|
],
|
||||||
|
"ssdp": [],
|
||||||
|
"zeroconf": [],
|
||||||
|
"homekit": {},
|
||||||
|
"dependencies": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"codeowners": [
|
||||||
|
"@JeromeHXP"
|
||||||
|
]
|
||||||
|
}
|
32
homeassistant/components/ondilo_ico/oauth_impl.py
Normal file
32
homeassistant/components/ondilo_ico/oauth_impl.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Local implementation of OAuth2 specific to Ondilo to hard code client id and secret and return a proper name."""
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.config_entry_oauth2_flow import LocalOAuth2Implementation
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
OAUTH2_AUTHORIZE,
|
||||||
|
OAUTH2_CLIENTID,
|
||||||
|
OAUTH2_CLIENTSECRET,
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OndiloOauth2Implementation(LocalOAuth2Implementation):
|
||||||
|
"""Local implementation of OAuth2 specific to Ondilo to hard code client id and secret and return a proper name."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant):
|
||||||
|
"""Just init default class with default values."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
OAUTH2_CLIENTID,
|
||||||
|
OAUTH2_CLIENTSECRET,
|
||||||
|
OAUTH2_AUTHORIZE,
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Name of the implementation."""
|
||||||
|
return "Ondilo"
|
185
homeassistant/components/ondilo_ico/sensor.py
Normal file
185
homeassistant/components/ondilo_ico/sensor.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"""Platform for sensor integration."""
|
||||||
|
import asyncio
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ondilo import OndiloError
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
DEVICE_CLASS_BATTERY,
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
PERCENTAGE,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
UpdateFailed,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
SENSOR_TYPES = {
|
||||||
|
"temperature": [
|
||||||
|
"Temperature",
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
"mdi:thermometer",
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
],
|
||||||
|
"orp": ["Oxydo Reduction Potential", "mV", "mdi:pool", None],
|
||||||
|
"ph": ["pH", "", "mdi:pool", None],
|
||||||
|
"tds": ["TDS", CONCENTRATION_PARTS_PER_MILLION, "mdi:pool", None],
|
||||||
|
"battery": ["Battery", PERCENTAGE, "mdi:battery", DEVICE_CLASS_BATTERY],
|
||||||
|
"rssi": [
|
||||||
|
"RSSI",
|
||||||
|
PERCENTAGE,
|
||||||
|
"mdi:wifi-strength-2",
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
],
|
||||||
|
"salt": ["Salt", "mg/L", "mdi:pool", None],
|
||||||
|
}
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(hours=1)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
|
"""Set up the Ondilo ICO sensors."""
|
||||||
|
|
||||||
|
api = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
def get_all_pool_data(pool):
|
||||||
|
"""Add pool details and last measures to pool data."""
|
||||||
|
pool["ICO"] = api.get_ICO_details(pool["id"])
|
||||||
|
pool["sensors"] = api.get_last_pool_measures(pool["id"])
|
||||||
|
|
||||||
|
return pool
|
||||||
|
|
||||||
|
async def async_update_data():
|
||||||
|
"""Fetch data from API endpoint.
|
||||||
|
|
||||||
|
This is the place to pre-process the data to lookup tables
|
||||||
|
so entities can quickly look up their data.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pools = await hass.async_add_executor_job(api.get_pools)
|
||||||
|
|
||||||
|
return await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.async_add_executor_job(get_all_pool_data, pool)
|
||||||
|
for pool in pools
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
except OndiloError as err:
|
||||||
|
raise UpdateFailed(f"Error communicating with API: {err}") from err
|
||||||
|
|
||||||
|
coordinator = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
# Name of the data. For logging purposes.
|
||||||
|
name="sensor",
|
||||||
|
update_method=async_update_data,
|
||||||
|
# Polling interval. Will only be polled if there are subscribers.
|
||||||
|
update_interval=SCAN_INTERVAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch initial data so we have data when entities subscribe
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for poolidx, pool in enumerate(coordinator.data):
|
||||||
|
for sensor_idx, sensor in enumerate(pool["sensors"]):
|
||||||
|
if sensor["data_type"] in SENSOR_TYPES:
|
||||||
|
entities.append(OndiloICO(coordinator, poolidx, sensor_idx))
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class OndiloICO(CoordinatorEntity):
|
||||||
|
"""Representation of a Sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, coordinator: DataUpdateCoordinator, poolidx: int, sensor_idx: int
|
||||||
|
):
|
||||||
|
"""Initialize sensor entity with data from coordinator."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
|
||||||
|
self._poolid = self.coordinator.data[poolidx]["id"]
|
||||||
|
|
||||||
|
pooldata = self._pooldata()
|
||||||
|
self._data_type = pooldata["sensors"][sensor_idx]["data_type"]
|
||||||
|
self._unique_id = f"{pooldata['ICO']['serial_number']}-{self._data_type}"
|
||||||
|
self._device_name = pooldata["name"]
|
||||||
|
self._name = f"{self._device_name} {SENSOR_TYPES[self._data_type][0]}"
|
||||||
|
self._device_class = SENSOR_TYPES[self._data_type][3]
|
||||||
|
self._icon = SENSOR_TYPES[self._data_type][2]
|
||||||
|
self._unit = SENSOR_TYPES[self._data_type][1]
|
||||||
|
|
||||||
|
def _pooldata(self):
|
||||||
|
"""Get pool data dict."""
|
||||||
|
return next(
|
||||||
|
(pool for pool in self.coordinator.data if pool["id"] == self._poolid),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _devdata(self):
|
||||||
|
"""Get device data dict."""
|
||||||
|
return next(
|
||||||
|
(
|
||||||
|
data_type
|
||||||
|
for data_type in self._pooldata()["sensors"]
|
||||||
|
if data_type["data_type"] == self._data_type
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Name of the sensor."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Last value of the sensor."""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Retrieving Ondilo sensor %s state value: %s",
|
||||||
|
self._name,
|
||||||
|
self._devdata()["value"],
|
||||||
|
)
|
||||||
|
return self._devdata()["value"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Icon to use in the frontend, if any."""
|
||||||
|
return self._icon
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self):
|
||||||
|
"""Return the device class of the sensor."""
|
||||||
|
return self._device_class
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the Unit of the sensor's measurement."""
|
||||||
|
return self._unit
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the unique ID of this entity."""
|
||||||
|
return self._unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info for the sensor."""
|
||||||
|
pooldata = self._pooldata()
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, pooldata["ICO"]["serial_number"])},
|
||||||
|
"name": self._device_name,
|
||||||
|
"manufacturer": "Ondilo",
|
||||||
|
"model": "ICO",
|
||||||
|
"sw_version": pooldata["ICO"]["sw_version"],
|
||||||
|
}
|
17
homeassistant/components/ondilo_ico/strings.json
Normal file
17
homeassistant/components/ondilo_ico/strings.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"title": "Ondilo ICO",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"pick_implementation": {
|
||||||
|
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]"
|
||||||
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
homeassistant/components/ondilo_ico/translations/en.json
Normal file
17
homeassistant/components/ondilo_ico/translations/en.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"authorize_url_timeout": "Timeout generating authorize URL.",
|
||||||
|
"missing_configuration": "The component is not configured. Please follow the documentation."
|
||||||
|
},
|
||||||
|
"create_entry": {
|
||||||
|
"default": "Successfully authenticated"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"pick_implementation": {
|
||||||
|
"title": "Pick Authentication Method"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Ondilo ICO"
|
||||||
|
}
|
@ -140,6 +140,7 @@ FLOWS = [
|
|||||||
"nws",
|
"nws",
|
||||||
"nzbget",
|
"nzbget",
|
||||||
"omnilogic",
|
"omnilogic",
|
||||||
|
"ondilo_ico",
|
||||||
"onewire",
|
"onewire",
|
||||||
"onvif",
|
"onvif",
|
||||||
"opentherm_gw",
|
"opentherm_gw",
|
||||||
|
@ -1033,6 +1033,9 @@ oemthermostat==1.1.1
|
|||||||
# homeassistant.components.omnilogic
|
# homeassistant.components.omnilogic
|
||||||
omnilogic==0.4.2
|
omnilogic==0.4.2
|
||||||
|
|
||||||
|
# homeassistant.components.ondilo_ico
|
||||||
|
ondilo==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.onkyo
|
# homeassistant.components.onkyo
|
||||||
onkyo-eiscp==1.2.7
|
onkyo-eiscp==1.2.7
|
||||||
|
|
||||||
|
@ -513,6 +513,9 @@ objgraph==3.4.1
|
|||||||
# homeassistant.components.omnilogic
|
# homeassistant.components.omnilogic
|
||||||
omnilogic==0.4.2
|
omnilogic==0.4.2
|
||||||
|
|
||||||
|
# homeassistant.components.ondilo_ico
|
||||||
|
ondilo==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==1.0.0
|
onvif-zeep-async==1.0.0
|
||||||
|
|
||||||
|
1
tests/components/ondilo_ico/__init__.py
Normal file
1
tests/components/ondilo_ico/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Ondilo ICO integration."""
|
88
tests/components/ondilo_ico/test_config_flow.py
Normal file
88
tests/components/ondilo_ico/test_config_flow.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
"""Test the Ondilo ICO config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
|
from homeassistant.components.ondilo_ico import config_flow
|
||||||
|
from homeassistant.components.ondilo_ico.const import (
|
||||||
|
DOMAIN,
|
||||||
|
OAUTH2_AUTHORIZE,
|
||||||
|
OAUTH2_CLIENTID,
|
||||||
|
OAUTH2_CLIENTSECRET,
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||||
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
CLIENT_ID = OAUTH2_CLIENTID
|
||||||
|
CLIENT_SECRET = OAUTH2_CLIENTSECRET
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_if_existing_entry(hass):
|
||||||
|
"""Check flow abort when an entry already exist."""
|
||||||
|
MockConfigEntry(domain=DOMAIN).add_to_hass(hass)
|
||||||
|
|
||||||
|
flow = config_flow.OAuth2FlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "single_instance_allowed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_full_flow(
|
||||||
|
hass, aiohttp_client, aioclient_mock, current_request_with_host
|
||||||
|
):
|
||||||
|
"""Check full flow."""
|
||||||
|
assert await setup.async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: {CONF_CLIENT_ID: CLIENT_ID, CONF_CLIENT_SECRET: CLIENT_SECRET},
|
||||||
|
"http": {"base_url": "https://example.com"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"redirect_uri": "https://example.com/auth/external/callback",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["url"] == (
|
||||||
|
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
|
||||||
|
"&redirect_uri=https://example.com/auth/external/callback"
|
||||||
|
f"&state={state}"
|
||||||
|
"&scope=api"
|
||||||
|
)
|
||||||
|
|
||||||
|
client = await aiohttp_client(hass.http.app)
|
||||||
|
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
aioclient_mock.post(
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
json={
|
||||||
|
"refresh_token": "mock-refresh-token",
|
||||||
|
"access_token": "mock-access-token",
|
||||||
|
"type": "Bearer",
|
||||||
|
"expires_in": 60,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.ondilo_ico.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup:
|
||||||
|
await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
Loading…
x
Reference in New Issue
Block a user