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."""
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."""
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 = {
"config": {
"name": "0123456789ABCDEF",
"longitude": "62.0022",
"latitude": "17.0022",
}
}
from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
async def test_forward_async_setup_entry() -> None:
"""Test that it will forward setup entry."""
hass = Mock()
async def test_setup_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> 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
assert len(hass.config_entries.async_setup_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
async def test_forward_async_unload_entry() -> None:
"""Test that it will forward unload entry."""
hass = AsyncMock()
hass.config_entries.async_unload_platforms = AsyncMock(return_value=True)
assert await smhi.async_unload_entry(hass, {}) is True
assert len(hass.config_entries.async_unload_platforms.mock_calls) == 1
async def test_remove_entry(hass: HomeAssistant) -> None:
"""Test remove entry."""
entry = MockConfigEntry(domain=DOMAIN, data=TEST_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(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."""
import asyncio
from datetime import datetime
import logging
from unittest.mock import AsyncMock, Mock, patch
from datetime import datetime, timedelta
from unittest.mock import 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 (
ATTR_SMHI_CLOUDINESS,
ATTR_SMHI_THUNDER_PROBABILITY,
ATTR_SMHI_WIND_GUST_SPEED,
)
from homeassistant.components.smhi.weather import CONDITION_CLASSES, RETRY_TIMEOUT
from homeassistant.components.weather import (
ATTR_FORECAST,
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_PRECIPITATION,
ATTR_FORECAST_TEMP,
@ -25,40 +26,36 @@ from homeassistant.components.weather import (
ATTR_WEATHER_VISIBILITY,
ATTR_WEATHER_WIND_BEARING,
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.util.dt import utcnow
from tests.common import MockConfigEntry, load_fixture
from . import ENTITY_ID, TEST_CONFIG
_LOGGER = logging.getLogger(__name__)
TEST_CONFIG = {"name": "test", "longitude": "17.84197", "latitude": "59.32624"}
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
async def test_setup_hass(hass: HomeAssistant, aioclient_mock) -> None:
"""Test for successfully setting up the smhi platform.
This test are deeper integrated with the core. Since only
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
"""
async def test_setup_hass(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None:
"""Test for successfully setting up the smhi integration."""
uri = APIURL_TEMPLATE.format(TEST_CONFIG["longitude"], TEST_CONFIG["latitude"])
api_response = load_fixture("smhi.json")
aioclient_mock.get(uri, text=api_response)
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()
assert aioclient_mock.call_count == 1
# Testing the actual entity state for
# 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.attributes[ATTR_SMHI_CLOUDINESS] == 50
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_WIND_SPEED] == 7
assert state.attributes[ATTR_WEATHER_WIND_BEARING] == 134
_LOGGER.error(state.attributes)
assert len(state.attributes["forecast"]) == 4
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"
def test_properties_no_data(hass: HomeAssistant) -> None:
async def test_properties_no_data(hass: HomeAssistant) -> None:
"""Test properties when no API data available."""
weather = weather_smhi.SmhiWeather("name", "10", "10")
weather.hass = hass
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry.add_to_hass(hass)
assert weather.name == "name"
assert weather.should_poll is True
assert weather.temperature is None
assert weather.humidity is None
assert weather.wind_speed is None
assert weather.wind_gust_speed is None
assert weather.wind_bearing is None
assert weather.visibility is None
assert weather.pressure is None
assert weather.cloudiness is None
assert weather.thunder_probability is None
assert weather.condition is None
assert weather.forecast is None
assert weather.temperature_unit == TEMP_CELSIUS
with patch(
"homeassistant.components.smhi.weather.Smhi.async_get_forecast",
side_effect=SmhiForecastException("boom"),
):
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 (
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
def test_properties_unknown_symbol() -> None:
async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
"""Test behaviour when unknown symbol from API."""
hass = Mock()
data = Mock()
data.temperature = 5
data.mean_precipitation = 0.5
data.total_precipitation = 1
data.humidity = 5
data.wind_speed = 10
data.wind_gust_speed = 17
data.wind_direction = 180
data.horizontal_visibility = 6
data.pressure = 1008
data.cloudiness = 52
data.thunder_probability = 41
data.symbol = 100 # Faulty symbol
data.valid_time = datetime(2018, 1, 1, 0, 1, 2)
data = SmhiForecast(
temperature=5,
temperature_max=10,
temperature_min=0,
humidity=5,
pressure=1008,
thunder=0,
cloudiness=52,
precipitation=1,
wind_direction=180,
wind_speed=10,
horizontal_visibility=6,
wind_gust=1.5,
mean_precipitation=0.5,
total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 1, 0, 1, 2),
)
data2 = Mock()
data2.temperature = 5
data2.mean_precipitation = 0.5
data2.total_precipitation = 1
data2.humidity = 5
data2.wind_speed = 10
data2.wind_gust_speed = 17
data2.wind_direction = 180
data2.horizontal_visibility = 6
data2.pressure = 1008
data2.cloudiness = 52
data2.thunder_probability = 41
data2.symbol = 100 # Faulty symbol
data2.valid_time = datetime(2018, 1, 1, 12, 1, 2)
data2 = SmhiForecast(
temperature=5,
temperature_max=10,
temperature_min=0,
humidity=5,
pressure=1008,
thunder=0,
cloudiness=52,
precipitation=1,
wind_direction=180,
wind_speed=10,
horizontal_visibility=6,
wind_gust=1.5,
mean_precipitation=0.5,
total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 1, 12, 1, 2),
)
data3 = Mock()
data3.temperature = 5
data3.mean_precipitation = 0.5
data3.total_precipitation = 1
data3.humidity = 5
data3.wind_speed = 10
data3.wind_gust_speed = 17
data3.wind_direction = 180
data3.horizontal_visibility = 6
data3.pressure = 1008
data3.cloudiness = 52
data3.thunder_probability = 41
data3.symbol = 100 # Faulty symbol
data3.valid_time = datetime(2018, 1, 2, 12, 1, 2)
data3 = SmhiForecast(
temperature=5,
temperature_max=10,
temperature_min=0,
humidity=5,
pressure=1008,
thunder=0,
cloudiness=52,
precipitation=1,
wind_direction=180,
wind_speed=10,
horizontal_visibility=6,
wind_gust=1.5,
mean_precipitation=0.5,
total_precipitation=1,
symbol=100, # Faulty symbol
valid_time=datetime(2018, 1, 2, 12, 1, 2),
)
testdata = [data, data2, data3]
weather = weather_smhi.SmhiWeather("name", "10", "10")
weather.hass = hass
weather._forecasts = testdata
assert weather.condition is None
forecast = weather.forecast[0]
assert forecast[ATTR_FORECAST_CONDITION] is None
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry.add_to_hass(hass)
with patch(
"homeassistant.components.smhi.weather.Smhi.async_get_forecast",
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
async def test_refresh_weather_forecast_exceeds_retries(hass) -> None:
@pytest.mark.parametrize("error", [SmhiForecastException(), asyncio.TimeoutError()])
async def test_refresh_weather_forecast_retry(
hass: HomeAssistant, error: Exception
) -> None:
"""Test the refresh weather forecast function."""
entry = MockConfigEntry(domain="smhi", data=TEST_CONFIG)
entry.add_to_hass(hass)
now = utcnow()
with patch.object(
hass.helpers.event, "async_call_later"
) as call_later, patch.object(
weather_smhi.SmhiWeather,
"get_weather_forecast",
side_effect=SmhiForecastException(),
):
with patch(
"homeassistant.components.smhi.weather.Smhi.async_get_forecast",
side_effect=error,
) as mock_get_forecast:
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022")
weather.hass = hass
weather._fail_count = 2
state = hass.states.get(ENTITY_ID)
await weather.async_update()
assert weather._forecasts is None
assert not call_later.mock_calls
assert state
assert state.name == "test"
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:
"""Test timeout exception."""
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022")
weather.hass = hass
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNKNOWN
assert mock_get_forecast.call_count == 2
with patch.object(
hass.helpers.event, "async_call_later"
) as call_later, patch.object(
weather_smhi.SmhiWeather, "retry_update"
), patch.object(
weather_smhi.SmhiWeather,
"get_weather_forecast",
side_effect=asyncio.TimeoutError,
):
future = future + timedelta(seconds=RETRY_TIMEOUT + 1)
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
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
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNKNOWN
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:
"""Test any exception."""
hass = Mock()
weather = weather_smhi.SmhiWeather("name", "17.0022", "62.0022")
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
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNKNOWN
# after three failed retries we stop retrying and go back to normal interval
assert mock_get_forecast.call_count == 3
def test_condition_class():
@ -239,7 +249,7 @@ def test_condition_class():
def get_condition(index: int) -> str:
"""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
# http://opendata.smhi.se/apidocs/metfcst/parameters.html