diff --git a/.coveragerc b/.coveragerc index 7071312a99c21d58fb9e1ea186e1c00809b2e20e..7c35022c355a43f013184d61c81c6663f13c24a7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -253,6 +253,7 @@ omit = homeassistant/components/github/sensor.py homeassistant/components/gitlab_ci/sensor.py homeassistant/components/gitter/sensor.py + homeassistant/components/glances/__init__.py homeassistant/components/glances/sensor.py homeassistant/components/gntp/notify.py homeassistant/components/goalfeed/* diff --git a/CODEOWNERS b/CODEOWNERS index 6e254c5a1d4688541052e1fa83fc024d51fd75fc..e1ff7b36ff154a78b88a31b3cd6a5b1f199f3d6b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -110,7 +110,7 @@ homeassistant/components/geniushub/* @zxdavb homeassistant/components/geo_rss_events/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/gitter/* @fabaff -homeassistant/components/glances/* @fabaff +homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/gntp/* @robbiet480 homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_cloud/* @lufton diff --git a/homeassistant/components/glances/.translations/en.json b/homeassistant/components/glances/.translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..1bd7275daeff156bdcc1aacb4f4dcc570d3a84dd --- /dev/null +++ b/homeassistant/components/glances/.translations/en.json @@ -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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/glances/__init__.py b/homeassistant/components/glances/__init__.py index b458d8788fcf7eca76370c16d8a834257c068d79..d09aa78253474d45720f32564e3e37e9d443cc31 100644 --- a/homeassistant/components/glances/__init__.py +++ b/homeassistant/components/glances/__init__.py @@ -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) diff --git a/homeassistant/components/glances/config_flow.py b/homeassistant/components/glances/config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..3c86fae0357350bd37e3fa944155d5b76223fd77 --- /dev/null +++ b/homeassistant/components/glances/config_flow.py @@ -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.""" diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py new file mode 100644 index 0000000000000000000000000000000000000000..e47586ea245b5ce0a34d96e5737b77d32a5a54ac --- /dev/null +++ b/homeassistant/components/glances/const.py @@ -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"], +} diff --git a/homeassistant/components/glances/manifest.json b/homeassistant/components/glances/manifest.json index 775d208c1c48e524449369a5e2c0023312f7a331..6067b1a986860d131302604888fcda5b27e65889 100644 --- a/homeassistant/components/glances/manifest.json +++ b/homeassistant/components/glances/manifest.json @@ -1,12 +1,14 @@ { "domain": "glances", "name": "Glances", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/glances", "requirements": [ "glances_api==0.2.0" ], "dependencies": [], "codeowners": [ - "@fabaff" + "@fabaff", + "@engrbm87" ] -} +} \ No newline at end of file diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 90b4b386f37e52bdd43f2b591c32e4e8fd3d0a54..760958f0dee0da8a05d742bf57399ca59d16728f 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -1,114 +1,31 @@ """Support gathering system information of hosts which are running glances.""" -from datetime import timedelta import logging -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -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.const import CONF_NAME, STATE_UNAVAILABLE +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle - -_LOGGER = logging.getLogger(__name__) - -CONF_VERSION = "version" -DEFAULT_HOST = "localhost" -DEFAULT_NAME = "Glances" -DEFAULT_PORT = "61208" -DEFAULT_VERSION = 2 +from .const import DATA_UPDATED, DOMAIN, SENSOR_TYPES -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]), - } -) +_LOGGER = logging.getLogger(__name__) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """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, - ) - ) + """Set up the Glances sensors is done through async_setup_entry.""" + pass - await glances.async_update() - if glances.api.data is None: - raise PlatformNotReady +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up the Glances sensors.""" + glances_data = hass.data[DOMAIN][config_entry.entry_id] + name = config_entry.data[CONF_NAME] dev = [] - for resource in var_conf: - dev.append(GlancesSensor(glances, name, resource)) + for sensor_type in SENSOR_TYPES: + dev.append( + GlancesSensor(glances_data, name, SENSOR_TYPES[sensor_type][0], sensor_type) + ) async_add_entities(dev, True) @@ -116,9 +33,10 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= class GlancesSensor(Entity): """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.""" - self.glances = glances + self.glances_data = glances_data + self._sensor_name = sensor_name self._name = name self.type = sensor_type self._state = None @@ -127,7 +45,12 @@ class GlancesSensor(Entity): @property def name(self): """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 def icon(self): @@ -142,17 +65,31 @@ class GlancesSensor(Entity): @property def available(self): """Could the device be accessed during the last update call.""" - return self.glances.available + return self.glances_data.available @property def state(self): """Return the state of the resources.""" 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): """Get the latest data from REST API.""" - await self.glances.async_update() - value = self.glances.api.data + value = self.glances_data.api.data if value is not None: if self.type == "disk_use_percent": @@ -249,24 +186,3 @@ class GlancesSensor(Entity): self._state = round(mem_use / 1024 ** 2, 1) except KeyError: 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 diff --git a/homeassistant/components/glances/strings.json b/homeassistant/components/glances/strings.json new file mode 100644 index 0000000000000000000000000000000000000000..1bd7275daeff156bdcc1aacb4f4dcc570d3a84dd --- /dev/null +++ b/homeassistant/components/glances/strings.json @@ -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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 664e83fba33aeab72f74e13c0bb32080d51a1384..60aa610ec073f4a1ab270999fe83ea52e23dc9d1 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -22,6 +22,7 @@ FLOWS = [ "esphome", "geofency", "geonetnz_quakes", + "glances", "gpslogger", "hangouts", "heos", diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7912de963abbf4a7339ec31d1de4f0a3ba049869..754a7d72a64c025cee30dfe44d065d1abfd43137 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -208,6 +208,9 @@ georss_qld_bushfire_alert_client==0.3 # homeassistant.components.nmap_tracker getmac==0.8.1 +# homeassistant.components.glances +glances_api==0.2.0 + # homeassistant.components.google google-api-python-client==1.6.4 diff --git a/tests/components/glances/__init__.py b/tests/components/glances/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..488265f970b3234105695fdbfba332440122c955 --- /dev/null +++ b/tests/components/glances/__init__.py @@ -0,0 +1 @@ +"""Tests for Glances.""" diff --git a/tests/components/glances/test_config_flow.py b/tests/components/glances/test_config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..e5be52e6b33783bc5925ce4fbb35b929e9a60f76 --- /dev/null +++ b/tests/components/glances/test_config_flow.py @@ -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