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
MatthewFlamm 2020-04-16 10:15:55 -04:00 committed by GitHub
parent 5617e6913b
commit 6d812bd957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 401 additions and 155 deletions

View File

@ -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)"
}

View File

@ -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."""

View File

@ -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."""

View File

@ -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"

View File

@ -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
} }

View File

@ -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"
}
}
}

View File

@ -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
) )

View File

@ -85,6 +85,7 @@ FLOWS = [
"notion", "notion",
"nuheat", "nuheat",
"nut", "nut",
"nws",
"opentherm_gw", "opentherm_gw",
"openuv", "openuv",
"owntracks", "owntracks",

View File

@ -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"]

View File

@ -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

View File

@ -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

View File

@ -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