Glances config flow (#27221)

* Glances Integration with config flow

* Glances Integration with config flow

* fix description texts

* Glances Integration with config flow

* Glances Integration with config flow

* fix description texts

* update .coverage.py

* add codeowner

* add test_options

* Fixed typos, Added import, fixed tests

* sort imports

* remove commented code
This commit is contained in:
Rami Mosleh 2019-10-21 11:17:21 +03:00 committed by Fabian Affolter
parent 09acbc211c
commit 09f9875ccf
13 changed files with 569 additions and 130 deletions

View File

@ -253,6 +253,7 @@ omit =
homeassistant/components/github/sensor.py homeassistant/components/github/sensor.py
homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitlab_ci/sensor.py
homeassistant/components/gitter/sensor.py homeassistant/components/gitter/sensor.py
homeassistant/components/glances/__init__.py
homeassistant/components/glances/sensor.py homeassistant/components/glances/sensor.py
homeassistant/components/gntp/notify.py homeassistant/components/gntp/notify.py
homeassistant/components/goalfeed/* homeassistant/components/goalfeed/*

View File

@ -110,7 +110,7 @@ homeassistant/components/geniushub/* @zxdavb
homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geo_rss_events/* @exxamalte
homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/gntp/* @robbiet480 homeassistant/components/gntp/* @robbiet480
homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_assistant/* @home-assistant/cloud
homeassistant/components/google_cloud/* @lufton homeassistant/components/google_cloud/* @lufton

View File

@ -0,0 +1,37 @@
{
"config": {
"title": "Glances",
"step": {
"user": {
"title": "Setup Glances",
"data": {
"name": "Name",
"host": "Host",
"username": "Username",
"password": "Password",
"port": "Port",
"version": "Glances API Version (2 or 3)",
"ssl": "Use SSL/TLS to connect to the Glances system",
"verify_ssl": "Verify the certification of the system"
}
}
},
"error": {
"cannot_connect": "Unable to connect to host",
"wrong_version": "Version not supported (2 or 3 only)"
},
"abort": {
"already_configured": "Host is already configured."
}
},
"options": {
"step": {
"init": {
"description": "Configure options for Glances",
"data": {
"scan_interval": "Update frequency"
}
}
}
}
}

View File

@ -1 +1,174 @@
"""The glances component.""" """The Glances component."""
from datetime import timedelta
import logging
from glances_api import Glances, exceptions
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import Config, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from .const import (
CONF_VERSION,
DATA_UPDATED,
DEFAULT_HOST,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_VERSION,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
GLANCES_SCHEMA = vol.All(
vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In([2, 3]),
}
)
)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [GLANCES_SCHEMA])}, extra=vol.ALLOW_EXTRA
)
async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Configure Glances using config flow only."""
if DOMAIN in config:
for entry in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=entry
)
)
return True
async def async_setup_entry(hass, config_entry):
"""Set up Glances from config entry."""
client = GlancesData(hass, config_entry)
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = client
if not await client.async_setup():
return False
return True
async def async_unload_entry(hass, config_entry):
"""Unload a config entry."""
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
hass.data[DOMAIN].pop(config_entry.entry_id)
return True
class GlancesData:
"""Get the latest data from Glances api."""
def __init__(self, hass, config_entry):
"""Initialize the Glances data."""
self.hass = hass
self.config_entry = config_entry
self.api = None
self.unsub_timer = None
self.available = False
@property
def host(self):
"""Return client host."""
return self.config_entry.data[CONF_HOST]
async def async_update(self):
"""Get the latest data from the Glances REST API."""
try:
await self.api.get_data()
self.available = True
except exceptions.GlancesApiError:
_LOGGER.error("Unable to fetch data from Glances")
self.available = False
_LOGGER.debug("Glances data updated")
async_dispatcher_send(self.hass, DATA_UPDATED)
async def async_setup(self):
"""Set up the Glances client."""
try:
self.api = get_api(self.hass, self.config_entry.data)
await self.api.get_data()
self.available = True
_LOGGER.debug("Successfully connected to Glances")
except exceptions.GlancesApiConnectionError:
_LOGGER.debug("Can not connect to Glances")
raise ConfigEntryNotReady
self.add_options()
self.set_scan_interval(self.config_entry.options[CONF_SCAN_INTERVAL])
self.config_entry.add_update_listener(self.async_options_updated)
self.hass.async_create_task(
self.hass.config_entries.async_forward_entry_setup(
self.config_entry, "sensor"
)
)
return True
def add_options(self):
"""Add options for Glances integration."""
if not self.config_entry.options:
options = {CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL}
self.hass.config_entries.async_update_entry(
self.config_entry, options=options
)
def set_scan_interval(self, scan_interval):
"""Update scan interval."""
async def refresh(event_time):
"""Get the latest data from Glances api."""
await self.async_update()
if self.unsub_timer is not None:
self.unsub_timer()
self.unsub_timer = async_track_time_interval(
self.hass, refresh, timedelta(seconds=scan_interval)
)
@staticmethod
async def async_options_updated(hass, entry):
"""Triggered by config entry options updates."""
hass.data[DOMAIN][entry.entry_id].set_scan_interval(
entry.options[CONF_SCAN_INTERVAL]
)
def get_api(hass, entry):
"""Return the api from glances_api."""
params = entry.copy()
params.pop(CONF_NAME)
verify_ssl = params.pop(CONF_VERIFY_SSL)
session = async_get_clientsession(hass, verify_ssl)
return Glances(hass.loop, session, **params)

View File

@ -0,0 +1,130 @@
"""Config flow for Glances."""
import glances_api
import voluptuous as vol
from homeassistant import config_entries, core, exceptions
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import callback
from . import get_api
from .const import (
CONF_VERSION,
DEFAULT_HOST,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SCAN_INTERVAL,
DEFAULT_VERSION,
DOMAIN,
SUPPORTED_VERSIONS,
)
DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME, default=DEFAULT_NAME): str,
vol.Required(CONF_HOST, default=DEFAULT_HOST): str,
vol.Optional(CONF_USERNAME): str,
vol.Optional(CONF_PASSWORD): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
vol.Required(CONF_VERSION, default=DEFAULT_VERSION): int,
vol.Optional(CONF_SSL, default=False): bool,
vol.Optional(CONF_VERIFY_SSL, default=False): bool,
}
)
async def validate_input(hass: core.HomeAssistant, data):
"""Validate the user input allows us to connect."""
for entry in hass.config_entries.async_entries(DOMAIN):
if entry.data[CONF_HOST] == data[CONF_HOST]:
raise AlreadyConfigured
if data[CONF_VERSION] not in SUPPORTED_VERSIONS:
raise WrongVersion
try:
api = get_api(hass, data)
await api.get_data()
except glances_api.exceptions.GlancesApiConnectionError:
raise CannotConnect
class GlancesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a Glances config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return GlancesOptionsFlowHandler(config_entry)
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
try:
await validate_input(self.hass, user_input)
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
except AlreadyConfigured:
return self.async_abort(reason="already_configured")
except CannotConnect:
errors["base"] = "cannot_connect"
except WrongVersion:
errors[CONF_VERSION] = "wrong_version"
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
async def async_step_import(self, import_config):
"""Import from Glances sensor config."""
return await self.async_step_user(user_input=import_config)
class GlancesOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle Glances client options."""
def __init__(self, config_entry):
"""Initialize Glances options flow."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Manage the Glances options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
options = {
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
): int
}
return self.async_show_form(step_id="init", data_schema=vol.Schema(options))
class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
class AlreadyConfigured(exceptions.HomeAssistantError):
"""Error to indicate host is already configured."""
class WrongVersion(exceptions.HomeAssistantError):
"""Error to indicate the selected version is wrong."""

View File

@ -0,0 +1,36 @@
"""Constants for Glances component."""
from homeassistant.const import TEMP_CELSIUS
DOMAIN = "glances"
CONF_VERSION = "version"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "Glances"
DEFAULT_PORT = 61208
DEFAULT_VERSION = 3
DEFAULT_SCAN_INTERVAL = 60
DATA_UPDATED = "glances_data_updated"
SUPPORTED_VERSIONS = [2, 3]
SENSOR_TYPES = {
"disk_use_percent": ["Disk used percent", "%", "mdi:harddisk"],
"disk_use": ["Disk used", "GiB", "mdi:harddisk"],
"disk_free": ["Disk free", "GiB", "mdi:harddisk"],
"memory_use_percent": ["RAM used percent", "%", "mdi:memory"],
"memory_use": ["RAM used", "MiB", "mdi:memory"],
"memory_free": ["RAM free", "MiB", "mdi:memory"],
"swap_use_percent": ["Swap used percent", "%", "mdi:memory"],
"swap_use": ["Swap used", "GiB", "mdi:memory"],
"swap_free": ["Swap free", "GiB", "mdi:memory"],
"processor_load": ["CPU load", "15 min", "mdi:memory"],
"process_running": ["Running", "Count", "mdi:memory"],
"process_total": ["Total", "Count", "mdi:memory"],
"process_thread": ["Thread", "Count", "mdi:memory"],
"process_sleeping": ["Sleeping", "Count", "mdi:memory"],
"cpu_use_percent": ["CPU used", "%", "mdi:memory"],
"cpu_temp": ["CPU Temp", TEMP_CELSIUS, "mdi:thermometer"],
"docker_active": ["Containers active", "", "mdi:docker"],
"docker_cpu_use": ["Containers CPU used", "%", "mdi:docker"],
"docker_memory_use": ["Containers RAM used", "MiB", "mdi:docker"],
}

View File

@ -1,12 +1,14 @@
{ {
"domain": "glances", "domain": "glances",
"name": "Glances", "name": "Glances",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/glances", "documentation": "https://www.home-assistant.io/integrations/glances",
"requirements": [ "requirements": [
"glances_api==0.2.0" "glances_api==0.2.0"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": [
"@fabaff" "@fabaff",
"@engrbm87"
] ]
} }

View File

@ -1,114 +1,31 @@
"""Support gathering system information of hosts which are running glances.""" """Support gathering system information of hosts which are running glances."""
from datetime import timedelta
import logging import logging
import voluptuous as vol from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE
from homeassistant.core import callback
from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SSL,
CONF_VERIFY_SSL,
CONF_RESOURCES,
STATE_UNAVAILABLE,
TEMP_CELSIUS,
)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_VERSION = "version"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "Glances"
DEFAULT_PORT = "61208"
DEFAULT_VERSION = 2
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
SENSOR_TYPES = {
"disk_use_percent": ["Disk used percent", "%", "mdi:harddisk"],
"disk_use": ["Disk used", "GiB", "mdi:harddisk"],
"disk_free": ["Disk free", "GiB", "mdi:harddisk"],
"memory_use_percent": ["RAM used percent", "%", "mdi:memory"],
"memory_use": ["RAM used", "MiB", "mdi:memory"],
"memory_free": ["RAM free", "MiB", "mdi:memory"],
"swap_use_percent": ["Swap used percent", "%", "mdi:memory"],
"swap_use": ["Swap used", "GiB", "mdi:memory"],
"swap_free": ["Swap free", "GiB", "mdi:memory"],
"processor_load": ["CPU load", "15 min", "mdi:memory"],
"process_running": ["Running", "Count", "mdi:memory"],
"process_total": ["Total", "Count", "mdi:memory"],
"process_thread": ["Thread", "Count", "mdi:memory"],
"process_sleeping": ["Sleeping", "Count", "mdi:memory"],
"cpu_use_percent": ["CPU used", "%", "mdi:memory"],
"cpu_temp": ["CPU Temp", TEMP_CELSIUS, "mdi:thermometer"],
"docker_active": ["Containers active", "", "mdi:docker"],
"docker_cpu_use": ["Containers CPU used", "%", "mdi:docker"],
"docker_memory_use": ["Containers RAM used", "MiB", "mdi:docker"],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
vol.Optional(CONF_RESOURCES, default=["disk_use"]): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
),
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): vol.In([2, 3]),
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Glances sensors is done through async_setup_entry."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Glances sensors.""" """Set up the Glances sensors."""
from glances_api import Glances
name = config[CONF_NAME]
host = config[CONF_HOST]
port = config[CONF_PORT]
version = config[CONF_VERSION]
var_conf = config[CONF_RESOURCES]
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
ssl = config[CONF_SSL]
verify_ssl = config[CONF_VERIFY_SSL]
session = async_get_clientsession(hass, verify_ssl)
glances = GlancesData(
Glances(
hass.loop,
session,
host=host,
port=port,
version=version,
username=username,
password=password,
ssl=ssl,
)
)
await glances.async_update()
if glances.api.data is None:
raise PlatformNotReady
glances_data = hass.data[DOMAIN][config_entry.entry_id]
name = config_entry.data[CONF_NAME]
dev = [] dev = []
for resource in var_conf: for sensor_type in SENSOR_TYPES:
dev.append(GlancesSensor(glances, name, resource)) dev.append(
GlancesSensor(glances_data, name, SENSOR_TYPES[sensor_type][0], sensor_type)
)
async_add_entities(dev, True) async_add_entities(dev, True)
@ -116,9 +33,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
class GlancesSensor(Entity): class GlancesSensor(Entity):
"""Implementation of a Glances sensor.""" """Implementation of a Glances sensor."""
def __init__(self, glances, name, sensor_type): def __init__(self, glances_data, name, sensor_name, sensor_type):
"""Initialize the sensor.""" """Initialize the sensor."""
self.glances = glances self.glances_data = glances_data
self._sensor_name = sensor_name
self._name = name self._name = name
self.type = sensor_type self.type = sensor_type
self._state = None self._state = None
@ -127,7 +45,12 @@ class GlancesSensor(Entity):
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return "{} {}".format(self._name, SENSOR_TYPES[self.type][0]) return f"{self._name} {self._sensor_name}"
@property
def unique_id(self):
"""Set unique_id for sensor."""
return f"{self.glances_data.host}-{self.name}"
@property @property
def icon(self): def icon(self):
@ -142,17 +65,31 @@ class GlancesSensor(Entity):
@property @property
def available(self): def available(self):
"""Could the device be accessed during the last update call.""" """Could the device be accessed during the last update call."""
return self.glances.available return self.glances_data.available
@property @property
def state(self): def state(self):
"""Return the state of the resources.""" """Return the state of the resources."""
return self._state return self._state
@property
def should_poll(self):
"""Return the polling requirement for this sensor."""
return False
async def async_added_to_hass(self):
"""Handle entity which will be added."""
async_dispatcher_connect(
self.hass, DATA_UPDATED, self._schedule_immediate_update
)
@callback
def _schedule_immediate_update(self):
self.async_schedule_update_ha_state(True)
async def async_update(self): async def async_update(self):
"""Get the latest data from REST API.""" """Get the latest data from REST API."""
await self.glances.async_update() value = self.glances_data.api.data
value = self.glances.api.data
if value is not None: if value is not None:
if self.type == "disk_use_percent": if self.type == "disk_use_percent":
@ -249,24 +186,3 @@ class GlancesSensor(Entity):
self._state = round(mem_use / 1024 ** 2, 1) self._state = round(mem_use / 1024 ** 2, 1)
except KeyError: except KeyError:
self._state = STATE_UNAVAILABLE self._state = STATE_UNAVAILABLE
class GlancesData:
"""The class for handling the data retrieval."""
def __init__(self, api):
"""Initialize the data object."""
self.api = api
self.available = True
@Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self):
"""Get the latest data from the Glances REST API."""
from glances_api.exceptions import GlancesApiError
try:
await self.api.get_data()
self.available = True
except GlancesApiError:
_LOGGER.error("Unable to fetch data from Glances")
self.available = False

View File

@ -0,0 +1,37 @@
{
"config": {
"title": "Glances",
"step": {
"user": {
"title": "Setup Glances",
"data": {
"name": "Name",
"host": "Host",
"username": "Username",
"password": "Password",
"port": "Port",
"version": "Glances API Version (2 or 3)",
"ssl": "Use SSL/TLS to connect to the Glances system",
"verify_ssl": "Verify the certification of the system"
}
}
},
"error": {
"cannot_connect": "Unable to connect to host",
"wrong_version": "Version not supported (2 or 3 only)"
},
"abort": {
"already_configured": "Host is already configured."
}
},
"options": {
"step": {
"init": {
"description": "Configure options for Glances",
"data": {
"scan_interval": "Update frequency"
}
}
}
}
}

View File

@ -22,6 +22,7 @@ FLOWS = [
"esphome", "esphome",
"geofency", "geofency",
"geonetnz_quakes", "geonetnz_quakes",
"glances",
"gpslogger", "gpslogger",
"hangouts", "hangouts",
"heos", "heos",

View File

@ -208,6 +208,9 @@ georss_qld_bushfire_alert_client==0.3
# homeassistant.components.nmap_tracker # homeassistant.components.nmap_tracker
getmac==0.8.1 getmac==0.8.1
# homeassistant.components.glances
glances_api==0.2.0
# homeassistant.components.google # homeassistant.components.google
google-api-python-client==1.6.4 google-api-python-client==1.6.4

View File

@ -0,0 +1 @@
"""Tests for Glances."""

View File

@ -0,0 +1,102 @@
"""Tests for Glances config flow."""
from unittest.mock import patch
from glances_api import Glances
from homeassistant.components.glances import config_flow
from homeassistant.components.glances.const import DOMAIN
from homeassistant.const import CONF_SCAN_INTERVAL
from tests.common import MockConfigEntry, mock_coro
NAME = "Glances"
HOST = "0.0.0.0"
USERNAME = "username"
PASSWORD = "password"
PORT = 61208
VERSION = 3
SCAN_INTERVAL = 10
DEMO_USER_INPUT = {
"name": NAME,
"host": HOST,
"username": USERNAME,
"password": PASSWORD,
"version": VERSION,
"port": PORT,
"ssl": False,
"verify_ssl": True,
}
def init_config_flow(hass):
"""Init a configuration flow."""
flow = config_flow.GlancesFlowHandler()
flow.hass = hass
return flow
async def test_form(hass):
"""Test config entry configured successfully."""
flow = init_config_flow(hass)
with patch("glances_api.Glances"), patch.object(
Glances, "get_data", return_value=mock_coro()
):
result = await flow.async_step_user(DEMO_USER_INPUT)
assert result["type"] == "create_entry"
assert result["title"] == NAME
assert result["data"] == DEMO_USER_INPUT
async def test_form_cannot_connect(hass):
"""Test to return error if we cannot connect."""
flow = init_config_flow(hass)
with patch("glances_api.Glances"):
result = await flow.async_step_user(DEMO_USER_INPUT)
assert result["type"] == "form"
assert result["errors"] == {"base": "cannot_connect"}
async def test_form_wrong_version(hass):
"""Test to check if wrong version is entered."""
flow = init_config_flow(hass)
user_input = DEMO_USER_INPUT.copy()
user_input.update(version=1)
result = await flow.async_step_user(user_input)
assert result["type"] == "form"
assert result["errors"] == {"version": "wrong_version"}
async def test_form_already_configured(hass):
"""Test host is already configured."""
entry = MockConfigEntry(
domain=DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60}
)
entry.add_to_hass(hass)
flow = init_config_flow(hass)
result = await flow.async_step_user(DEMO_USER_INPUT)
assert result["type"] == "abort"
assert result["reason"] == "already_configured"
async def test_options(hass):
"""Test options for Glances."""
entry = MockConfigEntry(
domain=DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60}
)
entry.add_to_hass(hass)
flow = init_config_flow(hass)
options_flow = flow.async_get_options_flow(entry)
result = await options_flow.async_step_init({CONF_SCAN_INTERVAL: 10})
assert result["type"] == "create_entry"
assert result["data"][CONF_SCAN_INTERVAL] == 10