Rewrite JuiceNet for async and config flow (#34365)
* Add config flow to JuiceNet * Fix some lint issues * Fix imports * Abort on reconfigure / Allow multiple accounts Abort on bad API token Fix strings * Remove unused variable * Update strings * Remove import * Fix import order again * Update imports Remove some unused parameters * Add back ignore * Update config_flow.py * iSort * Update juicenet integration to be async * Update coverage for juicenet config flow * Update homeassistant/components/juicenet/entity.py Co-Authored-By: J. Nick Koston <nick@koston.org> * Black * Make imports relative * Rename translations folder Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
502afbe9c2
commit
e696c08db0
16 changed files with 480 additions and 101 deletions
|
@ -353,7 +353,12 @@ omit =
|
|||
homeassistant/components/itach/remote.py
|
||||
homeassistant/components/itunes/media_player.py
|
||||
homeassistant/components/joaoapps_join/*
|
||||
homeassistant/components/juicenet/*
|
||||
homeassistant/components/juicenet/__init__.py
|
||||
homeassistant/components/juicenet/const.py
|
||||
homeassistant/components/juicenet/device.py
|
||||
homeassistant/components/juicenet/entity.py
|
||||
homeassistant/components/juicenet/sensor.py
|
||||
homeassistant/components/juicenet/switch.py
|
||||
homeassistant/components/kaiterra/*
|
||||
homeassistant/components/kankun/switch.py
|
||||
homeassistant/components/keba/*
|
||||
|
|
|
@ -1,68 +1,115 @@
|
|||
"""Support for Juicenet cloud."""
|
||||
"""The JuiceNet integration."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import pyjuicenet
|
||||
import aiohttp
|
||||
from pyjuicenet import Api, TokenError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||
from .device import JuiceNetApi
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "juicenet"
|
||||
PLATFORMS = ["sensor", "switch"]
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{DOMAIN: vol.Schema({vol.Required(CONF_ACCESS_TOKEN): cv.string})},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
JUICENET_COMPONENTS = ["sensor", "switch"]
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the JuiceNet component."""
|
||||
conf = config.get(DOMAIN)
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
if not conf:
|
||||
return True
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Juicenet component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up JuiceNet from a config entry."""
|
||||
|
||||
access_token = config[DOMAIN].get(CONF_ACCESS_TOKEN)
|
||||
hass.data[DOMAIN]["api"] = pyjuicenet.Api(access_token)
|
||||
config = entry.data
|
||||
|
||||
for component in JUICENET_COMPONENTS:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
session = async_get_clientsession(hass)
|
||||
|
||||
access_token = config[CONF_ACCESS_TOKEN]
|
||||
api = Api(access_token, session)
|
||||
|
||||
juicenet = JuiceNetApi(api)
|
||||
|
||||
try:
|
||||
await juicenet.setup()
|
||||
except TokenError as error:
|
||||
_LOGGER.error("JuiceNet Error %s", error)
|
||||
return False
|
||||
except aiohttp.ClientError as error:
|
||||
_LOGGER.error("Could not reach the JuiceNet API %s", error)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
if not juicenet.devices:
|
||||
_LOGGER.error("No JuiceNet devices found for this account")
|
||||
return False
|
||||
_LOGGER.info("%d JuiceNet device(s) found", len(juicenet.devices))
|
||||
|
||||
async def async_update_data():
|
||||
"""Update all device states from the JuiceNet API."""
|
||||
for device in juicenet.devices:
|
||||
await device.update_state(True)
|
||||
return True
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name="JuiceNet",
|
||||
update_method=async_update_data,
|
||||
update_interval=timedelta(seconds=30),
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
JUICENET_API: juicenet,
|
||||
JUICENET_COORDINATOR: coordinator,
|
||||
}
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class JuicenetDevice(Entity):
|
||||
"""Represent a base Juicenet device."""
|
||||
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)
|
||||
|
||||
def __init__(self, device, sensor_type, hass):
|
||||
"""Initialise the sensor."""
|
||||
self.hass = hass
|
||||
self.device = device
|
||||
self.type = sensor_type
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self.device.name()
|
||||
|
||||
def update(self):
|
||||
"""Update state of the device."""
|
||||
self.device.update_state()
|
||||
|
||||
@property
|
||||
def _manufacturer_device_id(self):
|
||||
"""Return the manufacturer device id."""
|
||||
return self.device.id()
|
||||
|
||||
@property
|
||||
def _token(self):
|
||||
"""Return the device API token."""
|
||||
return self.device.token()
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return f"{self.device.id()}-{self.type}"
|
||||
return unload_ok
|
||||
|
|
79
homeassistant/components/juicenet/config_flow.py
Normal file
79
homeassistant/components/juicenet/config_flow.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
"""Config flow for JuiceNet integration."""
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
from pyjuicenet import Api, TokenError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, core, exceptions
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN # pylint:disable=unused-import
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_SCHEMA = vol.Schema({vol.Required(CONF_ACCESS_TOKEN): str})
|
||||
|
||||
|
||||
async def validate_input(hass: core.HomeAssistant, data):
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
session = async_get_clientsession(hass)
|
||||
juicenet = Api(data[CONF_ACCESS_TOKEN], session)
|
||||
|
||||
try:
|
||||
await juicenet.get_devices()
|
||||
except TokenError as error:
|
||||
_LOGGER.error("Token Error %s", error)
|
||||
raise InvalidAuth
|
||||
except aiohttp.ClientError as error:
|
||||
_LOGGER.error("Error connecting %s", error)
|
||||
raise CannotConnect
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": "JuiceNet"}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for JuiceNet."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
|
||||
await self.async_set_unique_id(user_input[CONF_ACCESS_TOKEN])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
except CannotConnect:
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_import(self, user_input):
|
||||
"""Handle import."""
|
||||
return await self.async_step_user(user_input)
|
||||
|
||||
|
||||
class CannotConnect(exceptions.HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidAuth(exceptions.HomeAssistantError):
|
||||
"""Error to indicate there is invalid auth."""
|
6
homeassistant/components/juicenet/const.py
Normal file
6
homeassistant/components/juicenet/const.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
"""Constants used by the JuiceNet component."""
|
||||
|
||||
DOMAIN = "juicenet"
|
||||
|
||||
JUICENET_API = "juicenet_api"
|
||||
JUICENET_COORDINATOR = "juicenet_coordinator"
|
23
homeassistant/components/juicenet/device.py
Normal file
23
homeassistant/components/juicenet/device.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Adapter to wrap the pyjuicenet api for home assistant."""
|
||||
|
||||
import logging
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JuiceNetApi:
|
||||
"""Represent a connection to JuiceNet."""
|
||||
|
||||
def __init__(self, api):
|
||||
"""Create an object from the provided API instance."""
|
||||
self.api = api
|
||||
self._devices = []
|
||||
|
||||
async def setup(self):
|
||||
"""JuiceNet device setup.""" # noqa: D403
|
||||
self._devices = await self.api.get_devices()
|
||||
|
||||
@property
|
||||
def devices(self) -> list:
|
||||
"""Get a list of devices managed by this account."""
|
||||
return self._devices
|
54
homeassistant/components/juicenet/entity.py
Normal file
54
homeassistant/components/juicenet/entity.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
"""Adapter to wrap the pyjuicenet api for home assistant."""
|
||||
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class JuiceNetDevice(Entity):
|
||||
"""Represent a base JuiceNet device."""
|
||||
|
||||
def __init__(self, device, sensor_type, coordinator):
|
||||
"""Initialise the sensor."""
|
||||
self.device = device
|
||||
self.type = sensor_type
|
||||
self.coordinator = coordinator
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self.device.name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return False, updates are controlled via coordinator."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self.coordinator.last_update_success
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the entity."""
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to updates."""
|
||||
self.async_on_remove(
|
||||
self.coordinator.async_add_listener(self.async_write_ha_state)
|
||||
)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return f"{self.device.id}-{self.type}"
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information about this JuiceNet Device."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self.device.id)},
|
||||
"name": self.device.name,
|
||||
"manufacturer": "JuiceNet",
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
"domain": "juicenet",
|
||||
"name": "JuiceNet",
|
||||
"documentation": "https://www.home-assistant.io/integrations/juicenet",
|
||||
"requirements": ["python-juicenet==0.1.6"],
|
||||
"codeowners": ["@jesserockz"]
|
||||
"requirements": ["python-juicenet==1.0.1"],
|
||||
"codeowners": ["@jesserockz"],
|
||||
"config_flow": true
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ from homeassistant.const import (
|
|||
)
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import DOMAIN, JuicenetDevice
|
||||
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||
from .entity import JuiceNetDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -25,38 +26,39 @@ SENSOR_TYPES = {
|
|||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Juicenet sensor."""
|
||||
api = hass.data[DOMAIN]["api"]
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the JuiceNet Sensors."""
|
||||
entities = []
|
||||
juicenet_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
api = juicenet_data[JUICENET_API]
|
||||
coordinator = juicenet_data[JUICENET_COORDINATOR]
|
||||
|
||||
dev = []
|
||||
for device in api.get_devices():
|
||||
for variable in SENSOR_TYPES:
|
||||
dev.append(JuicenetSensorDevice(device, variable, hass))
|
||||
|
||||
add_entities(dev)
|
||||
for device in api.devices:
|
||||
for sensor in SENSOR_TYPES:
|
||||
entities.append(JuiceNetSensorDevice(device, sensor, coordinator))
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class JuicenetSensorDevice(JuicenetDevice, Entity):
|
||||
"""Implementation of a Juicenet sensor."""
|
||||
class JuiceNetSensorDevice(JuiceNetDevice, Entity):
|
||||
"""Implementation of a JuiceNet sensor."""
|
||||
|
||||
def __init__(self, device, sensor_type, hass):
|
||||
def __init__(self, device, sensor_type, coordinator):
|
||||
"""Initialise the sensor."""
|
||||
super().__init__(device, sensor_type, hass)
|
||||
super().__init__(device, sensor_type, coordinator)
|
||||
self._name = SENSOR_TYPES[sensor_type][0]
|
||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return f"{self.device.name()} {self._name}"
|
||||
return f"{self.device.name} {self._name}"
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon of the sensor."""
|
||||
icon = None
|
||||
if self.type == "status":
|
||||
status = self.device.getStatus()
|
||||
status = self.device.status
|
||||
if status == "standby":
|
||||
icon = "mdi:power-plug-off"
|
||||
elif status == "plugged":
|
||||
|
@ -87,29 +89,19 @@ class JuicenetSensorDevice(JuicenetDevice, Entity):
|
|||
"""Return the state."""
|
||||
state = None
|
||||
if self.type == "status":
|
||||
state = self.device.getStatus()
|
||||
state = self.device.status
|
||||
elif self.type == "temperature":
|
||||
state = self.device.getTemperature()
|
||||
state = self.device.temperature
|
||||
elif self.type == "voltage":
|
||||
state = self.device.getVoltage()
|
||||
state = self.device.voltage
|
||||
elif self.type == "amps":
|
||||
state = self.device.getAmps()
|
||||
state = self.device.amps
|
||||
elif self.type == "watts":
|
||||
state = self.device.getWatts()
|
||||
state = self.device.watts
|
||||
elif self.type == "charge_time":
|
||||
state = self.device.getChargeTime()
|
||||
state = self.device.charge_time
|
||||
elif self.type == "energy_added":
|
||||
state = self.device.getEnergyAdded()
|
||||
state = self.device.energy_added
|
||||
else:
|
||||
state = "Unknown"
|
||||
return state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attributes = {}
|
||||
if self.type == "status":
|
||||
man_dev_id = self.device.id()
|
||||
if man_dev_id:
|
||||
attributes["manufacturer_device_id"] = man_dev_id
|
||||
return attributes
|
||||
|
|
21
homeassistant/components/juicenet/strings.json
Normal file
21
homeassistant/components/juicenet/strings.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This JuiceNet account is already configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect, please try again",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_token": "JuiceNet API Token"
|
||||
},
|
||||
"description": "You will need the API Token from https://home.juice.net/Manage.",
|
||||
"title": "Connect to JuiceNet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,43 +3,45 @@ import logging
|
|||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
|
||||
from . import DOMAIN, JuicenetDevice
|
||||
from .const import DOMAIN, JUICENET_API, JUICENET_COORDINATOR
|
||||
from .entity import JuiceNetDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Juicenet switch."""
|
||||
api = hass.data[DOMAIN]["api"]
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the JuiceNet switches."""
|
||||
entities = []
|
||||
juicenet_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
api = juicenet_data[JUICENET_API]
|
||||
coordinator = juicenet_data[JUICENET_COORDINATOR]
|
||||
|
||||
devs = []
|
||||
for device in api.get_devices():
|
||||
devs.append(JuicenetChargeNowSwitch(device, hass))
|
||||
|
||||
add_entities(devs)
|
||||
for device in api.devices:
|
||||
entities.append(JuiceNetChargeNowSwitch(device, coordinator))
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class JuicenetChargeNowSwitch(JuicenetDevice, SwitchEntity):
|
||||
"""Implementation of a Juicenet switch."""
|
||||
class JuiceNetChargeNowSwitch(JuiceNetDevice, SwitchEntity):
|
||||
"""Implementation of a JuiceNet switch."""
|
||||
|
||||
def __init__(self, device, hass):
|
||||
def __init__(self, device, coordinator):
|
||||
"""Initialise the switch."""
|
||||
super().__init__(device, "charge_now", hass)
|
||||
super().__init__(device, "charge_now", coordinator)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return f"{self.device.name()} Charge Now"
|
||||
return f"{self.device.name} Charge Now"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self.device.getOverrideTime() != 0
|
||||
return self.device.override_time != 0
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Charge now."""
|
||||
self.device.setOverride(True)
|
||||
await self.device.set_override(True)
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Don't charge now."""
|
||||
self.device.setOverride(False)
|
||||
await self.device.set_override(False)
|
||||
|
|
21
homeassistant/components/juicenet/translations/en.json
Normal file
21
homeassistant/components/juicenet/translations/en.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This JuiceNet account is already configured"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect, please try again",
|
||||
"invalid_auth": "Invalid authentication",
|
||||
"unknown": "Unexpected error"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_token": "JuiceNet API Token"
|
||||
},
|
||||
"description": "You will need the API Token from https://home.juice.net/Manage.",
|
||||
"title": "Connect to JuiceNet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ FLOWS = [
|
|||
"iqvia",
|
||||
"islamic_prayer_times",
|
||||
"izone",
|
||||
"juicenet",
|
||||
"konnected",
|
||||
"life360",
|
||||
"lifx",
|
||||
|
|
|
@ -1669,7 +1669,7 @@ python-izone==1.1.2
|
|||
python-join-api==0.0.4
|
||||
|
||||
# homeassistant.components.juicenet
|
||||
python-juicenet==0.1.6
|
||||
python-juicenet==1.0.1
|
||||
|
||||
# homeassistant.components.lirc
|
||||
# python-lirc==1.2.3
|
||||
|
|
|
@ -668,6 +668,9 @@ python-forecastio==1.4.0
|
|||
# homeassistant.components.izone
|
||||
python-izone==1.1.2
|
||||
|
||||
# homeassistant.components.juicenet
|
||||
python-juicenet==1.0.1
|
||||
|
||||
# homeassistant.components.xiaomi_miio
|
||||
python-miio==0.5.0.1
|
||||
|
||||
|
|
1
tests/components/juicenet/__init__.py
Normal file
1
tests/components/juicenet/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
"""Tests for the JuiceNet component."""
|
123
tests/components/juicenet/test_config_flow.py
Normal file
123
tests/components/juicenet/test_config_flow.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
"""Test the JuiceNet config flow."""
|
||||
import aiohttp
|
||||
from asynctest import patch
|
||||
from asynctest.mock import MagicMock
|
||||
from pyjuicenet import TokenError
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.juicenet.const import DOMAIN
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
|
||||
|
||||
def _mock_juicenet_return_value(get_devices=None):
|
||||
juicenet_mock = MagicMock()
|
||||
type(juicenet_mock).get_devices = MagicMock(return_value=get_devices)
|
||||
return juicenet_mock
|
||||
|
||||
|
||||
async def test_form(hass):
|
||||
"""Test we get the form."""
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == "form"
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||
return_value=MagicMock(),
|
||||
), patch(
|
||||
"homeassistant.components.juicenet.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.juicenet.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "create_entry"
|
||||
assert result2["title"] == "JuiceNet"
|
||||
assert result2["data"] == {CONF_ACCESS_TOKEN: "access_token"}
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass):
|
||||
"""Test we handle invalid auth."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||
side_effect=TokenError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_cannot_connect(hass):
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||
side_effect=aiohttp.ClientError,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_form_catch_unknown_errors(hass):
|
||||
"""Test we handle cannot connect error."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||
side_effect=Exception,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_ACCESS_TOKEN: "access_token"}
|
||||
)
|
||||
|
||||
assert result2["type"] == "form"
|
||||
assert result2["errors"] == {"base": "unknown"}
|
||||
|
||||
|
||||
async def test_import(hass):
|
||||
"""Test that import works as expected."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.juicenet.config_flow.Api.get_devices",
|
||||
return_value=MagicMock(),
|
||||
), patch(
|
||||
"homeassistant.components.juicenet.async_setup", return_value=True
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.juicenet.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_ACCESS_TOKEN: "access_token"},
|
||||
)
|
||||
|
||||
assert result["type"] == "create_entry"
|
||||
assert result["title"] == "JuiceNet"
|
||||
assert result["data"] == {CONF_ACCESS_TOKEN: "access_token"}
|
||||
await hass.async_block_till_done()
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
Loading…
Add table
Reference in a new issue