From 47183ce02ea063ae48095ecab2dd8b62fbef6858 Mon Sep 17 00:00:00 2001 From: cgtobi Date: Thu, 23 Nov 2017 21:45:56 +0100 Subject: [PATCH] Temporarily fix yahoo weather API issue and add unit test. (#10737) * Temporarily fix yahoo weather API issue and add unit test. * Add test data. --- .coveragerc | 1 - homeassistant/components/sensor/yweather.py | 8 +- tests/components/sensor/test_yweather.py | 247 ++++++++++++++++++++ tests/fixtures/yahooweather.json | 138 +++++++++++ 4 files changed, 390 insertions(+), 4 deletions(-) create mode 100644 tests/components/sensor/test_yweather.py create mode 100644 tests/fixtures/yahooweather.json diff --git a/.coveragerc b/.coveragerc index f609b5cb053..1a8d8efc4ec 100644 --- a/.coveragerc +++ b/.coveragerc @@ -594,7 +594,6 @@ omit = homeassistant/components/sensor/worldtidesinfo.py homeassistant/components/sensor/worxlandroid.py homeassistant/components/sensor/xbox_live.py - homeassistant/components/sensor/yweather.py homeassistant/components/sensor/zamg.py homeassistant/components/shiftr.py homeassistant/components/spc.py diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index 873e27975db..846b221d5e3 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -160,13 +160,15 @@ class YahooWeatherSensor(Entity): self._code = self._data.yahoo.Forecast[self._forecast]['code'] self._state = self._data.yahoo.Forecast[self._forecast]['high'] elif self._type == 'wind_speed': - self._state = self._data.yahoo.Wind['speed'] + self._state = round(float(self._data.yahoo.Wind['speed'])/1.61, 2) elif self._type == 'humidity': self._state = self._data.yahoo.Atmosphere['humidity'] elif self._type == 'pressure': - self._state = self._data.yahoo.Atmosphere['pressure'] + self._state = round( + float(self._data.yahoo.Atmosphere['pressure'])/33.8637526, 2) elif self._type == 'visibility': - self._state = self._data.yahoo.Atmosphere['visibility'] + self._state = round( + float(self._data.yahoo.Atmosphere['visibility'])/1.61, 2) class YahooWeatherData(object): diff --git a/tests/components/sensor/test_yweather.py b/tests/components/sensor/test_yweather.py new file mode 100644 index 00000000000..88b94906a35 --- /dev/null +++ b/tests/components/sensor/test_yweather.py @@ -0,0 +1,247 @@ +"""The tests for the Yahoo weather sensor component.""" +import json + +import unittest +from unittest.mock import patch + +from homeassistant.setup import setup_component + +from tests.common import (get_test_home_assistant, load_fixture, + MockDependency) + +VALID_CONFIG_MINIMAL = { + 'sensor': { + 'platform': 'yweather', + 'monitored_conditions': [ + 'weather', + ], + } +} + +VALID_CONFIG_ALL = { + 'sensor': { + 'platform': 'yweather', + 'monitored_conditions': [ + 'weather', + 'weather_current', + 'temperature', + 'temp_min', + 'temp_max', + 'wind_speed', + 'pressure', + 'visibility', + 'humidity', + ], + } +} + +BAD_CONF_RAW = { + 'sensor': { + 'platform': 'yweather', + 'woeid': '12345', + 'monitored_conditions': [ + 'weather', + ], + } +} + +BAD_CONF_DATA = { + 'sensor': { + 'platform': 'yweather', + 'woeid': '111', + 'monitored_conditions': [ + 'weather', + ], + } +} + + +def _yql_queryMock(yql): # pylint: disable=invalid-name + """Mock yahoo query language query.""" + return ('{"query": {"count": 1, "created": "2017-11-17T13:40:47Z", ' + '"lang": "en-US", "results": {"place": {"woeid": "23511632"}}}}') + + +def get_woeidMock(lat, lon): # pylint: disable=invalid-name + """Mock get woeid Where On Earth Identifiers.""" + return '23511632' + + +def get_woeidNoneMock(lat, lon): # pylint: disable=invalid-name + """Mock get woeid Where On Earth Identifiers.""" + return None + + +class YahooWeatherMock(): + """Mock class for the YahooWeather object.""" + + def __init__(self, woeid, temp_unit): + """Initialize Telnet object.""" + self.woeid = woeid + self.temp_unit = temp_unit + self._data = json.loads(load_fixture('yahooweather.json')) + + # pylint: disable=no-self-use + def updateWeather(self): # pylint: disable=invalid-name + """Return sample values.""" + return True + + @property + def RawData(self): # pylint: disable=invalid-name + """Raw Data.""" + if self.woeid == '12345': + return json.loads('[]') + return self._data + + @property + def Units(self): # pylint: disable=invalid-name + """Return dict with units.""" + return self._data['query']['results']['channel']['units'] + + @property + def Now(self): # pylint: disable=invalid-name + """Current weather data.""" + if self.woeid == '111': + raise ValueError + return self._data['query']['results']['channel']['item']['condition'] + + @property + def Atmosphere(self): # pylint: disable=invalid-name + """Atmosphere weather data.""" + return self._data['query']['results']['channel']['atmosphere'] + + @property + def Wind(self): # pylint: disable=invalid-name + """Wind weather data.""" + return self._data['query']['results']['channel']['wind'] + + @property + def Forecast(self): # pylint: disable=invalid-name + """Forecast data 0-5 Days.""" + return self._data['query']['results']['channel']['item']['forecast'] + + def getWeatherImage(self, code): # pylint: disable=invalid-name + """Create a link to weather image from yahoo code.""" + return "https://l.yimg.com/a/i/us/we/52/{}.gif".format(code) + + +class TestWeather(unittest.TestCase): + """Test the Yahoo weather component.""" + + def setUp(self): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def tearDown(self): + """Stop down everything that was started.""" + self.hass.stop() + + @MockDependency('yahooweather') + @patch('yahooweather._yql_query', new=_yql_queryMock) + @patch('yahooweather.get_woeid', new=get_woeidMock) + @patch('yahooweather.YahooWeather', new=YahooWeatherMock) + def test_setup_minimal(self, mock_yahooweather): + """Test for minimal weather sensor config.""" + assert setup_component(self.hass, 'sensor', VALID_CONFIG_MINIMAL) + + state = self.hass.states.get('sensor.yweather_condition') + assert state is not None + + assert state.state == 'Mostly Cloudy' + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Condition') + + @MockDependency('yahooweather') + @patch('yahooweather._yql_query', new=_yql_queryMock) + @patch('yahooweather.get_woeid', new=get_woeidMock) + @patch('yahooweather.YahooWeather', new=YahooWeatherMock) + def test_setup_all(self, mock_yahooweather): + """Test for all weather data attributes.""" + assert setup_component(self.hass, 'sensor', VALID_CONFIG_ALL) + + state = self.hass.states.get('sensor.yweather_condition') + assert state is not None + self.assertEqual(state.state, 'Mostly Cloudy') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Condition') + + state = self.hass.states.get('sensor.yweather_current') + assert state is not None + self.assertEqual(state.state, 'Cloudy') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Current') + + state = self.hass.states.get('sensor.yweather_temperature') + assert state is not None + self.assertEqual(state.state, '18') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Temperature') + + state = self.hass.states.get('sensor.yweather_temperature_max') + assert state is not None + self.assertEqual(state.state, '23') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Temperature max') + + state = self.hass.states.get('sensor.yweather_temperature_min') + assert state is not None + self.assertEqual(state.state, '16') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Temperature min') + + state = self.hass.states.get('sensor.yweather_wind_speed') + assert state is not None + self.assertEqual(state.state, '3.94') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Wind speed') + + state = self.hass.states.get('sensor.yweather_pressure') + assert state is not None + self.assertEqual(state.state, '1000.0') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Pressure') + + state = self.hass.states.get('sensor.yweather_visibility') + assert state is not None + self.assertEqual(state.state, '14.23') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Visibility') + + state = self.hass.states.get('sensor.yweather_humidity') + assert state is not None + self.assertEqual(state.state, '71') + self.assertEqual(state.attributes.get('friendly_name'), + 'Yweather Humidity') + + @MockDependency('yahooweather') + @patch('yahooweather._yql_query', new=_yql_queryMock) + @patch('yahooweather.get_woeid', new=get_woeidNoneMock) + @patch('yahooweather.YahooWeather', new=YahooWeatherMock) + def test_setup_bad_woied(self, mock_yahooweather): + """Test for bad woeid.""" + assert setup_component(self.hass, 'sensor', VALID_CONFIG_MINIMAL) + + state = self.hass.states.get('sensor.yweather_condition') + assert state is None + + @MockDependency('yahooweather') + @patch('yahooweather._yql_query', new=_yql_queryMock) + @patch('yahooweather.get_woeid', new=get_woeidMock) + @patch('yahooweather.YahooWeather', new=YahooWeatherMock) + def test_setup_bad_raw(self, mock_yahooweather): + """Test for bad RawData.""" + assert setup_component(self.hass, 'sensor', BAD_CONF_RAW) + + state = self.hass.states.get('sensor.yweather_condition') + assert state is not None + + @MockDependency('yahooweather') + @patch('yahooweather._yql_query', new=_yql_queryMock) + @patch('yahooweather.get_woeid', new=get_woeidMock) + @patch('yahooweather.YahooWeather', new=YahooWeatherMock) + def test_setup_bad_data(self, mock_yahooweather): + """Test for bad data.""" + assert setup_component(self.hass, 'sensor', BAD_CONF_DATA) + + state = self.hass.states.get('sensor.yweather_condition') + assert state is None diff --git a/tests/fixtures/yahooweather.json b/tests/fixtures/yahooweather.json new file mode 100644 index 00000000000..f6ab2980618 --- /dev/null +++ b/tests/fixtures/yahooweather.json @@ -0,0 +1,138 @@ +{ + "query": { + "count": 1, + "created": "2017-11-17T13:40:47Z", + "lang": "en-US", + "results": { + "channel": { + "units": { + "distance": "km", + "pressure": "mb", + "speed": "km/h", + "temperature": "C" + }, + "title": "Yahoo! Weather - San Diego, CA, US", + "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-23511632/", + "description": "Yahoo! Weather for San Diego, CA, US", + "language": "en-us", + "lastBuildDate": "Fri, 17 Nov 2017 05:40 AM PST", + "ttl": "60", + "location": { + "city": "San Diego", + "country": "United States", + "region": " CA" + }, + "wind": { + "chill": "56", + "direction": "0", + "speed": "6.34" + }, + "atmosphere": { + "humidity": "71", + "pressure": "33863.75", + "rising": "0", + "visibility": "22.91" + }, + "astronomy": { + "sunrise": "6:21 am", + "sunset": "4:47 pm" + }, + "image": { + "title": "Yahoo! Weather", + "width": "142", + "height": "18", + "link": "http://weather.yahoo.com", + "url": "http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif" + }, + "item": { + "title": "Conditions for San Diego, CA, US at 05:00 AM PST", + "lat": "32.878101", + "long": "-117.23497", + "link": "http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-23511632/", + "pubDate": "Fri, 17 Nov 2017 05:00 AM PST", + "condition": { + "code": "26", + "date": "Fri, 17 Nov 2017 05:00 AM PST", + "temp": "18", + "text": "Cloudy" + }, + "forecast": [{ + "code": "28", + "date": "17 Nov 2017", + "day": "Fri", + "high": "23", + "low": "16", + "text": "Mostly Cloudy" + }, { + "code": "30", + "date": "18 Nov 2017", + "day": "Sat", + "high": "22", + "low": "13", + "text": "Partly Cloudy" + }, { + "code": "30", + "date": "19 Nov 2017", + "day": "Sun", + "high": "22", + "low": "12", + "text": "Partly Cloudy" + }, { + "code": "28", + "date": "20 Nov 2017", + "day": "Mon", + "high": "21", + "low": "11", + "text": "Mostly Cloudy" + }, { + "code": "28", + "date": "21 Nov 2017", + "day": "Tue", + "high": "24", + "low": "14", + "text": "Mostly Cloudy" + }, { + "code": "30", + "date": "22 Nov 2017", + "day": "Wed", + "high": "27", + "low": "15", + "text": "Partly Cloudy" + }, { + "code": "34", + "date": "23 Nov 2017", + "day": "Thu", + "high": "27", + "low": "15", + "text": "Mostly Sunny" + }, { + "code": "30", + "date": "24 Nov 2017", + "day": "Fri", + "high": "23", + "low": "16", + "text": "Partly Cloudy" + }, { + "code": "30", + "date": "25 Nov 2017", + "day": "Sat", + "high": "22", + "low": "15", + "text": "Partly Cloudy" + }, { + "code": "28", + "date": "26 Nov 2017", + "day": "Sun", + "high": "24", + "low": "13", + "text": "Mostly Cloudy" + }], + "description": "\n
\nCurrent Conditions:\n
Cloudy\n
\n
\nForecast:\n
Fri - Mostly Cloudy. High: 23Low: 16\n
Sat - Partly Cloudy. High: 22Low: 13\n
Sun - Partly Cloudy. High: 22Low: 12\n
Mon - Mostly Cloudy. High: 21Low: 11\n
Tue - Mostly Cloudy. High: 24Low: 14\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n
\n]]>", + "guid": { + "isPermaLink": "false" + } + } + } + } + } +}