diff --git a/homeassistant/components/nws/.translations/en.json b/homeassistant/components/nws/.translations/en.json new file mode 100644 index 00000000000..dd91b04bedc --- /dev/null +++ b/homeassistant/components/nws/.translations/en.json @@ -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)" +} \ No newline at end of file diff --git a/homeassistant/components/nws/__init__.py b/homeassistant/components/nws/__init__.py index 66e791e627a..f39525f7068 100644 --- a/homeassistant/components/nws/__init__.py +++ b/homeassistant/components/nws/__init__.py @@ -7,9 +7,9 @@ import aiohttp from pynws import SimpleNWS import voluptuous as vol +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv 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): - """Set up the National Weather Service integration.""" - if DOMAIN not in config: - 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) - ) - + """Set up the National Weather Service (NWS) component.""" + hass.data.setdefault(DOMAIN, {}) 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: """Data class for National Weather Service integration.""" diff --git a/homeassistant/components/nws/config_flow.py b/homeassistant/components/nws/config_flow.py new file mode 100644 index 00000000000..19d8de4bce0 --- /dev/null +++ b/homeassistant/components/nws/config_flow.py @@ -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.""" diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index 769f927ea71..dea1c36b5fc 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -52,3 +52,6 @@ CONDITION_CLASSES = { "cloudy": ["Mostly cloudy", "Overcast"], "partlycloudy": ["A few clouds", "Partly cloudy"], } + +DAYNIGHT = "daynight" +HOURLY = "hourly" diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index b4b6e402941..2aa783f7a28 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/nws", "codeowners": ["@MatthewFlamm"], "requirements": ["pynws==0.10.4"], - "quality_scale": "silver" + "quality_scale": "silver", + "config_flow": true } diff --git a/homeassistant/components/nws/strings.json b/homeassistant/components/nws/strings.json new file mode 100644 index 00000000000..44c5c6ca326 --- /dev/null +++ b/homeassistant/components/nws/strings.json @@ -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" + } + } +} diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 8e4e7e560cb..457bba72bbe 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,9 +1,6 @@ """Support for NWS weather service.""" -import asyncio import logging -import aiohttp - from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, ATTR_FORECAST_TEMP, @@ -13,8 +10,6 @@ from homeassistant.components.weather import ( WeatherEntity, ) from homeassistant.const import ( - CONF_LATITUDE, - CONF_LONGITUDE, LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, @@ -25,8 +20,8 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import callback -from homeassistant.exceptions import PlatformNotReady 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.pressure import convert as convert_pressure from homeassistant.util.temperature import convert as convert_temperature @@ -38,8 +33,9 @@ from .const import ( ATTR_FORECAST_PRECIP_PROB, ATTRIBUTION, CONDITION_CLASSES, - CONF_STATION, + DAYNIGHT, DOMAIN, + HOURLY, ) _LOGGER = logging.getLogger(__name__) @@ -73,28 +69,15 @@ def convert_condition(time, weather): 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.""" - if discovery_info is None: - 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() - + nws_data = hass.data[DOMAIN][entry.entry_id] async_add_entities( [ - NWSWeather(nws_data, "daynight", hass.config.units), - NWSWeather(nws_data, "hourly", hass.config.units), + NWSWeather(nws_data, DAYNIGHT, hass.config.units), + NWSWeather(nws_data, HOURLY, hass.config.units), ], False, ) @@ -131,7 +114,7 @@ class NWSWeather(WeatherEntity): def _update_callback(self) -> None: """Load data from integration.""" self.observation = self.nws.observation - if self.mode == "daynight": + if self.mode == DAYNIGHT: self._forecast = self.nws.forecast else: self._forecast = self.nws.forecast_hourly @@ -259,7 +242,7 @@ class NWSWeather(WeatherEntity): ATTR_FORECAST_TIME: forecast_entry.get("startTime"), } - if self.mode == "daynight": + if self.mode == DAYNIGHT: data[ATTR_FORECAST_DAYTIME] = forecast_entry.get("isDaytime") time = forecast_entry.get("iconTime") weather = forecast_entry.get("iconWeather") @@ -292,7 +275,7 @@ class NWSWeather(WeatherEntity): @property def available(self): """Return if state is available.""" - if self.mode == "daynight": + if self.mode == DAYNIGHT: return ( self.nws.update_observation_success and self.nws.update_forecast_success ) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 521e75ab36d..975b62b3e99 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -85,6 +85,7 @@ FLOWS = [ "notion", "nuheat", "nut", + "nws", "opentherm_gw", "openuv", "owntracks", diff --git a/tests/components/nws/const.py b/tests/components/nws/const.py index 26490e512f9..6dee20a0759 100644 --- a/tests/components/nws/const.py +++ b/tests/components/nws/const.py @@ -1,5 +1,5 @@ """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.weather import ( ATTR_FORECAST_CONDITION, @@ -16,6 +16,8 @@ from homeassistant.components.weather import ( ) from homeassistant.const import ( CONF_API_KEY, + CONF_LATITUDE, + CONF_LONGITUDE, LENGTH_KILOMETERS, LENGTH_METERS, 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.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"] diff --git a/tests/components/nws/test_config_flow.py b/tests/components/nws/test_config_flow.py new file mode 100644 index 00000000000..b88be18bb4e --- /dev/null +++ b/tests/components/nws/test_config_flow.py @@ -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 diff --git a/tests/components/nws/test_init.py b/tests/components/nws/test_init.py index e6fe5707a0d..68c1e8c8130 100644 --- a/tests/components/nws/test_init.py +++ b/tests/components/nws/test_init.py @@ -1,92 +1,26 @@ """Tests for init module.""" -from homeassistant.components import nws -from homeassistant.components.nws.const import CONF_STATION, DOMAIN -from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE -from homeassistant.setup import async_setup_component +from homeassistant.components.nws.const import DOMAIN +from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN -from tests.common import assert_setup_component -from tests.components.nws.const import MINIMAL_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}, - ] -} +from tests.common import MockConfigEntry +from tests.components.nws.const import NWS_CONFIG -async def test_no_config(hass, mock_simple_nws): - """Test that nws does not setup with no config.""" - with assert_setup_component(0): - assert await async_setup_component(hass, DOMAIN, {}) - await hass.async_block_till_done() +async def test_unload_entry(hass, mock_simple_nws): + """Test that nws setup with config yaml.""" + entry = MockConfigEntry(domain=DOMAIN, data=NWS_CONFIG,) + entry.add_to_hass(hass) - entity_registry = await hass.helpers.entity_registry.async_get_registry() - 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.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - entity_registry = await hass.helpers.entity_registry.async_get_registry() - assert len(entity_registry.entities) == 2 + assert len(hass.states.async_entity_ids(WEATHER_DOMAIN)) == 2 + assert DOMAIN in hass.data 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 diff --git a/tests/components/nws/test_weather.py b/tests/components/nws/test_weather.py index 4ebc4580ff8..2af76242410 100644 --- a/tests/components/nws/test_weather.py +++ b/tests/components/nws/test_weather.py @@ -6,19 +6,18 @@ import pytest from homeassistant.components import nws from homeassistant.components.weather import ATTR_FORECAST -from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util 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 ( EXPECTED_FORECAST_IMPERIAL, EXPECTED_FORECAST_METRIC, EXPECTED_OBSERVATION_IMPERIAL, EXPECTED_OBSERVATION_METRIC, - MINIMAL_CONFIG, NONE_FORECAST, NONE_OBSERVATION, + NWS_CONFIG, ) @@ -34,7 +33,9 @@ async def test_imperial_metric( ): """Test with imperial and metric 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() 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.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() state = hass.states.get("weather.abc_daynight") @@ -93,7 +96,9 @@ async def test_none(hass, mock_simple_nws): instance.observation = 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() 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.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() 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.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() + 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 "Success updating observation for station ABC" not in caplog.text caplog.clear() @@ -139,6 +158,16 @@ async def test_error_observation(hass, mock_simple_nws, caplog): async_fire_time_changed(hass, future_time) 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 "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.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() + 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 "Success updating forecast for station ABC" not in caplog.text caplog.clear() @@ -161,6 +198,12 @@ async def test_error_forecast(hass, caplog, mock_simple_nws): async_fire_time_changed(hass, future_time) 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 "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.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() + 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 "Success updating forecast_hourly for station ABC" not in caplog.text caplog.clear() @@ -183,5 +234,11 @@ async def test_error_forecast_hourly(hass, caplog, mock_simple_nws): async_fire_time_changed(hass, future_time) 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 "Success updating forecast_hourly for station ABC" in caplog.text