diff --git a/.coveragerc b/.coveragerc index 851922e4f3a99db1c638a64da173cc5187d97499..c41d5afe169c6dcefb9d44f2b20b2b39810dc977 100644 --- a/.coveragerc +++ b/.coveragerc @@ -219,6 +219,7 @@ omit = homeassistant/components/flic/binary_sensor.py homeassistant/components/flock/notify.py homeassistant/components/flume/* + homeassistant/components/flunearyou/__init__.py homeassistant/components/flunearyou/sensor.py homeassistant/components/flux_led/light.py homeassistant/components/folder/sensor.py diff --git a/homeassistant/components/flunearyou/.translations/en.json b/homeassistant/components/flunearyou/.translations/en.json new file mode 100644 index 0000000000000000000000000000000000000000..cd8c0d27c3636e13854d475e656248c4971fe5a4 --- /dev/null +++ b/homeassistant/components/flunearyou/.translations/en.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "These coordinates are already registered." + }, + "error": { + "general_error": "There was an unknown error." + }, + "step": { + "user": { + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + }, + "description": "Monitor user-based and CDC flu reports.", + "title": "Configure Flu Near You" + } + }, + "title": "Flu Near You" + } +} diff --git a/homeassistant/components/flunearyou/__init__.py b/homeassistant/components/flunearyou/__init__.py index 5657e646be509bed89b26644146615bc2fc8b99a..ce59c959133f5d42fe4f77ab37eb5b1b04565e28 100644 --- a/homeassistant/components/flunearyou/__init__.py +++ b/homeassistant/components/flunearyou/__init__.py @@ -1 +1,216 @@ """The flunearyou component.""" +import asyncio +from datetime import timedelta + +from pyflunearyou import Client +from pyflunearyou.errors import FluNearYouError +import voluptuous as vol + +from homeassistant.config_entries import SOURCE_IMPORT +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import aiohttp_client, config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.event import async_track_time_interval + +from .const import ( + CATEGORY_CDC_REPORT, + CATEGORY_USER_REPORT, + DATA_CLIENT, + DOMAIN, + LOGGER, + SENSORS, + TOPIC_UPDATE, +) + +DATA_LISTENER = "listener" + +DEFAULT_SCAN_INTERVAL = timedelta(minutes=30) + +CONFIG_SCHEMA = vol.Schema( + { + vol.Optional(DOMAIN): vol.Schema( + { + vol.Optional(CONF_LATITUDE): cv.latitude, + vol.Optional(CONF_LONGITUDE): cv.longitude, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +@callback +def async_get_api_category(sensor_type): + """Get the category that a particular sensor type belongs to.""" + try: + return next( + ( + category + for category, sensors in SENSORS.items() + for sensor in sensors + if sensor[0] == sensor_type + ) + ) + except StopIteration: + raise ValueError(f"Can't find category sensor type: {sensor_type}") + + +async def async_setup(hass, config): + """Set up the Flu Near You component.""" + hass.data[DOMAIN] = {DATA_CLIENT: {}, DATA_LISTENER: {}} + + if DOMAIN not in config: + return True + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data={ + CONF_LATITUDE: config[DOMAIN].get(CONF_LATITUDE, hass.config.latitude), + CONF_LONGITUDE: config[DOMAIN].get( + CONF_LATITUDE, hass.config.longitude + ), + }, + ) + ) + + return True + + +async def async_setup_entry(hass, config_entry): + """Set up Flu Near You as config entry.""" + websession = aiohttp_client.async_get_clientsession(hass) + + fny = FluNearYouData( + hass, + Client(websession), + config_entry.data.get(CONF_LATITUDE, hass.config.latitude), + config_entry.data.get(CONF_LONGITUDE, hass.config.longitude), + ) + + try: + await fny.async_update() + except FluNearYouError as err: + LOGGER.error("Error while setting up integration: %s", err) + raise ConfigEntryNotReady + + hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = fny + + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, "sensor") + ) + + async def refresh(event_time): + """Refresh data from Flu Near You.""" + await fny.async_update() + + hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval( + hass, refresh, DEFAULT_SCAN_INTERVAL + ) + + return True + + +async def async_unload_entry(hass, config_entry): + """Unload an Flu Near You config entry.""" + hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) + + remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) + remove_listener() + + await hass.config_entries.async_forward_entry_unload(config_entry, "sensor") + + return True + + +class FluNearYouData: + """Define a data object to retrieve info from Flu Near You.""" + + def __init__(self, hass, client, latitude, longitude): + """Initialize.""" + self._async_cancel_time_interval_listener = None + self._client = client + self._hass = hass + self.data = {} + self.latitude = latitude + self.longitude = longitude + + self._api_coros = { + CATEGORY_CDC_REPORT: self._client.cdc_reports.status_by_coordinates( + latitude, longitude + ), + CATEGORY_USER_REPORT: self._client.user_reports.status_by_coordinates( + latitude, longitude + ), + } + + self._api_category_count = { + CATEGORY_CDC_REPORT: 0, + CATEGORY_USER_REPORT: 0, + } + + self._api_category_locks = { + CATEGORY_CDC_REPORT: asyncio.Lock(), + CATEGORY_USER_REPORT: asyncio.Lock(), + } + + async def _async_get_data_from_api(self, api_category): + """Update and save data for a particular API category.""" + if self._api_category_count[api_category] == 0: + return + + try: + self.data[api_category] = await self._api_coros[api_category] + except FluNearYouError as err: + LOGGER.error("Unable to get %s data: %s", api_category, err) + self.data[api_category] = None + + async def _async_update_listener_action(self, now): + """Define an async_track_time_interval action to update data.""" + await self.async_update() + + @callback + def async_deregister_api_interest(self, sensor_type): + """Decrement the number of entities with data needs from an API category.""" + # If this deregistration should leave us with no registration at all, remove the + # time interval: + if sum(self._api_category_count.values()) == 0: + if self._async_cancel_time_interval_listener: + self._async_cancel_time_interval_listener() + self._async_cancel_time_interval_listener = None + return + + api_category = async_get_api_category(sensor_type) + self._api_category_count[api_category] -= 1 + + async def async_register_api_interest(self, sensor_type): + """Increment the number of entities with data needs from an API category.""" + # If this is the first registration we have, start a time interval: + if not self._async_cancel_time_interval_listener: + self._async_cancel_time_interval_listener = async_track_time_interval( + self._hass, self._async_update_listener_action, DEFAULT_SCAN_INTERVAL, + ) + + api_category = async_get_api_category(sensor_type) + self._api_category_count[api_category] += 1 + + # If a sensor registers interest in a particular API call and the data doesn't + # exist for it yet, make the API call and grab the data: + async with self._api_category_locks[api_category]: + if api_category not in self.data: + await self._async_get_data_from_api(api_category) + + async def async_update(self): + """Update Flu Near You data.""" + tasks = [ + self._async_get_data_from_api(api_category) + for api_category in self._api_coros + ] + + await asyncio.gather(*tasks) + + LOGGER.debug("Received new data") + async_dispatcher_send(self._hass, TOPIC_UPDATE) diff --git a/homeassistant/components/flunearyou/config_flow.py b/homeassistant/components/flunearyou/config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..2c48b14bb03329b966aba59134b526ae8dbde1b7 --- /dev/null +++ b/homeassistant/components/flunearyou/config_flow.py @@ -0,0 +1,60 @@ +"""Define a config flow manager for flunearyou.""" +from pyflunearyou import Client +from pyflunearyou.errors import FluNearYouError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE +from homeassistant.helpers import aiohttp_client, config_validation as cv + +from .const import DOMAIN, LOGGER # pylint: disable=unused-import + + +class FluNearYouFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle an FluNearYou config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + @property + def data_schema(self): + """Return the data schema for integration.""" + return vol.Schema( + { + vol.Required( + CONF_LATITUDE, default=self.hass.config.latitude + ): cv.latitude, + vol.Required( + CONF_LONGITUDE, default=self.hass.config.longitude + ): cv.longitude, + } + ) + + async def async_step_import(self, import_config): + """Import a config entry from configuration.yaml.""" + return await self.async_step_user(import_config) + + async def async_step_user(self, user_input=None): + """Handle the start of the config flow.""" + if not user_input: + return self.async_show_form(step_id="user", data_schema=self.data_schema) + + unique_id = f"{user_input[CONF_LATITUDE]}, {user_input[CONF_LONGITUDE]}" + + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + websession = aiohttp_client.async_get_clientsession(self.hass) + client = Client(websession) + + try: + await client.cdc_reports.status_by_coordinates( + user_input[CONF_LATITUDE], user_input[CONF_LONGITUDE] + ) + except FluNearYouError as err: + LOGGER.error("Error while setting up integration: %s", err) + return self.async_show_form( + step_id="user", errors={"base": "general_error"} + ) + + return self.async_create_entry(title=unique_id, data=user_input) diff --git a/homeassistant/components/flunearyou/const.py b/homeassistant/components/flunearyou/const.py new file mode 100644 index 0000000000000000000000000000000000000000..9693d59fc6e79ae88ecdb0caa0139b3b514195a2 --- /dev/null +++ b/homeassistant/components/flunearyou/const.py @@ -0,0 +1,38 @@ +"""Define flunearyou constants.""" +import logging + +DOMAIN = "flunearyou" +LOGGER = logging.getLogger("homeassistant.components.flunearyou") + +DATA_CLIENT = "client" + +CATEGORY_CDC_REPORT = "cdc_report" +CATEGORY_USER_REPORT = "user_report" + +TOPIC_UPDATE = "flunearyou_update" + +TYPE_CDC_LEVEL = "level" +TYPE_CDC_LEVEL2 = "level2" +TYPE_USER_CHICK = "chick" +TYPE_USER_DENGUE = "dengue" +TYPE_USER_FLU = "flu" +TYPE_USER_LEPTO = "lepto" +TYPE_USER_NO_SYMPTOMS = "none" +TYPE_USER_SYMPTOMS = "symptoms" +TYPE_USER_TOTAL = "total" + +SENSORS = { + CATEGORY_CDC_REPORT: [ + (TYPE_CDC_LEVEL, "CDC Level", "mdi:biohazard", None), + (TYPE_CDC_LEVEL2, "CDC Level 2", "mdi:biohazard", None), + ], + CATEGORY_USER_REPORT: [ + (TYPE_USER_CHICK, "Avian Flu Symptoms", "mdi:alert", "reports"), + (TYPE_USER_DENGUE, "Dengue Fever Symptoms", "mdi:alert", "reports"), + (TYPE_USER_FLU, "Flu Symptoms", "mdi:alert", "reports"), + (TYPE_USER_LEPTO, "Leptospirosis Symptoms", "mdi:alert", "reports"), + (TYPE_USER_NO_SYMPTOMS, "No Symptoms", "mdi:alert", "reports"), + (TYPE_USER_SYMPTOMS, "Flu-like Symptoms", "mdi:alert", "reports"), + (TYPE_USER_TOTAL, "Total Symptoms", "mdi:alert", "reports"), + ], +} diff --git a/homeassistant/components/flunearyou/manifest.json b/homeassistant/components/flunearyou/manifest.json index e7394356c64d1b6617deef0156302cbce2d24554..1a28c3076e71c928c6714cf4f54cba2bd89dbeab 100644 --- a/homeassistant/components/flunearyou/manifest.json +++ b/homeassistant/components/flunearyou/manifest.json @@ -1,8 +1,9 @@ { "domain": "flunearyou", "name": "Flu Near You", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/flunearyou", - "requirements": ["pyflunearyou==1.0.3"], + "requirements": ["pyflunearyou==1.0.7"], "dependencies": [], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/flunearyou/sensor.py b/homeassistant/components/flunearyou/sensor.py index e06eb3a8ef49dc8c1eea9949cbe120afb55ff8cf..6868d21ce1fcb96fdb5b8e1a22ca87c904a31ae1 100644 --- a/homeassistant/components/flunearyou/sensor.py +++ b/homeassistant/components/flunearyou/sensor.py @@ -1,25 +1,24 @@ """Support for user- and CDC-based flu info sensors from Flu Near You.""" -from datetime import timedelta -import logging - -from pyflunearyou import Client -from pyflunearyou.errors import FluNearYouError -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - ATTR_ATTRIBUTION, - ATTR_STATE, - CONF_LATITUDE, - CONF_LONGITUDE, - CONF_MONITORED_CONDITIONS, -) -from homeassistant.helpers import aiohttp_client -import homeassistant.helpers.config_validation as cv +from homeassistant.const import ATTR_ATTRIBUTION, ATTR_STATE +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__) +from .const import ( + CATEGORY_CDC_REPORT, + CATEGORY_USER_REPORT, + DATA_CLIENT, + DOMAIN, + SENSORS, + TOPIC_UPDATE, + TYPE_USER_CHICK, + TYPE_USER_DENGUE, + TYPE_USER_FLU, + TYPE_USER_LEPTO, + TYPE_USER_NO_SYMPTOMS, + TYPE_USER_SYMPTOMS, + TYPE_USER_TOTAL, +) ATTR_CITY = "city" ATTR_REPORTED_DATE = "reported_date" @@ -31,94 +30,46 @@ ATTR_ZIP_CODE = "zip_code" DEFAULT_ATTRIBUTION = "Data provided by Flu Near You" -MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) -SCAN_INTERVAL = timedelta(minutes=30) - -CATEGORY_CDC_REPORT = "cdc_report" -CATEGORY_USER_REPORT = "user_report" - -TYPE_CDC_LEVEL = "level" -TYPE_CDC_LEVEL2 = "level2" -TYPE_USER_CHICK = "chick" -TYPE_USER_DENGUE = "dengue" -TYPE_USER_FLU = "flu" -TYPE_USER_LEPTO = "lepto" -TYPE_USER_NO_SYMPTOMS = "none" -TYPE_USER_SYMPTOMS = "symptoms" -TYPE_USER_TOTAL = "total" - EXTENDED_TYPE_MAPPING = { TYPE_USER_FLU: "ili", TYPE_USER_NO_SYMPTOMS: "no_symptoms", TYPE_USER_TOTAL: "total_surveys", } -SENSORS = { - CATEGORY_CDC_REPORT: [ - (TYPE_CDC_LEVEL, "CDC Level", "mdi:biohazard", None), - (TYPE_CDC_LEVEL2, "CDC Level 2", "mdi:biohazard", None), - ], - CATEGORY_USER_REPORT: [ - (TYPE_USER_CHICK, "Avian Flu Symptoms", "mdi:alert", "reports"), - (TYPE_USER_DENGUE, "Dengue Fever Symptoms", "mdi:alert", "reports"), - (TYPE_USER_FLU, "Flu Symptoms", "mdi:alert", "reports"), - (TYPE_USER_LEPTO, "Leptospirosis Symptoms", "mdi:alert", "reports"), - (TYPE_USER_NO_SYMPTOMS, "No Symptoms", "mdi:alert", "reports"), - (TYPE_USER_SYMPTOMS, "Flu-like Symptoms", "mdi:alert", "reports"), - (TYPE_USER_TOTAL, "Total Symptoms", "mdi:alert", "reports"), - ], -} - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_LATITUDE): cv.latitude, - vol.Optional(CONF_LONGITUDE): cv.longitude, - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): vol.All( - cv.ensure_list, [vol.In(SENSORS)] - ), - } -) - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Configure the platform and add the sensors.""" - websession = aiohttp_client.async_get_clientsession(hass) - latitude = config.get(CONF_LATITUDE, hass.config.latitude) - longitude = config.get(CONF_LONGITUDE, hass.config.longitude) +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Flu Near You sensors based on a config entry.""" + fny = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] - fny = FluNearYouData( - Client(websession), latitude, longitude, config[CONF_MONITORED_CONDITIONS] + async_add_entities( + [ + FluNearYouSensor(fny, sensor_type, name, category, icon, unit) + for category, sensors in SENSORS.items() + for sensor_type, name, icon, unit in sensors + ], + True, ) - await fny.async_update() - - sensors = [ - FluNearYouSensor(fny, kind, name, category, icon, unit) - for category in config[CONF_MONITORED_CONDITIONS] - for kind, name, icon, unit in SENSORS[category] - ] - - async_add_entities(sensors, True) class FluNearYouSensor(Entity): """Define a base Flu Near You sensor.""" - def __init__(self, fny, kind, name, category, icon, unit): + def __init__(self, fny, sensor_type, name, category, icon, unit): """Initialize the sensor.""" + self._async_unsub_dispatcher_connect = None self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._category = category + self._fny = fny self._icon = icon - self._kind = kind self._name = name + self._sensor_type = sensor_type self._state = None self._unit = unit - self.fny = fny @property def available(self): """Return True if entity is available.""" - return bool(self.fny.data[self._category]) + return bool(self._fny.data[self._category]) @property def device_state_attributes(self): @@ -143,19 +94,43 @@ class FluNearYouSensor(Entity): @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return f"{self.fny.latitude},{self.fny.longitude}_{self._kind}" + return f"{self._fny.latitude},{self._fny.longitude}_{self._sensor_type}" @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" return self._unit - async def async_update(self): - """Update the sensor.""" - await self.fny.async_update() + async def async_added_to_hass(self): + """Register callbacks.""" + + @callback + def update(): + """Update the state.""" + self.update_from_latest_data() + self.async_write_ha_state() + + self._async_unsub_dispatcher_connect = async_dispatcher_connect( + self.hass, TOPIC_UPDATE, update + ) - cdc_data = self.fny.data.get(CATEGORY_CDC_REPORT) - user_data = self.fny.data.get(CATEGORY_USER_REPORT) + await self._fny.async_register_api_interest(self._sensor_type) + + self.update_from_latest_data() + + async def async_will_remove_from_hass(self) -> None: + """Disconnect dispatcher listener when removed.""" + if self._async_unsub_dispatcher_connect: + self._async_unsub_dispatcher_connect() + self._async_unsub_dispatcher_connect = None + + self._fny.async_deregister_api_interest(self._sensor_type) + + @callback + def update_from_latest_data(self): + """Update the sensor.""" + cdc_data = self._fny.data.get(CATEGORY_CDC_REPORT) + user_data = self._fny.data.get(CATEGORY_USER_REPORT) if self._category == CATEGORY_CDC_REPORT and cdc_data: self._attrs.update( @@ -164,7 +139,7 @@ class FluNearYouSensor(Entity): ATTR_STATE: cdc_data["name"], } ) - self._state = cdc_data[self._kind] + self._state = cdc_data[self._sensor_type] elif self._category == CATEGORY_USER_REPORT and user_data: self._attrs.update( { @@ -176,10 +151,10 @@ class FluNearYouSensor(Entity): } ) - if self._kind in user_data["state"]["data"]: - states_key = self._kind - elif self._kind in EXTENDED_TYPE_MAPPING: - states_key = EXTENDED_TYPE_MAPPING[self._kind] + if self._sensor_type in user_data["state"]["data"]: + states_key = self._sensor_type + elif self._sensor_type in EXTENDED_TYPE_MAPPING: + states_key = EXTENDED_TYPE_MAPPING[self._sensor_type] self._attrs[ATTR_STATE_REPORTS_THIS_WEEK] = user_data["state"]["data"][ states_key @@ -188,7 +163,7 @@ class FluNearYouSensor(Entity): "last_week_data" ][states_key] - if self._kind == TYPE_USER_TOTAL: + if self._sensor_type == TYPE_USER_TOTAL: self._state = sum( v for k, v in user_data["local"].items() @@ -202,32 +177,4 @@ class FluNearYouSensor(Entity): ) ) else: - self._state = user_data["local"][self._kind] - - -class FluNearYouData: - """Define a data object to retrieve info from Flu Near You.""" - - def __init__(self, client, latitude, longitude, sensor_types): - """Initialize.""" - self._client = client - self._sensor_types = sensor_types - self.data = {} - self.latitude = latitude - self.longitude = longitude - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def async_update(self): - """Update Flu Near You data.""" - for key, method in [ - (CATEGORY_CDC_REPORT, self._client.cdc_reports.status_by_coordinates), - (CATEGORY_USER_REPORT, self._client.user_reports.status_by_coordinates), - ]: - if key in self._sensor_types: - try: - self.data[key] = await method(self.latitude, self.longitude) - except FluNearYouError as err: - _LOGGER.error('There was an error with "%s" data: %s', key, err) - self.data[key] = {} - - _LOGGER.debug("New data stored: %s", self.data) + self._state = user_data["local"][self._sensor_type] diff --git a/homeassistant/components/flunearyou/strings.json b/homeassistant/components/flunearyou/strings.json new file mode 100644 index 0000000000000000000000000000000000000000..5539c6f82df7d6ce431c63792807b10b148273d0 --- /dev/null +++ b/homeassistant/components/flunearyou/strings.json @@ -0,0 +1,21 @@ +{ + "config": { + "title": "Flu Near You", + "step": { + "user": { + "title": "Configure Flu Near You", + "description": "Monitor user-based and CDC repots for a pair of coordinates.", + "data": { + "latitude": "Latitude", + "longitude": "Longitude" + } + } + }, + "error": { + "general_error": "There was an unknown error." + }, + "abort": { + "already_configured": "These coordinates are already registered." + } + } +} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index dd0342a06a31fa0b135d587707256be7fa18878b..1584d342db4f59e5e2cec247c5abc97a60869df9 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -31,6 +31,7 @@ FLOWS = [ "elkm1", "emulated_roku", "esphome", + "flunearyou", "freebox", "garmin_connect", "gdacs", diff --git a/requirements_all.txt b/requirements_all.txt index 95bdb6f7bc131c951093019d36c83645f0fe718e..a820ea3423d817885be7d8ced591ae99dede8c06 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1281,7 +1281,7 @@ pyflic-homeassistant==0.4.dev0 pyflume==0.3.0 # homeassistant.components.flunearyou -pyflunearyou==1.0.3 +pyflunearyou==1.0.7 # homeassistant.components.futurenow pyfnip==0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c7278c57b38bd704da53f5432153727ee604c4bf..9d050e7288b6e0ceb62ac1639aec9ed4d8da7ce3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -493,6 +493,9 @@ pyeverlights==0.1.0 # homeassistant.components.fido pyfido==2.1.1 +# homeassistant.components.flunearyou +pyflunearyou==1.0.7 + # homeassistant.components.fritzbox pyfritzhome==0.4.0 diff --git a/tests/components/flunearyou/__init__.py b/tests/components/flunearyou/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..21252facd759105b4fce07bc222c4e03b548cc24 --- /dev/null +++ b/tests/components/flunearyou/__init__.py @@ -0,0 +1 @@ +"""Define tests for the flunearyou component.""" diff --git a/tests/components/flunearyou/test_config_flow.py b/tests/components/flunearyou/test_config_flow.py new file mode 100644 index 0000000000000000000000000000000000000000..21fcb4798db38725d82bbc174c1a79e019be3c43 --- /dev/null +++ b/tests/components/flunearyou/test_config_flow.py @@ -0,0 +1,87 @@ +"""Define tests for the flunearyou config flow.""" +from asynctest import patch +from pyflunearyou.errors import FluNearYouError + +from homeassistant import data_entry_flow +from homeassistant.components.flunearyou import DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE + +from tests.common import MockConfigEntry + + +async def test_duplicate_error(hass): + """Test that an error is shown when duplicates are added.""" + conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"} + + MockConfigEntry( + domain=DOMAIN, unique_id="51.528308, -0.3817765", data=conf + ).add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + + +async def test_general_error(hass): + """Test that an error is shown on a library error.""" + conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"} + + with patch( + "pyflunearyou.cdc.CdcReport.status_by_coordinates", side_effect=FluNearYouError, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + assert result["errors"] == {"base": "general_error"} + + +async def test_show_form(hass): + """Test that the form is served with no input.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +async def test_step_import(hass): + """Test that the import step works.""" + conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"} + + with patch( + "homeassistant.components.flunearyou.async_setup_entry", return_value=True + ), patch("pyflunearyou.cdc.CdcReport.status_by_coordinates"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "51.528308, -0.3817765" + assert result["data"] == { + CONF_LATITUDE: "51.528308", + CONF_LONGITUDE: "-0.3817765", + } + + +async def test_step_user(hass): + """Test that the user step works.""" + conf = {CONF_LATITUDE: "51.528308", CONF_LONGITUDE: "-0.3817765"} + + with patch( + "homeassistant.components.flunearyou.async_setup_entry", return_value=True + ), patch("pyflunearyou.cdc.CdcReport.status_by_coordinates"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER}, data=conf + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "51.528308, -0.3817765" + assert result["data"] == { + CONF_LATITUDE: "51.528308", + CONF_LONGITUDE: "-0.3817765", + }