Add config flow to nws and remove yaml configuration (#34267)
* add config flow to nws and remove yaml * Don't duplicate scan_time Co-Authored-By: J. Nick Koston <nick@koston.org> * Use _abort_if_unique_id_configured Co-Authored-By: J. Nick Koston <nick@koston.org> * fix abort * Add unavailable tests * update and use better strings * lint Co-authored-by: J. Nick Koston <nick@koston.org>pull/34289/head
parent
5617e6913b
commit
6d812bd957
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API key (email)",
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"longitude": "Longitude",
|
||||||
|
"station": "METAR station code"
|
||||||
|
},
|
||||||
|
"description": "If a METAR station code is not specified, the latitude and longitude will be used to find the closest station.",
|
||||||
|
"title": "Connect to the National Weather Service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "National Weather Service (NWS)"
|
||||||
|
}
|
|
@ -7,9 +7,9 @@ import aiohttp
|
||||||
from pynws import SimpleNWS
|
from pynws import SimpleNWS
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import discovery
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
@ -52,38 +52,53 @@ def signal_unique_id(latitude, longitude):
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: dict):
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
"""Set up the National Weather Service integration."""
|
"""Set up the National Weather Service (NWS) component."""
|
||||||
if DOMAIN not in config:
|
hass.data.setdefault(DOMAIN, {})
|
||||||
return True
|
|
||||||
|
|
||||||
hass.data[DOMAIN] = hass.data.get(DOMAIN, {})
|
|
||||||
for entry in config[DOMAIN]:
|
|
||||||
latitude = entry.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = entry.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
api_key = entry[CONF_API_KEY]
|
|
||||||
|
|
||||||
client_session = async_get_clientsession(hass)
|
|
||||||
|
|
||||||
if base_unique_id(latitude, longitude) in hass.data[DOMAIN]:
|
|
||||||
_LOGGER.error(
|
|
||||||
"Duplicate entry in config: latitude %s latitude: %s",
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
nws_data = NwsData(hass, latitude, longitude, api_key, client_session)
|
|
||||||
hass.data[DOMAIN][base_unique_id(latitude, longitude)] = nws_data
|
|
||||||
async_track_time_interval(hass, nws_data.async_update, DEFAULT_SCAN_INTERVAL)
|
|
||||||
|
|
||||||
for component in PLATFORMS:
|
|
||||||
hass.async_create_task(
|
|
||||||
discovery.async_load_platform(hass, component, DOMAIN, entry, config)
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Set up a National Weather Service entry."""
|
||||||
|
latitude = entry.data[CONF_LATITUDE]
|
||||||
|
longitude = entry.data[CONF_LONGITUDE]
|
||||||
|
api_key = entry.data[CONF_API_KEY]
|
||||||
|
station = entry.data[CONF_STATION]
|
||||||
|
|
||||||
|
client_session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
nws_data = NwsData(hass, latitude, longitude, api_key, client_session)
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = nws_data
|
||||||
|
|
||||||
|
# async_set_station only does IO when station is None
|
||||||
|
await nws_data.async_set_station(station)
|
||||||
|
await nws_data.async_update()
|
||||||
|
|
||||||
|
async_track_time_interval(hass, nws_data.async_update, DEFAULT_SCAN_INTERVAL)
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
if len(hass.data[DOMAIN]) == 0:
|
||||||
|
hass.data.pop(DOMAIN)
|
||||||
|
return unload_ok
|
||||||
|
|
||||||
|
|
||||||
class NwsData:
|
class NwsData:
|
||||||
"""Data class for National Weather Service integration."""
|
"""Data class for National Weather Service integration."""
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
"""Config flow for National Weather Service (NWS) integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries, core, exceptions
|
||||||
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from . import NwsData, base_unique_id
|
||||||
|
from .const import CONF_STATION, DOMAIN # pylint:disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: core.HomeAssistant, data):
|
||||||
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||||
|
"""
|
||||||
|
latitude = data[CONF_LATITUDE]
|
||||||
|
longitude = data[CONF_LONGITUDE]
|
||||||
|
api_key = data[CONF_API_KEY]
|
||||||
|
station = data.get(CONF_STATION)
|
||||||
|
|
||||||
|
client_session = async_get_clientsession(hass)
|
||||||
|
ha_api_key = f"{api_key} homeassistant"
|
||||||
|
nws = NwsData(hass, latitude, longitude, ha_api_key, client_session)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await nws.async_set_station(station)
|
||||||
|
except aiohttp.ClientError as err:
|
||||||
|
_LOGGER.error("Could not connect: %s", err)
|
||||||
|
raise CannotConnect
|
||||||
|
|
||||||
|
return {"title": nws.station}
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for National Weather Service (NWS)."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
if user_input is not None:
|
||||||
|
await self.async_set_unique_id(
|
||||||
|
base_unique_id(user_input[CONF_LATITUDE], user_input[CONF_LONGITUDE])
|
||||||
|
)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
user_input[CONF_STATION] = info["title"]
|
||||||
|
return self.async_create_entry(title=info["title"], data=user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
|
||||||
|
data_schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): str,
|
||||||
|
vol.Required(
|
||||||
|
CONF_LATITUDE, default=self.hass.config.latitude
|
||||||
|
): cv.latitude,
|
||||||
|
vol.Required(
|
||||||
|
CONF_LONGITUDE, default=self.hass.config.longitude
|
||||||
|
): cv.longitude,
|
||||||
|
vol.Optional(CONF_STATION): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=data_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
|
@ -52,3 +52,6 @@ CONDITION_CLASSES = {
|
||||||
"cloudy": ["Mostly cloudy", "Overcast"],
|
"cloudy": ["Mostly cloudy", "Overcast"],
|
||||||
"partlycloudy": ["A few clouds", "Partly cloudy"],
|
"partlycloudy": ["A few clouds", "Partly cloudy"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DAYNIGHT = "daynight"
|
||||||
|
HOURLY = "hourly"
|
||||||
|
|
|
@ -4,5 +4,6 @@
|
||||||
"documentation": "https://www.home-assistant.io/integrations/nws",
|
"documentation": "https://www.home-assistant.io/integrations/nws",
|
||||||
"codeowners": ["@MatthewFlamm"],
|
"codeowners": ["@MatthewFlamm"],
|
||||||
"requirements": ["pynws==0.10.4"],
|
"requirements": ["pynws==0.10.4"],
|
||||||
"quality_scale": "silver"
|
"quality_scale": "silver",
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"title": "National Weather Service (NWS)",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"description": "If a METAR station code is not specified, the latitude and longitude will be used to find the closest station.",
|
||||||
|
"title": "Connect to the National Weather Service",
|
||||||
|
"data": {
|
||||||
|
"api_key": "API key (email)",
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"longitude": "Longitude",
|
||||||
|
"station": "METAR station code"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
"""Support for NWS weather service."""
|
"""Support for NWS weather service."""
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
ATTR_FORECAST_CONDITION,
|
ATTR_FORECAST_CONDITION,
|
||||||
ATTR_FORECAST_TEMP,
|
ATTR_FORECAST_TEMP,
|
||||||
|
@ -13,8 +10,6 @@ from homeassistant.components.weather import (
|
||||||
WeatherEntity,
|
WeatherEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_LATITUDE,
|
|
||||||
CONF_LONGITUDE,
|
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
LENGTH_METERS,
|
LENGTH_METERS,
|
||||||
LENGTH_MILES,
|
LENGTH_MILES,
|
||||||
|
@ -25,8 +20,8 @@ from homeassistant.const import (
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
from homeassistant.util.distance import convert as convert_distance
|
from homeassistant.util.distance import convert as convert_distance
|
||||||
from homeassistant.util.pressure import convert as convert_pressure
|
from homeassistant.util.pressure import convert as convert_pressure
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
|
@ -38,8 +33,9 @@ from .const import (
|
||||||
ATTR_FORECAST_PRECIP_PROB,
|
ATTR_FORECAST_PRECIP_PROB,
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
CONDITION_CLASSES,
|
CONDITION_CLASSES,
|
||||||
CONF_STATION,
|
DAYNIGHT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
HOURLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -73,28 +69,15 @@ def convert_condition(time, weather):
|
||||||
return cond, max(prec_probs)
|
return cond, max(prec_probs)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistantType, entry: ConfigType, async_add_entities
|
||||||
|
) -> None:
|
||||||
"""Set up the NWS weather platform."""
|
"""Set up the NWS weather platform."""
|
||||||
if discovery_info is None:
|
nws_data = hass.data[DOMAIN][entry.entry_id]
|
||||||
return
|
|
||||||
latitude = discovery_info.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = discovery_info.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
station = discovery_info.get(CONF_STATION)
|
|
||||||
|
|
||||||
nws_data = hass.data[DOMAIN][base_unique_id(latitude, longitude)]
|
|
||||||
|
|
||||||
try:
|
|
||||||
await nws_data.async_set_station(station)
|
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as err:
|
|
||||||
_LOGGER.error("Error automatically setting station: %s", str(err))
|
|
||||||
raise PlatformNotReady
|
|
||||||
|
|
||||||
await nws_data.async_update()
|
|
||||||
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
[
|
[
|
||||||
NWSWeather(nws_data, "daynight", hass.config.units),
|
NWSWeather(nws_data, DAYNIGHT, hass.config.units),
|
||||||
NWSWeather(nws_data, "hourly", hass.config.units),
|
NWSWeather(nws_data, HOURLY, hass.config.units),
|
||||||
],
|
],
|
||||||
False,
|
False,
|
||||||
)
|
)
|
||||||
|
@ -131,7 +114,7 @@ class NWSWeather(WeatherEntity):
|
||||||
def _update_callback(self) -> None:
|
def _update_callback(self) -> None:
|
||||||
"""Load data from integration."""
|
"""Load data from integration."""
|
||||||
self.observation = self.nws.observation
|
self.observation = self.nws.observation
|
||||||
if self.mode == "daynight":
|
if self.mode == DAYNIGHT:
|
||||||
self._forecast = self.nws.forecast
|
self._forecast = self.nws.forecast
|
||||||
else:
|
else:
|
||||||
self._forecast = self.nws.forecast_hourly
|
self._forecast = self.nws.forecast_hourly
|
||||||
|
@ -259,7 +242,7 @@ class NWSWeather(WeatherEntity):
|
||||||
ATTR_FORECAST_TIME: forecast_entry.get("startTime"),
|
ATTR_FORECAST_TIME: forecast_entry.get("startTime"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mode == "daynight":
|
if self.mode == DAYNIGHT:
|
||||||
data[ATTR_FORECAST_DAYTIME] = forecast_entry.get("isDaytime")
|
data[ATTR_FORECAST_DAYTIME] = forecast_entry.get("isDaytime")
|
||||||
time = forecast_entry.get("iconTime")
|
time = forecast_entry.get("iconTime")
|
||||||
weather = forecast_entry.get("iconWeather")
|
weather = forecast_entry.get("iconWeather")
|
||||||
|
@ -292,7 +275,7 @@ class NWSWeather(WeatherEntity):
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return if state is available."""
|
"""Return if state is available."""
|
||||||
if self.mode == "daynight":
|
if self.mode == DAYNIGHT:
|
||||||
return (
|
return (
|
||||||
self.nws.update_observation_success and self.nws.update_forecast_success
|
self.nws.update_observation_success and self.nws.update_forecast_success
|
||||||
)
|
)
|
||||||
|
|
|
@ -85,6 +85,7 @@ FLOWS = [
|
||||||
"notion",
|
"notion",
|
||||||
"nuheat",
|
"nuheat",
|
||||||
"nut",
|
"nut",
|
||||||
|
"nws",
|
||||||
"opentherm_gw",
|
"opentherm_gw",
|
||||||
"openuv",
|
"openuv",
|
||||||
"owntracks",
|
"owntracks",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""Helpers for interacting with pynws."""
|
"""Helpers for interacting with pynws."""
|
||||||
from homeassistant.components.nws.const import DOMAIN
|
from homeassistant.components.nws.const import CONF_STATION
|
||||||
from homeassistant.components.nws.weather import ATTR_FORECAST_PRECIP_PROB
|
from homeassistant.components.nws.weather import ATTR_FORECAST_PRECIP_PROB
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
ATTR_FORECAST_CONDITION,
|
ATTR_FORECAST_CONDITION,
|
||||||
|
@ -16,6 +16,8 @@ from homeassistant.components.weather import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
LENGTH_METERS,
|
LENGTH_METERS,
|
||||||
LENGTH_MILES,
|
LENGTH_MILES,
|
||||||
|
@ -29,7 +31,12 @@ from homeassistant.util.distance import convert as convert_distance
|
||||||
from homeassistant.util.pressure import convert as convert_pressure
|
from homeassistant.util.pressure import convert as convert_pressure
|
||||||
from homeassistant.util.temperature import convert as convert_temperature
|
from homeassistant.util.temperature import convert as convert_temperature
|
||||||
|
|
||||||
MINIMAL_CONFIG = {DOMAIN: [{CONF_API_KEY: "test"}]}
|
NWS_CONFIG = {
|
||||||
|
CONF_API_KEY: "test",
|
||||||
|
CONF_LATITUDE: 35,
|
||||||
|
CONF_LONGITUDE: -75,
|
||||||
|
CONF_STATION: "ABC",
|
||||||
|
}
|
||||||
|
|
||||||
DEFAULT_STATIONS = ["ABC", "XYZ"]
|
DEFAULT_STATIONS = ["ABC", "XYZ"]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""Test the National Weather Service (NWS) config flow."""
|
||||||
|
import aiohttp
|
||||||
|
from asynctest import patch
|
||||||
|
|
||||||
|
from homeassistant import config_entries, setup
|
||||||
|
from homeassistant.components.nws.const import DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass, mock_simple_nws):
|
||||||
|
"""Test we get the form."""
|
||||||
|
hass.config.latitude = 35
|
||||||
|
hass.config.longitude = -90
|
||||||
|
|
||||||
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nws.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.nws.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"api_key": "test"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
assert result2["title"] == "ABC"
|
||||||
|
assert result2["data"] == {
|
||||||
|
"api_key": "test",
|
||||||
|
"latitude": 35,
|
||||||
|
"longitude": -90,
|
||||||
|
"station": "ABC",
|
||||||
|
}
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass, mock_simple_nws):
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
mock_instance = mock_simple_nws.return_value
|
||||||
|
mock_instance.set_station.side_effect = aiohttp.ClientError
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"api_key": "test"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_unknown_error(hass, mock_simple_nws):
|
||||||
|
"""Test we handle unknown error."""
|
||||||
|
mock_instance = mock_simple_nws.return_value
|
||||||
|
mock_instance.set_station.side_effect = ValueError
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"api_key": "test"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "form"
|
||||||
|
assert result2["errors"] == {"base": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_already_configured(hass, mock_simple_nws):
|
||||||
|
"""Test we handle duplicate entries."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nws.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.nws.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"api_key": "test"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == "create_entry"
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.nws.async_setup", return_value=True
|
||||||
|
) as mock_setup, patch(
|
||||||
|
"homeassistant.components.nws.async_setup_entry", return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"api_key": "test"},
|
||||||
|
)
|
||||||
|
assert result2["type"] == "abort"
|
||||||
|
assert result2["reason"] == "already_configured"
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mock_setup.mock_calls) == 0
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 0
|
|
@ -1,92 +1,26 @@
|
||||||
"""Tests for init module."""
|
"""Tests for init module."""
|
||||||
from homeassistant.components import nws
|
from homeassistant.components.nws.const import DOMAIN
|
||||||
from homeassistant.components.nws.const import CONF_STATION, DOMAIN
|
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
|
|
||||||
from tests.common import assert_setup_component
|
from tests.common import MockConfigEntry
|
||||||
from tests.components.nws.const import MINIMAL_CONFIG
|
from tests.components.nws.const import NWS_CONFIG
|
||||||
|
|
||||||
LATLON_CONFIG = {
|
|
||||||
DOMAIN: [{CONF_API_KEY: "test", CONF_LATITUDE: 45.0, CONF_LONGITUDE: -75.0}]
|
|
||||||
}
|
|
||||||
FULL_CONFIG = {
|
|
||||||
DOMAIN: [
|
|
||||||
{
|
|
||||||
CONF_API_KEY: "test",
|
|
||||||
CONF_LATITUDE: 45.0,
|
|
||||||
CONF_LONGITUDE: -75.0,
|
|
||||||
CONF_STATION: "XYZ",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
DUPLICATE_CONFIG = {
|
|
||||||
DOMAIN: [
|
|
||||||
{CONF_API_KEY: "test", CONF_LATITUDE: 45.0, CONF_LONGITUDE: -75.0},
|
|
||||||
{CONF_API_KEY: "test", CONF_LATITUDE: 45.0, CONF_LONGITUDE: -75.0},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_no_config(hass, mock_simple_nws):
|
async def test_unload_entry(hass, mock_simple_nws):
|
||||||
"""Test that nws does not setup with no config."""
|
"""Test that nws setup with config yaml."""
|
||||||
with assert_setup_component(0):
|
entry = MockConfigEntry(domain=DOMAIN, data=NWS_CONFIG,)
|
||||||
assert await async_setup_component(hass, DOMAIN, {})
|
entry.add_to_hass(hass)
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
assert len(entity_registry.entities) == 0
|
|
||||||
|
|
||||||
assert DOMAIN not in hass.data
|
|
||||||
|
|
||||||
|
|
||||||
async def test_successful_minimal_config(hass, mock_simple_nws):
|
|
||||||
"""Test that nws setup with minimal config."""
|
|
||||||
hass.config.latitude = 40.0
|
|
||||||
hass.config.longitude = -75.0
|
|
||||||
with assert_setup_component(1, DOMAIN):
|
|
||||||
assert await async_setup_component(hass, DOMAIN, MINIMAL_CONFIG)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
|
||||||
assert len(entity_registry.entities) == 2
|
|
||||||
|
|
||||||
assert DOMAIN in hass.data
|
|
||||||
assert nws.base_unique_id(40.0, -75.0) in hass.data[DOMAIN]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_successful_latlon_config(hass, mock_simple_nws):
|
|
||||||
"""Test that nws setup with latlon config."""
|
|
||||||
with assert_setup_component(1, DOMAIN):
|
|
||||||
assert await async_setup_component(hass, DOMAIN, LATLON_CONFIG)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
|
||||||
assert len(entity_registry.entities) == 2
|
|
||||||
|
|
||||||
assert DOMAIN in hass.data
|
|
||||||
assert nws.base_unique_id(45.0, -75.0) in hass.data[DOMAIN]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_successful_full_config(hass, mock_simple_nws):
|
|
||||||
"""Test that nws setup with full config."""
|
|
||||||
with assert_setup_component(1, DOMAIN):
|
|
||||||
assert await async_setup_component(hass, DOMAIN, FULL_CONFIG)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
|
||||||
assert len(entity_registry.entities) == 2
|
|
||||||
|
|
||||||
assert DOMAIN in hass.data
|
|
||||||
assert nws.base_unique_id(45.0, -75.0) in hass.data[DOMAIN]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_unsuccessful_duplicate_config(hass, mock_simple_nws):
|
|
||||||
"""Test that nws setup with duplicate config."""
|
|
||||||
assert await async_setup_component(hass, DOMAIN, DUPLICATE_CONFIG)
|
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 2
|
||||||
assert len(entity_registry.entities) == 2
|
assert DOMAIN in hass.data
|
||||||
|
|
||||||
assert len(hass.data[DOMAIN]) == 1
|
assert len(hass.data[DOMAIN]) == 1
|
||||||
|
entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(entries) == 1
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entries[0].entry_id)
|
||||||
|
assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 0
|
||||||
|
assert DOMAIN not in hass.data
|
||||||
|
|
|
@ -6,19 +6,18 @@ import pytest
|
||||||
|
|
||||||
from homeassistant.components import nws
|
from homeassistant.components import nws
|
||||||
from homeassistant.components.weather import ATTR_FORECAST
|
from homeassistant.components.weather import ATTR_FORECAST
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
from homeassistant.util.unit_system import IMPERIAL_SYSTEM, METRIC_SYSTEM
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
from tests.components.nws.const import (
|
from tests.components.nws.const import (
|
||||||
EXPECTED_FORECAST_IMPERIAL,
|
EXPECTED_FORECAST_IMPERIAL,
|
||||||
EXPECTED_FORECAST_METRIC,
|
EXPECTED_FORECAST_METRIC,
|
||||||
EXPECTED_OBSERVATION_IMPERIAL,
|
EXPECTED_OBSERVATION_IMPERIAL,
|
||||||
EXPECTED_OBSERVATION_METRIC,
|
EXPECTED_OBSERVATION_METRIC,
|
||||||
MINIMAL_CONFIG,
|
|
||||||
NONE_FORECAST,
|
NONE_FORECAST,
|
||||||
NONE_OBSERVATION,
|
NONE_OBSERVATION,
|
||||||
|
NWS_CONFIG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +33,9 @@ async def test_imperial_metric(
|
||||||
):
|
):
|
||||||
"""Test with imperial and metric units."""
|
"""Test with imperial and metric units."""
|
||||||
hass.config.units = units
|
hass.config.units = units
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("weather.abc_hourly")
|
state = hass.states.get("weather.abc_hourly")
|
||||||
|
@ -73,7 +74,9 @@ async def test_none_values(hass, mock_simple_nws):
|
||||||
instance.observation = NONE_OBSERVATION
|
instance.observation = NONE_OBSERVATION
|
||||||
instance.forecast = NONE_FORECAST
|
instance.forecast = NONE_FORECAST
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("weather.abc_daynight")
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
@ -93,7 +96,9 @@ async def test_none(hass, mock_simple_nws):
|
||||||
instance.observation = None
|
instance.observation = None
|
||||||
instance.forecast = None
|
instance.forecast = None
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("weather.abc_daynight")
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
@ -114,7 +119,9 @@ async def test_error_station(hass, mock_simple_nws):
|
||||||
instance = mock_simple_nws.return_value
|
instance = mock_simple_nws.return_value
|
||||||
instance.set_station.side_effect = aiohttp.ClientError
|
instance.set_station.side_effect = aiohttp.ClientError
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG) is True
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert hass.states.get("weather.abc_hourly") is None
|
assert hass.states.get("weather.abc_hourly") is None
|
||||||
|
@ -126,9 +133,21 @@ async def test_error_observation(hass, mock_simple_nws, caplog):
|
||||||
instance = mock_simple_nws.return_value
|
instance = mock_simple_nws.return_value
|
||||||
instance.update_observation.side_effect = aiohttp.ClientError
|
instance.update_observation.side_effect = aiohttp.ClientError
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
instance.update_observation.assert_called_once()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
assert state
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_hourly")
|
||||||
|
assert state
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
assert "Error updating observation for station ABC" in caplog.text
|
assert "Error updating observation for station ABC" in caplog.text
|
||||||
assert "Success updating observation for station ABC" not in caplog.text
|
assert "Success updating observation for station ABC" not in caplog.text
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
@ -139,6 +158,16 @@ async def test_error_observation(hass, mock_simple_nws, caplog):
|
||||||
async_fire_time_changed(hass, future_time)
|
async_fire_time_changed(hass, future_time)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert instance.update_observation.call_count == 2
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
assert state
|
||||||
|
assert state.state == "sunny"
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_hourly")
|
||||||
|
assert state
|
||||||
|
assert state.state == "sunny"
|
||||||
|
|
||||||
assert "Error updating observation for station ABC" not in caplog.text
|
assert "Error updating observation for station ABC" not in caplog.text
|
||||||
assert "Success updating observation for station ABC" in caplog.text
|
assert "Success updating observation for station ABC" in caplog.text
|
||||||
|
|
||||||
|
@ -148,9 +177,17 @@ async def test_error_forecast(hass, caplog, mock_simple_nws):
|
||||||
instance = mock_simple_nws.return_value
|
instance = mock_simple_nws.return_value
|
||||||
instance.update_forecast.side_effect = aiohttp.ClientError
|
instance.update_forecast.side_effect = aiohttp.ClientError
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
instance.update_forecast.assert_called_once()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
assert state
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
assert "Error updating forecast for station ABC" in caplog.text
|
assert "Error updating forecast for station ABC" in caplog.text
|
||||||
assert "Success updating forecast for station ABC" not in caplog.text
|
assert "Success updating forecast for station ABC" not in caplog.text
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
@ -161,6 +198,12 @@ async def test_error_forecast(hass, caplog, mock_simple_nws):
|
||||||
async_fire_time_changed(hass, future_time)
|
async_fire_time_changed(hass, future_time)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert instance.update_forecast.call_count == 2
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_daynight")
|
||||||
|
assert state
|
||||||
|
assert state.state == "sunny"
|
||||||
|
|
||||||
assert "Error updating forecast for station ABC" not in caplog.text
|
assert "Error updating forecast for station ABC" not in caplog.text
|
||||||
assert "Success updating forecast for station ABC" in caplog.text
|
assert "Success updating forecast for station ABC" in caplog.text
|
||||||
|
|
||||||
|
@ -170,9 +213,17 @@ async def test_error_forecast_hourly(hass, caplog, mock_simple_nws):
|
||||||
instance = mock_simple_nws.return_value
|
instance = mock_simple_nws.return_value
|
||||||
instance.update_forecast_hourly.side_effect = aiohttp.ClientError
|
instance.update_forecast_hourly.side_effect = aiohttp.ClientError
|
||||||
|
|
||||||
assert await async_setup_component(hass, nws.DOMAIN, MINIMAL_CONFIG)
|
entry = MockConfigEntry(domain=nws.DOMAIN, data=NWS_CONFIG,)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_hourly")
|
||||||
|
assert state
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
|
instance.update_forecast_hourly.assert_called_once()
|
||||||
|
|
||||||
assert "Error updating forecast_hourly for station ABC" in caplog.text
|
assert "Error updating forecast_hourly for station ABC" in caplog.text
|
||||||
assert "Success updating forecast_hourly for station ABC" not in caplog.text
|
assert "Success updating forecast_hourly for station ABC" not in caplog.text
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
@ -183,5 +234,11 @@ async def test_error_forecast_hourly(hass, caplog, mock_simple_nws):
|
||||||
async_fire_time_changed(hass, future_time)
|
async_fire_time_changed(hass, future_time)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert instance.update_forecast_hourly.call_count == 2
|
||||||
|
|
||||||
|
state = hass.states.get("weather.abc_hourly")
|
||||||
|
assert state
|
||||||
|
assert state.state == "sunny"
|
||||||
|
|
||||||
assert "Error updating forecast_hourly for station ABC" not in caplog.text
|
assert "Error updating forecast_hourly for station ABC" not in caplog.text
|
||||||
assert "Success updating forecast_hourly for station ABC" in caplog.text
|
assert "Success updating forecast_hourly for station ABC" in caplog.text
|
||||||
|
|
Loading…
Reference in New Issue