Add config flow to Meteo-France (#29927)

* Add config flow to Meteo-France

* Review 1

* Use config_entry.unique_id

* Fix config_flow _show_setup_form + init

* Remove empty *_setup_platform()

* Avoid HomeAssistantError: Entity id already exists: sensor.[city_name]_[sensor_type]. Platform meteo_france does not generate unique IDs

- when multiple district in one city

* Review + abort when API error

* Fix I/O

* Remove monitored_conditions

* Add async_unload_entry

* Review 3

* Fix pipe

* alert_watcher is already None

* Review 4

* Better fix for "Entity id already exists"

* Whoops, fix tests

* Fix string
This commit is contained in:
Quentame 2020-02-04 22:37:59 +01:00 committed by GitHub
parent 1efea50654
commit 201ea2557e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 389 additions and 155 deletions

View File

@ -413,7 +413,10 @@ omit =
homeassistant/components/mediaroom/media_player.py
homeassistant/components/message_bird/notify.py
homeassistant/components/met/weather.py
homeassistant/components/meteo_france/*
homeassistant/components/meteo_france/__init__.py
homeassistant/components/meteo_france/const.py
homeassistant/components/meteo_france/sensor.py
homeassistant/components/meteo_france/weather.py
homeassistant/components/meteoalarm/*
homeassistant/components/metoffice/sensor.py
homeassistant/components/metoffice/weather.py

View File

@ -206,7 +206,7 @@ homeassistant/components/mcp23017/* @jardiamj
homeassistant/components/mediaroom/* @dgomes
homeassistant/components/melissa/* @kennedyshead
homeassistant/components/met/* @danielhiversen
homeassistant/components/meteo_france/* @victorcerutti @oncleben31
homeassistant/components/meteo_france/* @victorcerutti @oncleben31 @Quentame
homeassistant/components/meteoalarm/* @rolfberkenbosch
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
homeassistant/components/mikrotik/* @engrbm87

View File

@ -0,0 +1,18 @@
{
"config": {
"abort": {
"already_configured": "City already configured",
"unknown": "Unknown error: please retry later"
},
"step": {
"user": {
"data": {
"city": "City"
},
"description": "Enter the postal code (only for France, recommended) or city name",
"title": "M\u00e9t\u00e9o-France"
}
},
"title": "M\u00e9t\u00e9o-France"
}
}

View File

@ -1,4 +1,5 @@
"""Support for Meteo-France weather data."""
import asyncio
import datetime
import logging
@ -6,116 +7,96 @@ from meteofrance.client import meteofranceClient, meteofranceError
from vigilancemeteo import VigilanceMeteoError, VigilanceMeteoFranceProxy
import voluptuous as vol
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util import Throttle
from .const import CONF_CITY, DATA_METEO_FRANCE, DOMAIN, SENSOR_TYPES
from .const import CONF_CITY, DOMAIN, PLATFORMS
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = datetime.timedelta(minutes=5)
def has_all_unique_cities(value):
"""Validate that all cities are unique."""
cities = [location[CONF_CITY] for location in value]
vol.Schema(vol.Unique())(cities)
return value
CITY_SCHEMA = vol.Schema({vol.Required(CONF_CITY): cv.string})
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Required(CONF_CITY): cv.string,
vol.Optional(CONF_MONITORED_CONDITIONS): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)]
),
}
)
],
has_all_unique_cities,
)
},
extra=vol.ALLOW_EXTRA,
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [CITY_SCHEMA]))}, extra=vol.ALLOW_EXTRA,
)
def setup(hass, config):
"""Set up the Meteo-France component."""
hass.data[DATA_METEO_FRANCE] = {}
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Set up Meteo-France from legacy config file."""
# Check if at least weather alert have to be monitored for one location.
need_weather_alert_watcher = False
for location in config[DOMAIN]:
if (
CONF_MONITORED_CONDITIONS in location
and "weather_alert" in location[CONF_MONITORED_CONDITIONS]
):
need_weather_alert_watcher = True
conf = config.get(DOMAIN)
if conf is None:
return True
# If weather alert monitoring is expected initiate a client to be used by
# all weather_alert entities.
if need_weather_alert_watcher:
_LOGGER.debug("Weather Alert monitoring expected. Loading vigilancemeteo")
weather_alert_client = VigilanceMeteoFranceProxy()
try:
weather_alert_client.update_data()
except VigilanceMeteoError as exp:
_LOGGER.error(
"Unexpected error when creating the vigilance_meteoFrance proxy: %s ",
exp,
for city_conf in conf:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=city_conf.copy()
)
else:
weather_alert_client = None
hass.data[DATA_METEO_FRANCE]["weather_alert_client"] = weather_alert_client
for location in config[DOMAIN]:
city = location[CONF_CITY]
try:
client = meteofranceClient(city)
except meteofranceError as exp:
_LOGGER.error(
"Unexpected error when creating the meteofrance proxy: %s", exp
)
return
client.need_rain_forecast = bool(
CONF_MONITORED_CONDITIONS in location
and "next_rain" in location[CONF_MONITORED_CONDITIONS]
)
hass.data[DATA_METEO_FRANCE][city] = MeteoFranceUpdater(client)
hass.data[DATA_METEO_FRANCE][city].update()
if CONF_MONITORED_CONDITIONS in location:
monitored_conditions = location[CONF_MONITORED_CONDITIONS]
_LOGGER.debug("meteo_france sensor platform loaded for %s", city)
load_platform(
hass,
"sensor",
DOMAIN,
{CONF_CITY: city, CONF_MONITORED_CONDITIONS: monitored_conditions},
config,
)
load_platform(hass, "weather", DOMAIN, {CONF_CITY: city}, config)
return True
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Set up an Meteo-France account from a config entry."""
hass.data.setdefault(DOMAIN, {})
# Weather alert
weather_alert_client = VigilanceMeteoFranceProxy()
try:
await hass.async_add_executor_job(weather_alert_client.update_data)
except VigilanceMeteoError as exp:
_LOGGER.error(
"Unexpected error when creating the vigilance_meteoFrance proxy: %s ", exp
)
return False
hass.data[DOMAIN]["weather_alert_client"] = weather_alert_client
# Weather
city = entry.data[CONF_CITY]
try:
client = await hass.async_add_executor_job(meteofranceClient, city)
except meteofranceError as exp:
_LOGGER.error("Unexpected error when creating the meteofrance proxy: %s", exp)
return False
hass.data[DOMAIN][city] = MeteoFranceUpdater(client)
await hass.async_add_executor_job(hass.data[DOMAIN][city].update)
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
_LOGGER.debug("meteo_france sensor platform loaded for %s", city)
return True
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN].pop(entry.data[CONF_CITY])
return unload_ok
class MeteoFranceUpdater:
"""Update data from Meteo-France."""
def __init__(self, client):
def __init__(self, client: meteofranceClient):
"""Initialize the data object."""
self._client = client

View File

@ -0,0 +1,62 @@
"""Config flow to configure the Meteo-France integration."""
import logging
from meteofrance.client import meteofranceClient, meteofranceError
import voluptuous as vol
from homeassistant import config_entries
from .const import CONF_CITY
from .const import DOMAIN # pylint: disable=unused-import
_LOGGER = logging.getLogger(__name__)
class MeteoFranceFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a Meteo-France config flow."""
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def _show_setup_form(self, user_input=None, errors=None):
"""Show the setup form to the user."""
if user_input is None:
user_input = {}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{vol.Required(CONF_CITY, default=user_input.get(CONF_CITY, "")): str}
),
errors=errors or {},
)
async def async_step_user(self, user_input=None):
"""Handle a flow initiated by the user."""
errors = {}
if user_input is None:
return self._show_setup_form(user_input, errors)
city = user_input[CONF_CITY] # Might be a city name or a postal code
city_name = None
try:
client = await self.hass.async_add_executor_job(meteofranceClient, city)
city_name = client.get_data()["name"]
except meteofranceError as exp:
_LOGGER.error(
"Unexpected error when creating the meteofrance proxy: %s", exp
)
return self.async_abort(reason="unknown")
# Check if already configured
await self.async_set_unique_id(city_name)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=city_name, data={CONF_CITY: city})
async def async_step_import(self, user_input):
"""Import a config entry."""
return await self.async_step_user(user_input)

View File

@ -3,7 +3,7 @@
from homeassistant.const import TEMP_CELSIUS
DOMAIN = "meteo_france"
DATA_METEO_FRANCE = "data_meteo_france"
PLATFORMS = ["sensor", "weather"]
ATTRIBUTION = "Data provided by Météo-France"
CONF_CITY = "city"

View File

@ -1,8 +1,9 @@
{
"domain": "meteo_france",
"name": "Météo-France",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/meteo_france",
"requirements": ["meteofrance==0.3.7", "vigilancemeteo==3.0.0"],
"dependencies": [],
"codeowners": ["@victorcerutti", "@oncleben31"]
"codeowners": ["@victorcerutti", "@oncleben31", "@Quentame"]
}

View File

@ -1,15 +1,18 @@
"""Support for Meteo-France raining forecast sensor."""
import logging
from vigilancemeteo import DepartmentWeatherAlert
from meteofrance.client import meteofranceClient
from vigilancemeteo import DepartmentWeatherAlert, VigilanceMeteoFranceProxy
from homeassistant.const import ATTR_ATTRIBUTION, CONF_MONITORED_CONDITIONS
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import HomeAssistantType
from .const import (
ATTRIBUTION,
CONF_CITY,
DATA_METEO_FRANCE,
DOMAIN,
SENSOR_TYPE_CLASS,
SENSOR_TYPE_ICON,
SENSOR_TYPE_NAME,
@ -23,52 +26,47 @@ STATE_ATTR_FORECAST = "1h rain forecast"
STATE_ATTR_BULLETIN_TIME = "Bulletin date"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Meteo-France sensor."""
if discovery_info is None:
return
city = discovery_info[CONF_CITY]
monitored_conditions = discovery_info[CONF_MONITORED_CONDITIONS]
client = hass.data[DATA_METEO_FRANCE][city]
weather_alert_client = hass.data[DATA_METEO_FRANCE]["weather_alert_client"]
async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
) -> None:
"""Set up the Meteo-France sensor platform."""
city = entry.data[CONF_CITY]
client = hass.data[DOMAIN][city]
weather_alert_client = hass.data[DOMAIN]["weather_alert_client"]
alert_watcher = None
if "weather_alert" in monitored_conditions:
datas = hass.data[DATA_METEO_FRANCE][city].get_data()
# Check if a department code is available for this city.
if "dept" in datas:
try:
# If yes create the watcher DepartmentWeatherAlert object.
alert_watcher = DepartmentWeatherAlert(
datas["dept"], weather_alert_client
)
except ValueError as exp:
_LOGGER.error(
"Unexpected error when creating the weather alert sensor for %s in department %s: %s",
city,
datas["dept"],
exp,
)
alert_watcher = None
else:
_LOGGER.info(
"Weather alert watcher added for %s in department %s",
city,
datas["dept"],
)
else:
_LOGGER.warning(
"No 'dept' key found for '%s'. So weather alert information won't be available",
city,
datas = client.get_data()
# Check if a department code is available for this city.
if "dept" in datas:
try:
# If yes create the watcher DepartmentWeatherAlert object.
alert_watcher = await hass.async_add_executor_job(
DepartmentWeatherAlert, datas["dept"], weather_alert_client
)
# Exit and don't create the sensor if no department code available.
return
_LOGGER.info(
"Weather alert watcher added for %s in department %s",
city,
datas["dept"],
)
except ValueError as exp:
_LOGGER.error(
"Unexpected error when creating the weather alert sensor for %s in department %s: %s",
city,
datas["dept"],
exp,
)
else:
_LOGGER.warning(
"No 'dept' key found for '%s'. So weather alert information won't be available",
city,
)
# Exit and don't create the sensor if no department code available.
return
add_entities(
async_add_entities(
[
MeteoFranceSensor(variable, client, alert_watcher)
for variable in monitored_conditions
MeteoFranceSensor(sensor_type, client, alert_watcher)
for sensor_type in SENSOR_TYPES
],
True,
)
@ -77,9 +75,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class MeteoFranceSensor(Entity):
"""Representation of a Meteo-France sensor."""
def __init__(self, condition, client, alert_watcher):
def __init__(
self,
sensor_type: str,
client: meteofranceClient,
alert_watcher: VigilanceMeteoFranceProxy,
):
"""Initialize the Meteo-France sensor."""
self._condition = condition
self._type = sensor_type
self._client = client
self._alert_watcher = alert_watcher
self._state = None
@ -88,7 +91,12 @@ class MeteoFranceSensor(Entity):
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._data['name']} {SENSOR_TYPES[self._condition][SENSOR_TYPE_NAME]}"
return f"{self._data['name']} {SENSOR_TYPES[self._type][SENSOR_TYPE_NAME]}"
@property
def unique_id(self):
"""Return the unique id of the sensor."""
return self.name
@property
def state(self):
@ -99,7 +107,7 @@ class MeteoFranceSensor(Entity):
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
# Attributes for next_rain sensor.
if self._condition == "next_rain" and "rain_forecast" in self._data:
if self._type == "next_rain" and "rain_forecast" in self._data:
return {
**{STATE_ATTR_FORECAST: self._data["rain_forecast"]},
**self._data["next_rain_intervals"],
@ -107,7 +115,7 @@ class MeteoFranceSensor(Entity):
}
# Attributes for weather_alert sensor.
if self._condition == "weather_alert" and self._alert_watcher is not None:
if self._type == "weather_alert" and self._alert_watcher is not None:
return {
**{STATE_ATTR_BULLETIN_TIME: self._alert_watcher.bulletin_date},
**self._alert_watcher.alerts_list,
@ -120,17 +128,17 @@ class MeteoFranceSensor(Entity):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return SENSOR_TYPES[self._condition][SENSOR_TYPE_UNIT]
return SENSOR_TYPES[self._type][SENSOR_TYPE_UNIT]
@property
def icon(self):
"""Return the icon."""
return SENSOR_TYPES[self._condition][SENSOR_TYPE_ICON]
return SENSOR_TYPES[self._type][SENSOR_TYPE_ICON]
@property
def device_class(self):
"""Return the device class of the sensor."""
return SENSOR_TYPES[self._condition][SENSOR_TYPE_CLASS]
return SENSOR_TYPES[self._type][SENSOR_TYPE_CLASS]
def update(self):
"""Fetch new state data for the sensor."""
@ -138,13 +146,12 @@ class MeteoFranceSensor(Entity):
self._client.update()
self._data = self._client.get_data()
if self._condition == "weather_alert":
if self._type == "weather_alert":
if self._alert_watcher is not None:
self._alert_watcher.update_department_status()
self._state = self._alert_watcher.department_color
_LOGGER.debug(
"weather alert watcher for %s updated. Proxy"
" have the status: %s",
"weather alert watcher for %s updated. Proxy have the status: %s",
self._data["name"],
self._alert_watcher.proxy.status,
)
@ -153,9 +160,9 @@ class MeteoFranceSensor(Entity):
"No weather alert data for location %s", self._data["name"]
)
else:
self._state = self._data[self._condition]
self._state = self._data[self._type]
except KeyError:
_LOGGER.error(
"No condition %s for location %s", self._condition, self._data["name"]
"No condition %s for location %s", self._type, self._data["name"]
)
self._state = None

View File

@ -0,0 +1,18 @@
{
"config": {
"title": "Météo-France",
"step": {
"user": {
"title": "Météo-France",
"description": "Enter the postal code (only for France, recommended) or city name",
"data": {
"city": "City"
}
}
},
"abort":{
"already_configured": "City already configured",
"unknown": "Unknown error: please retry later"
}
}
}

View File

@ -2,6 +2,8 @@
from datetime import timedelta
import logging
from meteofrance.client import meteofranceClient
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_TEMP,
@ -9,29 +11,30 @@ from homeassistant.components.weather import (
ATTR_FORECAST_TIME,
WeatherEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS
from homeassistant.helpers.typing import HomeAssistantType
import homeassistant.util.dt as dt_util
from .const import ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DATA_METEO_FRANCE
from .const import ATTRIBUTION, CONDITION_CLASSES, CONF_CITY, DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
) -> None:
"""Set up the Meteo-France weather platform."""
if discovery_info is None:
return
city = entry.data[CONF_CITY]
client = hass.data[DOMAIN][city]
city = discovery_info[CONF_CITY]
client = hass.data[DATA_METEO_FRANCE][city]
add_entities([MeteoFranceWeather(client)], True)
async_add_entities([MeteoFranceWeather(client)], True)
class MeteoFranceWeather(WeatherEntity):
"""Representation of a weather condition."""
def __init__(self, client):
def __init__(self, client: meteofranceClient):
"""Initialise the platform with a data instance and station name."""
self._client = client
self._data = {}
@ -46,6 +49,11 @@ class MeteoFranceWeather(WeatherEntity):
"""Return the name of the sensor."""
return self._data["name"]
@property
def unique_id(self):
"""Return the unique id of the sensor."""
return self.name
@property
def condition(self):
"""Return the current condition."""

View File

@ -54,6 +54,7 @@ FLOWS = [
"luftdaten",
"mailgun",
"met",
"meteo_france",
"mikrotik",
"mobile_app",
"mqtt",

View File

@ -298,6 +298,9 @@ luftdaten==0.6.3
# homeassistant.components.mythicbeastsdns
mbddns==0.1.2
# homeassistant.components.meteo_france
meteofrance==0.3.7
# homeassistant.components.mfi
mficlient==0.3.0
@ -663,6 +666,9 @@ url-normalize==1.4.1
# homeassistant.components.uvc
uvcclient==0.11.0
# homeassistant.components.meteo_france
vigilancemeteo==3.0.0
# homeassistant.components.verisure
vsure==1.5.4

View File

@ -0,0 +1 @@
"""Tests for the Meteo-France component."""

View File

@ -0,0 +1,128 @@
"""Tests for the Meteo-France config flow."""
from unittest.mock import patch
from meteofrance.client import meteofranceError
import pytest
from homeassistant import data_entry_flow
from homeassistant.components.meteo_france.const import CONF_CITY, DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from tests.common import MockConfigEntry
CITY_1_POSTAL = "74220"
CITY_1_NAME = "La Clusaz"
CITY_2_POSTAL_DISTRICT_1 = "69001"
CITY_2_POSTAL_DISTRICT_4 = "69004"
CITY_2_NAME = "Lyon"
@pytest.fixture(name="client_1")
def mock_controller_client_1():
"""Mock a successful client."""
with patch(
"homeassistant.components.meteo_france.config_flow.meteofranceClient",
update=False,
) as service_mock:
service_mock.return_value.get_data.return_value = {"name": CITY_1_NAME}
yield service_mock
@pytest.fixture(name="client_2")
def mock_controller_client_2():
"""Mock a successful client."""
with patch(
"homeassistant.components.meteo_france.config_flow.meteofranceClient",
update=False,
) as service_mock:
service_mock.return_value.get_data.return_value = {"name": CITY_2_NAME}
yield service_mock
async def test_user(hass, client_1):
"""Test user config."""
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"
# test with all provided
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].unique_id == CITY_1_NAME
assert result["title"] == CITY_1_NAME
assert result["data"][CONF_CITY] == CITY_1_POSTAL
async def test_import(hass, client_1):
"""Test import step."""
# import with all
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY_1_POSTAL},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["result"].unique_id == CITY_1_NAME
assert result["title"] == CITY_1_NAME
assert result["data"][CONF_CITY] == CITY_1_POSTAL
async def test_abort_if_already_setup(hass, client_1):
"""Test we abort if already setup."""
MockConfigEntry(
domain=DOMAIN, data={CONF_CITY: CITY_1_POSTAL}, unique_id=CITY_1_NAME
).add_to_hass(hass)
# Should fail, same CITY same postal code (import)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data={CONF_CITY: CITY_1_POSTAL},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
# Should fail, same CITY same postal code (flow)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_abort_if_already_setup_district(hass, client_2):
"""Test we abort if already setup."""
MockConfigEntry(
domain=DOMAIN, data={CONF_CITY: CITY_2_POSTAL_DISTRICT_1}, unique_id=CITY_2_NAME
).add_to_hass(hass)
# Should fail, same CITY different postal code (import)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_CITY: CITY_2_POSTAL_DISTRICT_4},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
# Should fail, same CITY different postal code (flow)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_CITY: CITY_2_POSTAL_DISTRICT_4},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_client_failed(hass):
"""Test when we have errors during client fetch."""
with patch(
"homeassistant.components.meteo_france.config_flow.meteofranceClient",
side_effect=meteofranceError(),
):
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data={CONF_CITY: CITY_1_POSTAL},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "unknown"