Add Flick Electric NZ integration (#30696)
* Add integration for Flick Electric NZ * Start adding Config Flow and external API * Second Wave of Config Flow and API implementation * Fix test (errors is None instead of blank array?) * Don't update sensor if price is still valid * Add input validation * Fix linting for DOMAIN Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * Remove platform schema (config is by entries only) * Don't catch AbortFlow exception Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * Update test * Re-arrange try-catch in config flow * Fix linting in sensor.py * Staticly define list of components * Fix test exceptions * Fix _validate_input not being awaited * Fix tests * Fix pylint logger * Rename test and remove print debug * Add test for duplicate entry * Don't format string in log function * Add tests __init__ file * Remove duplicate result assignment * Add test for generic exception handling * Move translations folder * Simplify testing * Fix strings/translation * Move to "flick_electric" as domain Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
0cf1ca7736
commit
e2b622fb78
14 changed files with 464 additions and 0 deletions
|
@ -233,6 +233,9 @@ omit =
|
||||||
homeassistant/components/fleetgo/device_tracker.py
|
homeassistant/components/fleetgo/device_tracker.py
|
||||||
homeassistant/components/flexit/climate.py
|
homeassistant/components/flexit/climate.py
|
||||||
homeassistant/components/flic/binary_sensor.py
|
homeassistant/components/flic/binary_sensor.py
|
||||||
|
homeassistant/components/flick_electric/__init__.py
|
||||||
|
homeassistant/components/flick_electric/const.py
|
||||||
|
homeassistant/components/flick_electric/sensor.py
|
||||||
homeassistant/components/flock/notify.py
|
homeassistant/components/flock/notify.py
|
||||||
homeassistant/components/flume/*
|
homeassistant/components/flume/*
|
||||||
homeassistant/components/flunearyou/__init__.py
|
homeassistant/components/flunearyou/__init__.py
|
||||||
|
|
|
@ -125,6 +125,7 @@ homeassistant/components/file/* @fabaff
|
||||||
homeassistant/components/filter/* @dgomes
|
homeassistant/components/filter/* @dgomes
|
||||||
homeassistant/components/fitbit/* @robbiet480
|
homeassistant/components/fitbit/* @robbiet480
|
||||||
homeassistant/components/fixer/* @fabaff
|
homeassistant/components/fixer/* @fabaff
|
||||||
|
homeassistant/components/flick_electric/* @ZephireNZ
|
||||||
homeassistant/components/flock/* @fabaff
|
homeassistant/components/flock/* @fabaff
|
||||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||||
homeassistant/components/flunearyou/* @bachya
|
homeassistant/components/flunearyou/* @bachya
|
||||||
|
|
102
homeassistant/components/flick_electric/__init__.py
Normal file
102
homeassistant/components/flick_electric/__init__.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
"""The Flick Electric integration."""
|
||||||
|
|
||||||
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
from pyflick import FlickAPI
|
||||||
|
from pyflick.authentication import AbstractFlickAuth
|
||||||
|
from pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_CLIENT_ID,
|
||||||
|
CONF_CLIENT_SECRET,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
|
from .const import CONF_TOKEN_EXPIRES_IN, CONF_TOKEN_EXPIRY, DOMAIN
|
||||||
|
|
||||||
|
CONF_ID_TOKEN = "id_token"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up the Flick Electric component."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Set up Flick Electric from a config entry."""
|
||||||
|
auth = HassFlickAuth(hass, entry)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = FlickAPI(auth)
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, "sensor")
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
if await hass.config_entries.async_forward_entry_unload(entry, "sensor"):
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class HassFlickAuth(AbstractFlickAuth):
|
||||||
|
"""Implementation of AbstractFlickAuth based on a Home Assistant entity config."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Flick authention based on a Home Assistant entity config."""
|
||||||
|
super().__init__(aiohttp_client.async_get_clientsession(hass))
|
||||||
|
self._entry = entry
|
||||||
|
self._hass = hass
|
||||||
|
|
||||||
|
async def _get_entry_token(self):
|
||||||
|
# No token saved, generate one
|
||||||
|
if (
|
||||||
|
CONF_TOKEN_EXPIRY not in self._entry.data
|
||||||
|
or CONF_ACCESS_TOKEN not in self._entry.data
|
||||||
|
):
|
||||||
|
await self._update_token()
|
||||||
|
|
||||||
|
# Token is expired, generate a new one
|
||||||
|
if self._entry.data[CONF_TOKEN_EXPIRY] <= dt.now().timestamp():
|
||||||
|
await self._update_token()
|
||||||
|
|
||||||
|
return self._entry.data[CONF_ACCESS_TOKEN]
|
||||||
|
|
||||||
|
async def _update_token(self):
|
||||||
|
token = await self.get_new_token(
|
||||||
|
username=self._entry.data[CONF_USERNAME],
|
||||||
|
password=self._entry.data[CONF_PASSWORD],
|
||||||
|
client_id=self._entry.data.get(CONF_CLIENT_ID, DEFAULT_CLIENT_ID),
|
||||||
|
client_secret=self._entry.data.get(
|
||||||
|
CONF_CLIENT_SECRET, DEFAULT_CLIENT_SECRET
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Reduce expiry by an hour to avoid API being called after expiry
|
||||||
|
expiry = dt.now().timestamp() + int(token[CONF_TOKEN_EXPIRES_IN] - 3600)
|
||||||
|
|
||||||
|
self._hass.config_entries.async_update_entry(
|
||||||
|
self._entry,
|
||||||
|
data={
|
||||||
|
**self._entry.data,
|
||||||
|
CONF_ACCESS_TOKEN: token,
|
||||||
|
CONF_TOKEN_EXPIRY: expiry,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_get_access_token(self):
|
||||||
|
"""Get Access Token from HASS Storage."""
|
||||||
|
token = await self._get_entry_token()
|
||||||
|
|
||||||
|
return token[CONF_ID_TOKEN]
|
92
homeassistant/components/flick_electric/config_flow.py
Normal file
92
homeassistant/components/flick_electric/config_flow.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
"""Config Flow for Flick Electric integration."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
from pyflick.authentication import AuthException, SimpleFlickAuth
|
||||||
|
from pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries, exceptions
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_CLIENT_ID,
|
||||||
|
CONF_CLIENT_SECRET,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_USERNAME,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
|
from .const import DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
vol.Optional(CONF_CLIENT_ID): str,
|
||||||
|
vol.Optional(CONF_CLIENT_SECRET): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FlickConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Flick config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
async def _validate_input(self, user_input):
|
||||||
|
auth = SimpleFlickAuth(
|
||||||
|
username=user_input[CONF_USERNAME],
|
||||||
|
password=user_input[CONF_PASSWORD],
|
||||||
|
websession=aiohttp_client.async_get_clientsession(self.hass),
|
||||||
|
client_id=user_input.get(CONF_CLIENT_ID, DEFAULT_CLIENT_ID),
|
||||||
|
client_secret=user_input.get(CONF_CLIENT_SECRET, DEFAULT_CLIENT_SECRET),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with async_timeout.timeout(60):
|
||||||
|
token = await auth.async_get_access_token()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
raise CannotConnect()
|
||||||
|
except AuthException:
|
||||||
|
raise InvalidAuth()
|
||||||
|
else:
|
||||||
|
return token is not None
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input):
|
||||||
|
"""Handle gathering login info."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
await self._validate_input(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"
|
||||||
|
else:
|
||||||
|
await self.async_set_unique_id(
|
||||||
|
f"flick_electric_{user_input[CONF_USERNAME]}"
|
||||||
|
)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=f"Flick Electric: {user_input[CONF_USERNAME]}",
|
||||||
|
data=user_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
11
homeassistant/components/flick_electric/const.py
Normal file
11
homeassistant/components/flick_electric/const.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
"""Constants for the Flick Electric integration."""
|
||||||
|
|
||||||
|
DOMAIN = "flick_electric"
|
||||||
|
|
||||||
|
CONF_TOKEN_EXPIRES_IN = "expires_in"
|
||||||
|
CONF_TOKEN_EXPIRY = "expires"
|
||||||
|
|
||||||
|
ATTR_START_AT = "start_at"
|
||||||
|
ATTR_END_AT = "end_at"
|
||||||
|
|
||||||
|
ATTR_COMPONENTS = ["retailer", "ea", "metering", "generation", "admin", "network"]
|
12
homeassistant/components/flick_electric/manifest.json
Normal file
12
homeassistant/components/flick_electric/manifest.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"domain": "flick_electric",
|
||||||
|
"name": "Flick Electric",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/flick_electric/",
|
||||||
|
"requirements": [
|
||||||
|
"PyFlick==0.0.2"
|
||||||
|
],
|
||||||
|
"codeowners": [
|
||||||
|
"@ZephireNZ"
|
||||||
|
]
|
||||||
|
}
|
83
homeassistant/components/flick_electric/sensor.py
Normal file
83
homeassistant/components/flick_electric/sensor.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
"""Support for Flick Electric Pricing data."""
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import async_timeout
|
||||||
|
from pyflick import FlickAPI, FlickPrice
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
|
from .const import ATTR_COMPONENTS, ATTR_END_AT, ATTR_START_AT, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_AUTH_URL = "https://api.flick.energy/identity/oauth/token"
|
||||||
|
_RESOURCE = "https://api.flick.energy/customer/mobile_provider/price"
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(minutes=5)
|
||||||
|
|
||||||
|
ATTRIBUTION = "Data provided by Flick Electric"
|
||||||
|
FRIENDLY_NAME = "Flick Power Price"
|
||||||
|
UNIT_NAME = "cents"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
||||||
|
):
|
||||||
|
"""Flick Sensor Setup."""
|
||||||
|
api: FlickAPI = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
|
async_add_entities([FlickPricingSensor(api)], True)
|
||||||
|
|
||||||
|
|
||||||
|
class FlickPricingSensor(Entity):
|
||||||
|
"""Entity object for Flick Electric sensor."""
|
||||||
|
|
||||||
|
def __init__(self, api: FlickAPI):
|
||||||
|
"""Entity object for Flick Electric sensor."""
|
||||||
|
self._api: FlickAPI = api
|
||||||
|
self._price: FlickPrice = None
|
||||||
|
self._attributes = {
|
||||||
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
|
ATTR_FRIENDLY_NAME: FRIENDLY_NAME,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the sensor."""
|
||||||
|
return FRIENDLY_NAME
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the sensor."""
|
||||||
|
return self._price.price
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self):
|
||||||
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
|
return UNIT_NAME
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes."""
|
||||||
|
return self._attributes
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Get the Flick Pricing data from the web service."""
|
||||||
|
if self._price and self._price.end_at >= utcnow():
|
||||||
|
return # Power price data is still valid
|
||||||
|
|
||||||
|
with async_timeout.timeout(60):
|
||||||
|
self._price = await self._api.getPricing()
|
||||||
|
|
||||||
|
self._attributes[ATTR_START_AT] = self._price.start_at
|
||||||
|
self._attributes[ATTR_END_AT] = self._price.end_at
|
||||||
|
for component in self._price.components:
|
||||||
|
if component.charge_setter not in ATTR_COMPONENTS:
|
||||||
|
_LOGGER.warning("Found unknown component: %s", component.charge_setter)
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._attributes[component.charge_setter] = float(component.value)
|
24
homeassistant/components/flick_electric/strings.json
Normal file
24
homeassistant/components/flick_electric/strings.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "Flick Electric",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Flick Login Credentials",
|
||||||
|
"data": {
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"client_id": "Client ID (Optional)",
|
||||||
|
"client_secret": "Client Secret (Optional)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "That account is already configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
homeassistant/components/flick_electric/translations/en.json
Normal file
24
homeassistant/components/flick_electric/translations/en.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "Flick Electric",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Flick Login Credentials",
|
||||||
|
"data": {
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"client_id": "Client ID (Optional)",
|
||||||
|
"client_secret": "Client Secret (Optional)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "That account is already configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ FLOWS = [
|
||||||
"elkm1",
|
"elkm1",
|
||||||
"emulated_roku",
|
"emulated_roku",
|
||||||
"esphome",
|
"esphome",
|
||||||
|
"flick_electric",
|
||||||
"flume",
|
"flume",
|
||||||
"flunearyou",
|
"flunearyou",
|
||||||
"freebox",
|
"freebox",
|
||||||
|
|
|
@ -46,6 +46,9 @@ OPi.GPIO==0.4.0
|
||||||
# homeassistant.components.essent
|
# homeassistant.components.essent
|
||||||
PyEssent==0.13
|
PyEssent==0.13
|
||||||
|
|
||||||
|
# homeassistant.components.flick_electric
|
||||||
|
PyFlick==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.github
|
# homeassistant.components.github
|
||||||
PyGithub==1.43.8
|
PyGithub==1.43.8
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
HAP-python==2.8.3
|
HAP-python==2.8.3
|
||||||
|
|
||||||
|
# homeassistant.components.flick_electric
|
||||||
|
PyFlick==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.mobile_app
|
# homeassistant.components.mobile_app
|
||||||
# homeassistant.components.owntracks
|
# homeassistant.components.owntracks
|
||||||
PyNaCl==1.3.0
|
PyNaCl==1.3.0
|
||||||
|
|
1
tests/components/flick_electric/__init__.py
Normal file
1
tests/components/flick_electric/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Tests for the Flick Electric integration."""
|
104
tests/components/flick_electric/test_config_flow.py
Normal file
104
tests/components/flick_electric/test_config_flow.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
"""Test the Flick Electric config flow."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from asynctest import patch
|
||||||
|
from pyflick.authentication import AuthException
|
||||||
|
|
||||||
|
from homeassistant import config_entries, data_entry_flow, setup
|
||||||
|
from homeassistant.components.flick_electric.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
CONF = {CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"}
|
||||||
|
|
||||||
|
|
||||||
|
async def _flow_submit(hass):
|
||||||
|
return await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}, data=CONF,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token",
|
||||||
|
return_value="123456789abcdef",
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.flick_electric.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.flick_electric.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], CONF,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result2["title"] == "Flick Electric: test-username"
|
||||||
|
assert result2["data"] == CONF
|
||||||
|
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_duplicate_login(hass):
|
||||||
|
"""Test uniqueness of username."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=CONF,
|
||||||
|
title="Flick Electric: test-username",
|
||||||
|
unique_id="flick_electric_test-username",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token",
|
||||||
|
return_value="123456789abcdef",
|
||||||
|
):
|
||||||
|
result = await _flow_submit(hass)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass):
|
||||||
|
"""Test we handle invalid auth."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token",
|
||||||
|
side_effect=AuthException,
|
||||||
|
):
|
||||||
|
result = await _flow_submit(hass)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token",
|
||||||
|
side_effect=asyncio.TimeoutError,
|
||||||
|
):
|
||||||
|
result = await _flow_submit(hass)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_generic_exception(hass):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.flick_electric.config_flow.SimpleFlickAuth.async_get_access_token",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result = await _flow_submit(hass)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": "unknown"}
|
Loading…
Add table
Reference in a new issue