From fd3a90a91e7150b7fb40157c9b014f2f475debcf Mon Sep 17 00:00:00 2001 From: ktdad Date: Thu, 18 Jul 2019 06:50:03 -0400 Subject: [PATCH] change to simpler api --- homeassistant/components/nws/manifest.json | 5 +- homeassistant/components/nws/weather.py | 75 +-- requirements_all.txt | 5 +- tests/components/nws/test_nws.py | 561 +++++---------------- 4 files changed, 178 insertions(+), 468 deletions(-) diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 58d735602de..510462437aa 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -4,8 +4,5 @@ "documentation": "https://www.home-assistant.io/components/nws", "dependencies": [], "codeowners": ["@MatthewFlamm"], - "requirements": [ - "pynws==0.6", - "metar==1.7.0" - ] + "requirements": ["pynws==0.7.0"] } diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index a8b21ea6ebd..4350c29a4b5 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -1,13 +1,10 @@ """Support for NWS weather service.""" -import asyncio from collections import OrderedDict from datetime import timedelta from json import JSONDecodeError import logging -from statistics import mean import aiohttp -import async_timeout import voluptuous as vol from homeassistant.components.weather import ( @@ -72,13 +69,7 @@ CONDITION_CLASSES = OrderedDict([ 'Partly cloudy']), ]) -ERRORS = (aiohttp.ClientError, JSONDecodeError, asyncio.CancelledError) - -FORECAST_CLASSES = { - ATTR_FORECAST_DETAIL_DESCRIPTION: 'detailedForecast', - ATTR_FORECAST_TEMP: 'temperature', - ATTR_FORECAST_TIME: 'startTime', -} +ERRORS = (aiohttp.ClientError, JSONDecodeError) FORECAST_MODE = ['daynight', 'hourly'] @@ -88,7 +79,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_LONGITUDE): cv.longitude, vol.Optional(CONF_MODE, default='daynight'): vol.In(FORECAST_MODE), vol.Optional(CONF_STATION): cv.string, - vol.Required(CONF_API_KEY): cv.string + vol.Required(CONF_API_KEY): cv.string, }) @@ -109,16 +100,16 @@ def convert_condition(time, weather): if cond == 'clear': if time == 'day': - return 'sunny', max(prec_prob) + return 'sunny', max(prec_probs) if time == 'night': - return 'clear-night', max(prec_prob) - return cond, max(prec_prob) + return 'clear-night', max(prec_probs) + return cond, max(prec_probs) async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the NWS weather platform.""" - from pynws import SimpleNws + from pynws import SimpleNWS latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) station = config.get(CONF_STATION) @@ -132,7 +123,7 @@ async def async_setup_platform(hass, config, async_add_entities, websession = async_get_clientsession(hass) # ID request as being from HA, pynws prepends the api_key in addition api_key_ha = f"{api_key} homeassistant" - nws = simple_nws(lat, lon, userid=api_key_ha, mode, websession) + nws = SimpleNWS(latitude, longitude, api_key_ha, mode, websession) _LOGGER.debug("Setting up station: %s", station) try: @@ -147,25 +138,29 @@ async def async_setup_platform(hass, config, async_add_entities, latitude, longitude, nws.station) async_add_entities( - [NWSWeather(nws, hass.config.units, config)], + [NWSWeather(nws, mode, hass.config.units, config)], True) class NWSWeather(WeatherEntity): """Representation of a weather condition.""" - def __init__(self, nws, units, config): + def __init__(self, nws, mode, units, config): """Initialise the platform with a data instance and station name.""" self.nws = nws - self.station_name = config.get(CONF_NAME, self._nws.station) + self.station_name = config.get(CONF_NAME, self.nws.station) self.is_metric = units.is_metric + self.mode = mode + + self.observation = None + self._forecast = None @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Update Condition.""" _LOGGER.debug("Updating station observations %s", self.nws.station) try: - await self.nws.update_observations() + await self.nws.update_observation() except ERRORS as status: _LOGGER.error("Error updating observation from station %s: %s", self.nws.station, status) @@ -178,7 +173,7 @@ class NWSWeather(WeatherEntity): _LOGGER.error("Error updating forecast from station %s: %s", self.nws.station, status) else: - self.forecast = self.nws.forecast + self._forecast = self.nws.forecast return @property @@ -205,7 +200,7 @@ class NWSWeather(WeatherEntity): def pressure(self): """Return the current pressure.""" pressure_pa = None - if self._observation: + if self.observation: pressure_pa = self.observation.get('seaLevelPressure') if pressure_pa is None: return None @@ -237,7 +232,7 @@ class NWSWeather(WeatherEntity): return None wind_m_hr = wind_m_s * 3600 - if self._is_metric: + if self.is_metric: wind = convert_distance(wind_m_hr, LENGTH_METERS, LENGTH_KILOMETERS) else: @@ -275,11 +270,11 @@ class NWSWeather(WeatherEntity): """Return visibility.""" vis_m = None if self.observation: - vis_m = self._observation.get('visibility') + vis_m = self.observation.get('visibility') if vis_m is None: return None - if self._is_metric: + if self.is_metric: vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_KILOMETERS) else: vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_MILES) @@ -288,31 +283,39 @@ class NWSWeather(WeatherEntity): @property def forecast(self): """Return forecast.""" + if self._forecast is None: + return None forecast = [] for forecast_entry in self._forecast: data = { - ATTR_FORECAST_DETAIL_DESCRIPTION: forecast_entry.get('detailedForecast'), + ATTR_FORECAST_DETAIL_DESCRIPTION: forecast_entry.get( + 'detailedForecast'), ATTR_FORECAST_TEMP: forecast_entry.get('temperature'), 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') - cond, precip = convert_condition(time, weather) + if time and weather: + cond, precip = convert_condition(time, weather) + else: + cond, precip = None, None data[ATTR_FORECAST_CONDITION] = cond data[ATTR_FORECAST_PRECIP_PROB] = precip data[ATTR_FORECAST_WIND_BEARING] = \ - WIND[forecast_entry['windDirection']] + forecast_entry.get('windBearing') wind_speed = forecast_entry.get('windSpeedAvg') - if self._is_metric: - data[ATTR_FORECAST_WIND_SPEED] = round( - convert_distance(wind_speed, - LENGTH_MILES, LENGTH_KILOMETERS)) + if wind_speed: + if self.is_metric: + data[ATTR_FORECAST_WIND_SPEED] = round( + convert_distance(wind_speed, + LENGTH_MILES, LENGTH_KILOMETERS)) + else: + data[ATTR_FORECAST_WIND_SPEED] = round(wind_speed) else: - data[ATTR_FORECAST_WIND_SPEED] = round(wind_speed_avg) - + data[ATTR_FORECAST_WIND_SPEED] = None forecast.append(data) return forecast diff --git a/requirements_all.txt b/requirements_all.txt index e89f26bb1b7..2646eebacd0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,9 +765,6 @@ mbddns==0.1.2 # homeassistant.components.message_bird messagebird==1.2.0 -# homeassistant.components.nws -metar==1.7.0 - # homeassistant.components.meteoalarm meteoalertapi==0.1.5 @@ -1292,7 +1289,7 @@ pynuki==1.3.3 pynut2==2.1.2 # homeassistant.components.nws -pynws==0.6 +pynws==0.7.0 # homeassistant.components.nx584 pynx584==0.4 diff --git a/tests/components/nws/test_nws.py b/tests/components/nws/test_nws.py index b2d32f68001..211432380d1 100644 --- a/tests/components/nws/test_nws.py +++ b/tests/components/nws/test_nws.py @@ -26,129 +26,74 @@ from homeassistant.setup import setup_component from tests.common import get_test_home_assistant, MockDependency - -OBS = [{ - 'temperature': {'value': 7, 'qualityControl': 'qc:V'}, - 'relativeHumidity': {'value': 10, 'qualityControl': 'qc:V'}, - 'windChill': {'value': 10, 'qualityControl': 'qc:V'}, - 'heatIndex': {'value': 10, 'qualityControl': 'qc:V'}, - 'windDirection': {'value': 180, 'qualityControl': 'qc:V'}, - 'visibility': {'value': 10000, 'qualityControl': 'qc:V'}, - 'windSpeed': {'value': 10, 'qualityControl': 'qc:V'}, - 'seaLevelPressure': {'value': 30000, 'qualityControl': 'qc:V'}, - 'windGust': {'value': 10, 'qualityControl': 'qc:V'}, - 'dewpoint': {'value': 10, 'qualityControl': 'qc:V'}, - 'icon': 'https://api.weather.gov/icons/land/day/skc?size=medium', - 'textDescription': 'Sunny' -}] - -METAR_MSG = ("PHNG 182257Z 06012KT 10SM FEW020 SCT026 SCT035 " - "28/22 A3007 RMK AO2 SLP177 T02780217") - -OBS_METAR = [{ - "rawMessage": METAR_MSG, - "textDescription": "Partly Cloudy", - "icon": "https://api.weather.gov/icons/land/day/sct?size=medium", - "temperature": {"value": None, "qualityControl": "qc:Z"}, - "windDirection": {"value": None, "qualityControl": "qc:Z"}, - "windSpeed": {"value": None, "qualityControl": "qc:Z"}, - "seaLevelPressure": {"value": None, "qualityControl": "qc:Z"}, - "visibility": {"value": None, "qualityControl": "qc:Z"}, - "relativeHumidity": {"value": None, "qualityControl": "qc:Z"}, -}] - -OBS_NONE = [{ - "rawMessage": None, - "textDescription": None, - "icon": None, - "temperature": {"value": None, "qualityControl": "qc:Z"}, - "windDirection": {"value": None, "qualityControl": "qc:Z"}, - "windSpeed": {"value": None, "qualityControl": "qc:Z"}, - "seaLevelPressure": {"value": None, "qualityControl": "qc:Z"}, - "visibility": {"value": None, "qualityControl": "qc:Z"}, - "relativeHumidity": {"value": None, "qualityControl": "qc:Z"}, -}] - +OBS = { + 'temperature': 7, + 'relativeHumidity': 10, + 'windBearing': 180, + 'visibility': 10000, + 'windSpeed': 10, + 'seaLevelPressure': 30000, + 'iconTime': 'day', + 'iconWeather': (('Fair/clear', None),), + } FORE = [{ - 'endTime': '2018-12-21T18:00:00-05:00', - 'windSpeed': '8 to 10 mph', - 'windDirection': 'S', - 'shortForecast': 'Chance Showers And Thunderstorms', - 'isDaytime': True, - 'startTime': '2018-12-21T15:00:00-05:00', - 'temperatureTrend': None, 'temperature': 41, - 'temperatureUnit': 'F', - 'detailedForecast': 'A detailed description', - 'name': 'This Afternoon', - 'number': 1, - 'icon': 'https://api.weather.gov/icons/land/day/skc/tsra,40?size=medium' -}] - -HOURLY_FORE = [{ - 'endTime': '2018-12-22T05:00:00-05:00', - 'windSpeed': '4 mph', - 'windDirection': 'N', - 'shortForecast': 'Chance Showers And Thunderstorms', - 'startTime': '2018-12-22T04:00:00-05:00', - 'temperatureTrend': None, - 'temperature': 32, - 'temperatureUnit': 'F', - 'detailedForecast': '', - 'number': 2, - 'icon': 'https://api.weather.gov/icons/land/night/skc?size=medium' + 'windBearing': 180, + 'windSpeedAvg': 9, + 'iconTime': 'day', + 'startTime': '2018-12-21T15:00:00-05:00', + 'iconWeather': (('Fair/Clear', None), + ('Thunderstorm (high cloud cover)', 40),), }] STN = 'STNA' -class MockNws(): +class MockNws: """Mock Station from pynws.""" - def __init__(self, websession, latlon, userid): + data_obs = None + data_fore = None + + error_obs = False + error_fore = False + error_stn = False + + def __init__(self, lat, lon, userid, mode, session): """Init mock nws.""" - pass + self.station = None + self.stations = None - async def observations(self, limit): + async def update_observation(self): + """Mock observation update.""" + if self.error_obs: + raise aiohttp.ClientError + return + + async def update_forecast(self): + """Mock forecast update.""" + if self.error_fore: + raise aiohttp.ClientError + return + + @property + def observation(self): """Mock Observation.""" - return OBS + return self.data_obs - async def forecast(self): + @property + def forecast(self): """Mock Forecast.""" - return FORE + return self.data_fore - async def forecast_hourly(self): - """Mock Hourly Forecast.""" - return HOURLY_FORE - - async def stations(self): + async def set_station(self, station=None): """Mock stations.""" - return [STN] - - -class Prop: - """Property data class for metar. Initialize with desired return value.""" - - def __init__(self, value_return): - """Initialize with desired return.""" - self.value_return = value_return - - def value(self, units=''): - """Return provided value.""" - return self.value_return - - -class MockMetar: - """Mock Metar parser.""" - - def __init__(self, code): - """Set up mocked return values.""" - self.temp = Prop(27) - self.press = Prop(1111) - self.wind_speed = Prop(27) - self.wind_dir = Prop(175) - self.vis = Prop(5000) + if self.error_stn: + raise aiohttp.ClientError + self.stations = [STN] + self.station = station or STN + return class TestNWS(unittest.TestCase): @@ -161,15 +106,25 @@ class TestNWS(unittest.TestCase): self.lat = self.hass.config.latitude = 40.00 self.lon = self.hass.config.longitude = -8.00 + # Initialize class variables as tests modify them + MockNws.data_obs = None + MockNws.data_fore = None + + MockNws.error_obs = False + MockNws.error_fore = False + MockNws.error_stn = False + def tearDown(self): """Stop down everything that was started.""" self.hass.stop() - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_w_name(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws(self, mock_pynws): + """Test for successfully setting up with imperial.""" + mock_pynws.SimpleNWS.data_obs = OBS + mock_pynws.SimpleNWS.data_fore = FORE + assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'name': 'HomeWeather', @@ -203,137 +158,14 @@ class TestNWS(unittest.TestCase): assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180 assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == 9 - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_w_station(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with station.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - 'station': 'STNB', - 'api_key': 'test_email', - } - }) + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_metric(self, mock_pynws): + """Test for successfully setting up with metric.""" + mock_pynws.SimpleNWS.data_obs = OBS + mock_pynws.SimpleNWS.data_fore = FORE - assert self.hass.states.get('weather.stnb') - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_w_no_name(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform w no name.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - 'api_key': 'test_email', - } - }) - - assert self.hass.states.get('weather.' + STN) - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test__hourly(self, mock_metar, mock_pynws): - """Test for successfully setting up hourly forecast.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HourlyWeather', - 'platform': 'nws', - 'api_key': 'test_email', - 'mode': 'hourly', - } - }) - - state = self.hass.states.get('weather.hourlyweather') - data = state.attributes - - forecast = data.get(ATTR_FORECAST) - assert forecast[0].get(ATTR_FORECAST_CONDITION) == 'clear-night' - assert forecast[0].get(ATTR_FORECAST_PRECIP_PROB) is None - assert forecast[0].get(ATTR_FORECAST_TEMP) == 32 - assert forecast[0].get(ATTR_FORECAST_TIME) == \ - '2018-12-22T04:00:00-05:00' - assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 0 - assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == 4 - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_daynight(self, mock_metar, mock_pynws): - """Test for successfully setting up daynight forecast.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - 'api_key': 'test_email', - 'mode': 'daynight', - } - }) - assert self.hass.states.get('weather.' + STN) - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_latlon(self, mock_metar, mock_pynws): - """Test for successsfully setting up the NWS platform with lat/lon.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - 'api_key': 'test_email', - 'latitude': self.lat, - 'longitude': self.lon, - } - }) - assert self.hass.states.get('weather.' + STN) - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_setup_failure_mode(self, mock_metar, mock_pynws): - """Test for unsuccessfully setting up incorrect mode.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - 'api_key': 'test_email', - 'mode': 'abc', - } - }) - assert self.hass.states.get('weather.' + STN) is None - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_setup_failure_no_apikey(self, mock_metar, mock_pynws): - """Test for unsuccessfully setting up without api_key.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'platform': 'nws', - } - }) - - assert self.hass.states.get('weather.' + STN) is None - - -class TestNwsMetric(unittest.TestCase): - """Test the NWS weather component using metric units.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - self.lat = self.hass.config.latitude = 40.00 - self.lon = self.hass.config.longitude = -8.00 - - def tearDown(self): - """Stop down everything that was started.""" - self.hass.stop() - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_metric(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'name': 'HomeWeather', @@ -346,14 +178,13 @@ class TestNwsMetric(unittest.TestCase): assert state.state == 'sunny' data = state.attributes + temp_f = convert_temperature(7, TEMP_CELSIUS, TEMP_FAHRENHEIT) assert data.get(ATTR_WEATHER_TEMPERATURE) == \ - display_temp(self.hass, 7, TEMP_CELSIUS, PRECISION_WHOLE) - + display_temp(self.hass, temp_f, TEMP_FAHRENHEIT, PRECISION_WHOLE) assert data.get(ATTR_WEATHER_HUMIDITY) == 10 assert data.get(ATTR_WEATHER_PRESSURE) == round( convert_pressure(30000, PRESSURE_PA, PRESSURE_HPA)) - # m/s to km/hr - assert data.get(ATTR_WEATHER_WIND_SPEED) == round(10 * 3.6) + assert data.get(ATTR_WEATHER_WIND_SPEED) == round(3.6 * 10) assert data.get(ATTR_WEATHER_WIND_BEARING) == 180 assert data.get(ATTR_WEATHER_VISIBILITY) == round( convert_distance(10000, LENGTH_METERS, LENGTH_KILOMETERS)) @@ -362,47 +193,18 @@ class TestNwsMetric(unittest.TestCase): forecast = data.get(ATTR_FORECAST) assert forecast[0].get(ATTR_FORECAST_CONDITION) == 'lightning-rainy' assert forecast[0].get(ATTR_FORECAST_PRECIP_PROB) == 40 - assert forecast[0].get(ATTR_FORECAST_TEMP) == round( - convert_temperature(41, TEMP_FAHRENHEIT, TEMP_CELSIUS)) + assert forecast[0].get(ATTR_FORECAST_TEMP) == convert_temperature( + 41, TEMP_FAHRENHEIT, TEMP_CELSIUS) assert forecast[0].get(ATTR_FORECAST_TIME) == \ '2018-12-21T15:00:00-05:00' assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180 assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == round( convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS)) - -class MockNws_Metar(MockNws): - """Mock Station from pynws.""" - - def __init__(self, websession, latlon, userid): - """Init mock nws.""" - pass - - async def observations(self, limit): - """Mock Observation.""" - return OBS_METAR - - -class TestNWS_Metar(unittest.TestCase): - """Test the NWS weather component with metar code.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.units = IMPERIAL_SYSTEM - self.lat = self.hass.config.latitude = 40.00 - self.lon = self.hass.config.longitude = -8.00 - - def tearDown(self): - """Stop down everything that was started.""" - self.hass.stop() - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws_Metar) - @patch("metar.Metar.Metar", new=MockMetar) - def test_metar(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_no_obs_fore1x(self, mock_pynws): + """Test with no data.""" assert setup_component(self.hass, weather.DOMAIN, { 'weather': { 'name': 'HomeWeather', @@ -411,154 +213,15 @@ class TestNWS_Metar(unittest.TestCase): } }) - from metar import Metar - truth = Metar.Metar(METAR_MSG) - state = self.hass.states.get('weather.homeweather') - data = state.attributes - - temp_f = convert_temperature(truth.temp.value(), TEMP_CELSIUS, - TEMP_FAHRENHEIT) - assert data.get(ATTR_WEATHER_TEMPERATURE) == \ - display_temp(self.hass, temp_f, TEMP_FAHRENHEIT, PRECISION_WHOLE) - assert data.get(ATTR_WEATHER_HUMIDITY) is None - assert data.get(ATTR_WEATHER_PRESSURE) == round( - convert_pressure(truth.press.value(), PRESSURE_HPA, PRESSURE_INHG), - 2) - - wind_speed_mi_s = convert_distance( - truth.wind_speed.value(), LENGTH_METERS, LENGTH_MILES) - assert data.get(ATTR_WEATHER_WIND_SPEED) == round( - wind_speed_mi_s * 3600) - assert data.get(ATTR_WEATHER_WIND_BEARING) == truth.wind_dir.value() - vis = convert_distance(truth.vis.value(), LENGTH_METERS, LENGTH_MILES) - assert data.get(ATTR_WEATHER_VISIBILITY) == round(vis) - - -class MockNwsFailObs(MockNws): - """Mock Station from pynws.""" - - def __init__(self, websession, latlon, userid): - """Init mock nws.""" - pass - - async def observations(self, limit): - """Mock Observation.""" - raise aiohttp.ClientError - - -class MockNwsFailStn(MockNws): - """Mock Station from pynws.""" - - def __init__(self, websession, latlon, userid): - """Init mock nws.""" - pass - - async def stations(self): - """Mock Observation.""" - raise aiohttp.ClientError - - -class MockNwsFailFore(MockNws): - """Mock Station from pynws.""" - - def __init__(self, websession, latlon, userid): - """Init mock nws.""" - pass - - async def forecast(self): - """Mock Observation.""" - raise aiohttp.ClientError - - -class MockNws_NoObs(MockNws): - """Mock Station from pynws.""" - - def __init__(self, websession, latlon, userid): - """Init mock nws.""" - pass - - async def observations(self, limit): - """Mock Observation.""" - return OBS_NONE - - -class TestFailures(unittest.TestCase): - """Test the NWS weather component.""" - - def setUp(self): - """Set up things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.config.units = IMPERIAL_SYSTEM - self.lat = self.hass.config.latitude = 40.00 - self.lon = self.hass.config.longitude = -8.00 - - def tearDown(self): - """Stop down everything that was started.""" - self.hass.stop() - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNwsFailObs) - def test_obs_fail(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HomeWeather', - 'platform': 'nws', - 'api_key': 'test_email', - } - }) - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNwsFailStn) - def test_fail_stn(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HomeWeather', - 'platform': 'nws', - 'api_key': 'test_email', - } - }) - state = self.hass.states.get('weather.homeweather') - assert state is None - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNwsFailFore) - def test_fail_fore(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HomeWeather', - 'platform': 'nws', - 'api_key': 'test_email', - } - }) - - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws_NoObs) - def test_no_obs(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" - assert setup_component(self.hass, weather.DOMAIN, { - 'weather': { - 'name': 'HomeWeather', - 'platform': 'nws', - 'api_key': 'test_email', - } - }) state = self.hass.states.get('weather.homeweather') assert state.state == 'unknown' - @MockDependency("metar") - @MockDependency("pynws") - @patch("pynws.Nws", new=MockNws) - def test_no_lat(self, mock_metar, mock_pynws): - """Test for successfully setting up the NWS platform with name.""" - hass = self.hass - hass.config.latitude = None + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_missing_valuess(self, mock_pynws): + """Test with missing data.""" + mock_pynws.SimpleNWS.data_obs = {key: None for key in OBS} + mock_pynws.SimpleNWS.data_fore = [{key: None for key in FORE[0]}] assert setup_component(self.hass, weather.DOMAIN, { 'weather': { @@ -568,5 +231,55 @@ class TestFailures(unittest.TestCase): } }) + state = self.hass.states.get('weather.homeweather') + assert state.state == 'unknown' + + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_error_obs(self, mock_pynws): + """Test for successfully setting up the NWS platform with name.""" + mock_pynws.SimpleNWS.error_obs = True + + assert setup_component(self.hass, weather.DOMAIN, { + 'weather': { + 'name': 'HomeWeather', + 'platform': 'nws', + 'api_key': 'test_email', + } + }) + + state = self.hass.states.get('weather.homeweather') + assert state.state == 'unknown' + + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_error_fore(self, mock_pynws): + """Test error forecast.""" + mock_pynws.SimpleNWS.error_fore = True + assert setup_component(self.hass, weather.DOMAIN, { + 'weather': { + 'name': 'HomeWeather', + 'platform': 'nws', + 'api_key': 'test_email', + } + }) + + state = self.hass.states.get('weather.homeweather') + data = state.attributes + assert data.get('forecast') is None + + @MockDependency('pynws') + @patch("pynws.SimpleNWS", new=MockNws) + def test_nws_error_stn(self, mock_pynws): + """Test station error..""" + mock_pynws.SimpleNWS.error_stn = True + assert setup_component(self.hass, weather.DOMAIN, { + 'weather': { + 'name': 'HomeWeather', + 'platform': 'nws', + 'api_key': 'test_email', + } + }) + state = self.hass.states.get('weather.homeweather') assert state is None