Clean smhi tests (#50681)

This commit is contained in:
Martin Hjelmare 2021-05-15 20:22:32 +02:00 committed by GitHub
parent 562e0d785d
commit dab66a58ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 210 additions and 174 deletions

View File

@ -1 +1,3 @@
"""Tests for the SMHI component.""" """Tests for the SMHI component."""
ENTITY_ID = "weather.smhi_test"
TEST_CONFIG = {"name": "test", "longitude": "17.84197", "latitude": "59.32624"}

View File

@ -0,0 +1,10 @@
"""Provide common smhi fixtures."""
import pytest
from tests.common import load_fixture
@pytest.fixture(scope="session")
def api_response():
"""Return an API response."""
return load_fixture("smhi.json")

View File

@ -1,30 +1,44 @@
"""Test SMHI component setup process.""" """Test SMHI component setup process."""
from unittest.mock import Mock from smhi.smhi_lib import APIURL_TEMPLATE
from homeassistant.components import smhi from homeassistant.components.smhi.const import DOMAIN
from homeassistant.core import HomeAssistant
from .common import AsyncMock from . import ENTITY_ID, TEST_CONFIG
TEST_CONFIG = { from tests.common import MockConfigEntry
"config": { from tests.test_util.aiohttp import AiohttpClientMocker
"name": "0123456789ABCDEF",
"longitude": "62.0022",
"latitude": "17.0022",
}
}
async def test_forward_async_setup_entry() -> None: async def test_setup_entry(
"""Test that it will forward setup entry.""" hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
hass = Mock() ) -> None:
"""Test setup entry."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG)
entry.add_to_hass(hass)
assert await smhi.async_setup_entry(hass, {}) is True await hass.config_entries.async_setup(entry.entry_id)
assert len(hass.config_entries.async_setup_platforms.mock_calls) == 1 await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state
async def test_forward_async_unload_entry() -> None: async def test_remove_entry(hass: HomeAssistant) -> None:
"""Test that it will forward unload entry.""" """Test remove entry."""
hass = AsyncMock() entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG)
hass.config_entries.async_unload_platforms = AsyncMock(return_value=True) entry.add_to_hass(hass)
assert await smhi.async_unload_entry(hass, {}) is True
assert len(hass.config_entries.async_unload_platforms.mock_calls) == 1 await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state
await hass.config_entries.async_remove(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert not state

View File

@ -1,18 +1,19 @@
"""Test for the smhi weather entity.""" """Test for the smhi weather entity."""
import asyncio import asyncio
from datetime import datetime from datetime import datetime, timedelta
import logging from unittest.mock import patch
from unittest.mock import AsyncMock, Mock, patch
from smhi.smhi_lib import APIURL_TEMPLATE, SmhiForecastException import pytest
from smhi.smhi_lib import APIURL_TEMPLATE, SmhiForecast, SmhiForecastException
from homeassistant.components.smhi import weather as weather_smhi
from homeassistant.components.smhi.const import ( from homeassistant.components.smhi.const import (
ATTR_SMHI_CLOUDINESS, ATTR_SMHI_CLOUDINESS,
ATTR_SMHI_THUNDER_PROBABILITY, ATTR_SMHI_THUNDER_PROBABILITY,
ATTR_SMHI_WIND_GUST_SPEED, ATTR_SMHI_WIND_GUST_SPEED,
) )
from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CONDITION, ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_TEMP, ATTR_FORECAST_TEMP,
@ -25,40 +26,36 @@ from homeassistant.components.weather import (
ATTR_WEATHER_VISIBILITY, ATTR_WEATHER_VISIBILITY,
ATTR_WEATHER_WIND_BEARING, ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_SPEED, ATTR_WEATHER_WIND_SPEED,
DOMAIN as WEATHER_DOMAIN,
) )
from homeassistant.const import TEMP_CELSIUS from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, load_fixture from . import ENTITY_ID, TEST_CONFIG
_LOGGER = logging.getLogger(__name__) from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
TEST_CONFIG = {"name": "test", "longitude": "17.84197", "latitude": "59.32624"}
async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None: async def test_setup_hass(
"""Test for successfully setting up the smhi platform. hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
This test are deeper integrated with the core. Since only """Test for successfully setting up the smhi integration."""
config_flow is used the component are setup with
"async_forward_entry_setup". The actual result are tested
with the entity state rather than "per function" unity tests
"""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"]) uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
api_response = load_fixture("smhi.json")
aioclient_mock.get(uri, text=api_response) aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG) entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry.add_to_hass(hass)
await hass.config_entries.async_forward_entry_setup(entry, WEATHER_DOMAIN) await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert aioclient_mock.call_count == 1 assert aioclient_mock.call_count == 1
# Testing the actual entity state for # Testing the actual entity state for
# deeper testing than normal unity test # deeper testing than normal unity test
state = hass.states.get("weather.smhi_test") state = hass.states.get(ENTITY_ID)
assert state
assert state.state == "sunny" assert state.state == "sunny"
assert state.attributes[ATTR_SMHI_CLOUDINESS] == 50 assert state.attributes[ATTR_SMHI_CLOUDINESS] == 50
assert state.attributes[ATTR_SMHI_THUNDER_PROBABILITY] == 33 assert state.attributes[ATTR_SMHI_THUNDER_PROBABILITY] == 33
@ -70,7 +67,6 @@ async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None:
assert state.attributes[ATTR_WEATHER_VISIBILITY] == 50 assert state.attributes[ATTR_WEATHER_VISIBILITY] == 50
assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 7 assert state.attributes[ATTR_WEATHER_WIND_SPEED] == 7
assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 134 assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 134
_LOGGER.error(state.attributes)
assert len(state.attributes["forecast"]) == 4 assert len(state.attributes["forecast"]) == 4
forecast = state.attributes["forecast"][1] forecast = state.attributes["forecast"][1]
@ -81,157 +77,171 @@ async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None:
assert forecast[ATTR_FORECAST_CONDITION] == "partlycloudy" assert forecast[ATTR_FORECAST_CONDITION] == "partlycloudy"
def test_properties_no_data(hass: HomeAssistant) -> None: async def test_properties_no_data(hass: HomeAssistant) -> None:
"""Test properties when no API data available.""" """Test properties when no API data available."""
weather = weather_smhi.SmhiWeather("name", "10", "10") entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
weather.hass = hass entry.add_to_hass(hass)
assert weather.name == "name" with patch(
assert weather.should_poll is True "homeassistant.components.smhi.weather.Smhi.async_get_forecast",
assert weather.temperature is None side_effect=SmhiForecastException("boom"),
assert weather.humidity is None ):
assert weather.wind_speed is None await hass.config_entries.async_setup(entry.entry_id)
assert weather.wind_gust_speed is None await hass.async_block_till_done()
assert weather.wind_bearing is None
assert weather.visibility is None state = hass.states.get(ENTITY_ID)
assert weather.pressure is None
assert weather.cloudiness is None assert state
assert weather.thunder_probability is None assert state.name == "test"
assert weather.condition is None assert state.state == STATE_UNKNOWN
assert weather.forecast is None assert (
assert weather.temperature_unit == TEMP_CELSIUS state.attributes[ATTR_WEATHER_ATTRIBUTION] == "Swedish weather institute (SMHI)"
)
assert ATTR_WEATHER_HUMIDITY not in state.attributes
assert ATTR_WEATHER_PRESSURE not in state.attributes
assert ATTR_WEATHER_TEMPERATURE not in state.attributes
assert ATTR_WEATHER_VISIBILITY not in state.attributes
assert ATTR_WEATHER_WIND_SPEED not in state.attributes
assert ATTR_WEATHER_WIND_BEARING not in state.attributes
assert ATTR_FORECAST not in state.attributes
assert ATTR_SMHI_CLOUDINESS not in state.attributes
assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes
assert ATTR_SMHI_WIND_GUST_SPEED not in state.attributes
# pylint: disable=protected-access async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
def test_properties_unknown_symbol() -> None:
"""Test behaviour when unknown symbol from API.""" """Test behaviour when unknown symbol from API."""
hass = Mock() data = SmhiForecast(
data = Mock() temperature=5,
data.temperature = 5 temperature_max=10,
data.mean_precipitation = 0.5 temperature_min=0,
data.total_precipitation = 1 humidity=5,
data.humidity = 5 pressure=1008,
data.wind_speed = 10 thunder=0,
data.wind_gust_speed = 17 cloudiness=52,
data.wind_direction = 180 precipitation=1,
data.horizontal_visibility = 6 wind_direction=180,
data.pressure = 1008 wind_speed=10,
data.cloudiness = 52 horizontal_visibility=6,
data.thunder_probability = 41 wind_gust=1.5,
data.symbol = 100 # Faulty symbol mean_precipitation=0.5,
data.valid_time = datetime(2018, 1, 1, 0, 1, 2) total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 1, 0, 1, 2),
)
data2 = Mock() data2 = SmhiForecast(
data2.temperature = 5 temperature=5,
data2.mean_precipitation = 0.5 temperature_max=10,
data2.total_precipitation = 1 temperature_min=0,
data2.humidity = 5 humidity=5,
data2.wind_speed = 10 pressure=1008,
data2.wind_gust_speed = 17 thunder=0,
data2.wind_direction = 180 cloudiness=52,
data2.horizontal_visibility = 6 precipitation=1,
data2.pressure = 1008 wind_direction=180,
data2.cloudiness = 52 wind_speed=10,
data2.thunder_probability = 41 horizontal_visibility=6,
data2.symbol = 100 # Faulty symbol wind_gust=1.5,
data2.valid_time = datetime(2018, 1, 1, 12, 1, 2) mean_precipitation=0.5,
total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 1, 12, 1, 2),
)
data3 = Mock() data3 = SmhiForecast(
data3.temperature = 5 temperature=5,
data3.mean_precipitation = 0.5 temperature_max=10,
data3.total_precipitation = 1 temperature_min=0,
data3.humidity = 5 humidity=5,
data3.wind_speed = 10 pressure=1008,
data3.wind_gust_speed = 17 thunder=0,
data3.wind_direction = 180 cloudiness=52,
data3.horizontal_visibility = 6 precipitation=1,
data3.pressure = 1008 wind_direction=180,
data3.cloudiness = 52 wind_speed=10,
data3.thunder_probability = 41 horizontal_visibility=6,
data3.symbol = 100 # Faulty symbol wind_gust=1.5,
data3.valid_time = datetime(2018, 1, 2, 12, 1, 2) mean_precipitation=0.5,
total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 2, 12, 1, 2),
)
testdata = [data, data2, data3] testdata = [data, data2, data3]
weather = weather_smhi.SmhiWeather("name", "10", "10") entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
weather.hass = hass entry.add_to_hass(hass)
weather._forecasts = testdata
assert weather.condition is None with patch(
forecast = weather.forecast[0] "homeassistant.components.smhi.weather.Smhi.async_get_forecast",
assert forecast[ATTR_FORECAST_CONDITION] is None return_value=testdata,
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID)
assert state
assert state.name == "test"
assert state.state == STATE_UNKNOWN
assert ATTR_FORECAST in state.attributes
assert all(
forecast[ATTR_FORECAST_CONDITION] is None
for forecast in state.attributes[ATTR_FORECAST]
)
# pylint: disable=protected-access @pytest.mark.parametrize("error", [SmhiForecastException(), asyncio.TimeoutError()])
async def test_refresh_weather_forecast_exceeds_retries(hass) -> None: async def test_refresh_weather_forecast_retry(
hass: HomeAssistant, error: Exception
) -> None:
"""Test the refresh weather forecast function.""" """Test the refresh weather forecast function."""
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry.add_to_hass(hass)
now = utcnow()
with patch.object( with patch(
hass.helpers.event, "async_call_later" "homeassistant.components.smhi.weather.Smhi.async_get_forecast",
) as call_later, patch.object( side_effect=error,
weather_smhi.SmhiWeather, ) as mock_get_forecast:
"get_weather_forecast", await hass.config_entries.async_setup(entry.entry_id)
side_effect=SmhiForecastException(), await hass.async_block_till_done()
):
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022") state = hass.states.get(ENTITY_ID)
weather.hass = hass
weather._fail_count = 2
await weather.async_update() assert state
assert weather._forecasts is None assert state.name == "test"
assert not call_later.mock_calls assert state.state == STATE_UNKNOWN
assert mock_get_forecast.call_count == 1
future = now + timedelta(seconds=RETRY_TIMEOUT + 1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
async def test_refresh_weather_forecast_timeout(hass) -> None: state = hass.states.get(ENTITY_ID)
"""Test timeout exception.""" assert state
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022") assert state.state == STATE_UNKNOWN
weather.hass = hass assert mock_get_forecast.call_count == 2
with patch.object( future = future + timedelta(seconds=RETRY_TIMEOUT + 1)
hass.helpers.event, "async_call_later" async_fire_time_changed(hass, future)
) as call_later, patch.object( await hass.async_block_till_done()
weather_smhi.SmhiWeather, "retry_update"
), patch.object(
weather_smhi.SmhiWeather,
"get_weather_forecast",
side_effect=asyncio.TimeoutError,
):
await weather.async_update() state = hass.states.get(ENTITY_ID)
assert len(call_later.mock_calls) == 1 assert state
# Assert we are going to wait RETRY_TIMEOUT seconds assert state.state == STATE_UNKNOWN
assert call_later.mock_calls[0][1][0] == weather_smhi.RETRY_TIMEOUT assert mock_get_forecast.call_count == 3
future = future + timedelta(seconds=RETRY_TIMEOUT + 1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
async def test_refresh_weather_forecast_exception() -> None: state = hass.states.get(ENTITY_ID)
"""Test any exception.""" assert state
assert state.state == STATE_UNKNOWN
hass = Mock() # after three failed retries we stop retrying and go back to normal interval
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022") assert mock_get_forecast.call_count == 3
weather.hass = hass
with patch.object(
hass.helpers.event, "async_call_later"
) as call_later, patch.object(
weather,
"get_weather_forecast",
side_effect=SmhiForecastException(),
):
await weather.async_update()
assert len(call_later.mock_calls) == 1
# Assert we are going to wait RETRY_TIMEOUT seconds
assert call_later.mock_calls[0][1][0] == weather_smhi.RETRY_TIMEOUT
async def test_retry_update():
"""Test retry function of refresh forecast."""
hass = Mock()
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022")
weather.hass = hass
with patch.object(weather, "async_update", AsyncMock()) as update:
await weather.retry_update(None)
assert len(update.mock_calls) == 1
def test_condition_class(): def test_condition_class():
@ -239,7 +249,7 @@ def test_condition_class():
def get_condition(index: int) -> str: def get_condition(index: int) -> str:
"""Return condition given index.""" """Return condition given index."""
return [k for k, v in weather_smhi.CONDITION_CLASSES.items() if index in v][0] return [k for k, v in CONDITION_CLASSES.items() if index in v][0]
# SMHI definitions as follows, see # SMHI definitions as follows, see
# http://opendata.smhi.se/apidocs/metfcst/parameters.html # http://opendata.smhi.se/apidocs/metfcst/parameters.html