Modernize tests for smhi (#139334)

* Modernize tests for smhi

* Fixes

* Mods

* Fix weather

* Coverage 100%

* Fix init test

* Fixes

* Fixes

* Remove waits
This commit is contained in:
G Johansson 2025-05-28 10:56:07 +02:00 committed by GitHub
parent 192aa76cd7
commit 4858b2171e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 336 additions and 349 deletions

View File

@ -1,25 +1,137 @@
"""Provide common smhi fixtures.""" """Provide common smhi fixtures."""
from __future__ import annotations
from collections.abc import AsyncGenerator, Generator
import json
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from pysmhi.smhi_forecast import SMHIForecast, SMHIPointForecast
import pytest import pytest
from homeassistant.components.smhi import PLATFORMS
from homeassistant.components.smhi.const import DOMAIN from homeassistant.components.smhi.const import DOMAIN
from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, Platform
from homeassistant.core import HomeAssistant
from tests.common import load_fixture from . import TEST_CONFIG
from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture(scope="package") @pytest.fixture
def api_response(): def mock_setup_entry() -> Generator[AsyncMock]:
"""Return an API response.""" """Override async_setup_entry."""
return load_fixture("smhi.json", DOMAIN) with patch(
"homeassistant.components.smhi.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture(scope="package") @pytest.fixture(name="load_platforms")
def api_response_night(): async def patch_platform_constant() -> list[Platform]:
"""Return an API response for night only.""" """Return list of platforms to load."""
return load_fixture("smhi_night.json", DOMAIN) return PLATFORMS
@pytest.fixture(scope="package") @pytest.fixture
def api_response_lack_data(): async def load_int(
"""Return an API response.""" hass: HomeAssistant,
return load_fixture("smhi_short.json", DOMAIN) mock_client: SMHIPointForecast,
load_platforms: list[Platform],
) -> MockConfigEntry:
"""Set up the SMHI integration."""
hass.config.latitude = "59.32624"
hass.config.longitude = "17.84197"
config_entry = MockConfigEntry(
domain=DOMAIN,
data=TEST_CONFIG,
entry_id="01JMZDH8N5PFHGJNYKKYCSCWER",
unique_id="59.32624-17.84197",
version=3,
title="Test",
)
config_entry.add_to_hass(hass)
with patch("homeassistant.components.smhi.PLATFORMS", load_platforms):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
@pytest.fixture(name="mock_client")
async def get_client(
hass: HomeAssistant,
get_data: tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]],
) -> AsyncGenerator[MagicMock]:
"""Mock SMHIPointForecast client."""
with (
patch(
"homeassistant.components.smhi.coordinator.SMHIPointForecast",
autospec=True,
) as mock_client,
patch(
"homeassistant.components.smhi.config_flow.SMHIPointForecast",
return_value=mock_client.return_value,
),
):
client = mock_client.return_value
client.async_get_daily_forecast.return_value = get_data[0]
client.async_get_twice_daily_forecast.return_value = get_data[1]
client.async_get_hourly_forecast.return_value = get_data[2]
yield client
@pytest.fixture(name="get_data")
async def get_data_from_library(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
load_json: dict[str, Any],
) -> AsyncGenerator[tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]]]:
"""Get data from api."""
client = SMHIPointForecast(
TEST_CONFIG[CONF_LOCATION][CONF_LONGITUDE],
TEST_CONFIG[CONF_LOCATION][CONF_LATITUDE],
aioclient_mock.create_session(hass.loop),
)
with patch.object(
client._api,
"async_get_data",
return_value=load_json,
):
data_daily = await client.async_get_daily_forecast()
data_twice_daily = await client.async_get_twice_daily_forecast()
data_hourly = await client.async_get_hourly_forecast()
yield (data_daily, data_twice_daily, data_hourly)
await client._api._session.close()
@pytest.fixture(name="load_json")
def load_json_from_fixture(
load_data: tuple[str, str, str],
to_load: int,
) -> dict[str, Any]:
"""Load fixture with json data and return."""
return json.loads(load_data[to_load])
@pytest.fixture(name="load_data", scope="package")
def load_data_from_fixture() -> tuple[str, str, str]:
"""Load fixture with fixture data and return."""
return (
load_fixture("smhi.json", "smhi"),
load_fixture("smhi_night.json", "smhi"),
load_fixture("smhi_short.json", "smhi"),
)
@pytest.fixture
def to_load() -> int:
"""Fixture to load."""
return 0

View File

@ -1,5 +1,5 @@
# serializer version: 1 # serializer version: 1
# name: test_clear_night[clear-night_forecast] # name: test_clear_night[1][clear-night_forecast]
dict({ dict({
'weather.smhi_test': dict({ 'weather.smhi_test': dict({
'forecast': list([ 'forecast': list([
@ -59,11 +59,11 @@
}), }),
}) })
# --- # ---
# name: test_clear_night[clear_night] # name: test_clear_night[1][clear_night]
ReadOnlyDict({ ReadOnlyDict({
'attribution': 'Swedish weather institute (SMHI)', 'attribution': 'Swedish weather institute (SMHI)',
'cloud_coverage': 100, 'cloud_coverage': 100,
'friendly_name': 'test', 'friendly_name': 'Test',
'humidity': 100, 'humidity': 100,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>, 'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 992.4, 'pressure': 992.4,
@ -80,7 +80,7 @@
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>, 'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}) })
# --- # ---
# name: test_forecast_service[get_forecasts] # name: test_forecast_service[load_platforms0]
dict({ dict({
'weather.smhi_test': dict({ 'weather.smhi_test': dict({
'forecast': list([ 'forecast': list([
@ -218,7 +218,7 @@
}), }),
}) })
# --- # ---
# name: test_forecast_services # name: test_forecast_services[load_platforms0]
dict({ dict({
'cloud_coverage': 100, 'cloud_coverage': 100,
'condition': 'cloudy', 'condition': 'cloudy',
@ -233,7 +233,7 @@
'wind_speed': 10.08, 'wind_speed': 10.08,
}) })
# --- # ---
# name: test_forecast_services.1 # name: test_forecast_services[load_platforms0].1
dict({ dict({
'cloud_coverage': 75, 'cloud_coverage': 75,
'condition': 'partlycloudy', 'condition': 'partlycloudy',
@ -248,7 +248,7 @@
'wind_speed': 14.76, 'wind_speed': 14.76,
}) })
# --- # ---
# name: test_forecast_services.2 # name: test_forecast_services[load_platforms0].2
dict({ dict({
'cloud_coverage': 100, 'cloud_coverage': 100,
'condition': 'fog', 'condition': 'fog',
@ -263,7 +263,7 @@
'wind_speed': 9.72, 'wind_speed': 9.72,
}) })
# --- # ---
# name: test_forecast_services.3 # name: test_forecast_services[load_platforms0].3
dict({ dict({
'cloud_coverage': 100, 'cloud_coverage': 100,
'condition': 'cloudy', 'condition': 'cloudy',
@ -278,11 +278,11 @@
'wind_speed': 12.24, 'wind_speed': 12.24,
}) })
# --- # ---
# name: test_setup_hass # name: test_setup_hass[load_platforms0]
ReadOnlyDict({ ReadOnlyDict({
'attribution': 'Swedish weather institute (SMHI)', 'attribution': 'Swedish weather institute (SMHI)',
'cloud_coverage': 100, 'cloud_coverage': 100,
'friendly_name': 'test', 'friendly_name': 'Test',
'humidity': 100, 'humidity': 100,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>, 'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 992.4, 'pressure': 992.4,

View File

@ -2,9 +2,10 @@
from __future__ import annotations from __future__ import annotations
from unittest.mock import patch from unittest.mock import MagicMock, patch
from pysmhi import SmhiForecastException from pysmhi import SmhiForecastException
import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.smhi.const import DOMAIN from homeassistant.components.smhi.const import DOMAIN
@ -16,8 +17,13 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant) -> None:
async def test_form(
hass: HomeAssistant,
mock_client: MagicMock,
) -> None:
"""Test we get the form and create an entry.""" """Test we get the form and create an entry."""
hass.config.latitude = 0.0 hass.config.latitude = 0.0
@ -29,17 +35,11 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {} assert result["errors"] == {}
with ( with patch(
patch( "homeassistant.components.smhi.async_setup_entry",
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast", return_value=True,
return_value={"test": "something", "test2": "something else"}, ) as mock_setup_entry:
), result = await hass.config_entries.flow.async_configure(
patch(
"homeassistant.components.smhi.async_setup_entry",
return_value=True,
) as mock_setup_entry,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
CONF_LOCATION: { CONF_LOCATION: {
@ -48,11 +48,11 @@ async def test_form(hass: HomeAssistant) -> None:
} }
}, },
) )
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "Home" assert result["title"] == "Home"
assert result2["data"] == { assert result["result"].unique_id == "0.0-0.0"
assert result["data"] == {
"location": { "location": {
"latitude": 0.0, "latitude": 0.0,
"longitude": 0.0, "longitude": 0.0,
@ -61,33 +61,22 @@ async def test_form(hass: HomeAssistant) -> None:
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
# Check title is "Weather" when not home coordinates # Check title is "Weather" when not home coordinates
result3 = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with ( result = await hass.config_entries.flow.async_configure(
patch( result["flow_id"],
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast", {
return_value={"test": "something", "test2": "something else"}, CONF_LOCATION: {
), CONF_LATITUDE: 1.0,
patch( CONF_LONGITUDE: 1.0,
"homeassistant.components.smhi.async_setup_entry", }
return_value=True, },
), )
):
result4 = await hass.config_entries.flow.async_configure(
result3["flow_id"],
{
CONF_LOCATION: {
CONF_LATITUDE: 1.0,
CONF_LONGITUDE: 1.0,
}
},
)
await hass.async_block_till_done()
assert result4["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result4["title"] == "Weather 1.0 1.0" assert result["title"] == "Weather 1.0 1.0"
assert result4["data"] == { assert result["data"] == {
"location": { "location": {
"latitude": 1.0, "latitude": 1.0,
"longitude": 1.0, "longitude": 1.0,
@ -95,55 +84,45 @@ async def test_form(hass: HomeAssistant) -> None:
} }
async def test_form_invalid_coordinates(hass: HomeAssistant) -> None: async def test_form_invalid_coordinates(
hass: HomeAssistant,
mock_client: MagicMock,
) -> None:
"""Test we handle invalid coordinates.""" """Test we handle invalid coordinates."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( mock_client.async_get_daily_forecast.side_effect = SmhiForecastException
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast",
side_effect=SmhiForecastException,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LOCATION: {
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
}
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM result = await hass.config_entries.flow.async_configure(
assert result2["errors"] == {"base": "wrong_location"} result["flow_id"],
{
CONF_LOCATION: {
CONF_LATITUDE: 0.0,
CONF_LONGITUDE: 0.0,
}
},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "wrong_location"}
# Continue flow with new coordinates # Continue flow with new coordinates
with ( mock_client.async_get_daily_forecast.side_effect = None
patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast", result["flow_id"],
return_value={"test": "something", "test2": "something else"}, {
), CONF_LOCATION: {
patch( CONF_LATITUDE: 2.0,
"homeassistant.components.smhi.async_setup_entry", CONF_LONGITUDE: 2.0,
return_value=True, }
), },
): )
result3 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_LOCATION: {
CONF_LATITUDE: 2.0,
CONF_LONGITUDE: 2.0,
}
},
)
await hass.async_block_till_done()
assert result3["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result3["title"] == "Weather 2.0 2.0" assert result["title"] == "Weather 2.0 2.0"
assert result3["data"] == { assert result["data"] == {
"location": { "location": {
"latitude": 2.0, "latitude": 2.0,
"longitude": 2.0, "longitude": 2.0,
@ -151,7 +130,10 @@ async def test_form_invalid_coordinates(hass: HomeAssistant) -> None:
} }
async def test_form_unique_id_exist(hass: HomeAssistant) -> None: async def test_form_unique_id_exist(
hass: HomeAssistant,
mock_client: MagicMock,
) -> None:
"""Test we handle unique id already exist.""" """Test we handle unique id already exist."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -169,27 +151,23 @@ async def test_form_unique_id_exist(hass: HomeAssistant) -> None:
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
with patch( result = await hass.config_entries.flow.async_configure(
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast", result["flow_id"],
return_value={"test": "something", "test2": "something else"}, {
): CONF_LOCATION: {
result2 = await hass.config_entries.flow.async_configure( CONF_LATITUDE: 1.0,
result["flow_id"], CONF_LONGITUDE: 1.0,
{ }
CONF_LOCATION: { },
CONF_LATITUDE: 1.0, )
CONF_LONGITUDE: 1.0,
}
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_reconfigure_flow( async def test_reconfigure_flow(
hass: HomeAssistant, hass: HomeAssistant,
mock_client: MagicMock,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry, device_registry: dr.DeviceRegistry,
) -> None: ) -> None:
@ -217,44 +195,32 @@ async def test_reconfigure_flow(
result = await entry.start_reconfigure_flow(hass) result = await entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
with patch( mock_client.async_get_daily_forecast.side_effect = SmhiForecastException
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast",
side_effect=SmhiForecastException, result = await hass.config_entries.flow.async_configure(
): result["flow_id"],
result = await hass.config_entries.flow.async_configure( {
result["flow_id"], CONF_LOCATION: {
{ CONF_LATITUDE: 0.0,
CONF_LOCATION: { CONF_LONGITUDE: 0.0,
CONF_LATITUDE: 0.0, }
CONF_LONGITUDE: 0.0, },
} )
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "wrong_location"} assert result["errors"] == {"base": "wrong_location"}
with ( mock_client.async_get_daily_forecast.side_effect = None
patch(
"homeassistant.components.smhi.config_flow.SMHIPointForecast.async_get_daily_forecast", result = await hass.config_entries.flow.async_configure(
return_value={"test": "something", "test2": "something else"}, result["flow_id"],
), {
patch( CONF_LOCATION: {
"homeassistant.components.smhi.async_setup_entry", CONF_LATITUDE: 58.2898,
return_value=True, CONF_LONGITUDE: 14.6304,
) as mock_setup_entry, }
): },
result = await hass.config_entries.flow.async_configure( )
result["flow_id"],
{
CONF_LOCATION: {
CONF_LATITUDE: 58.2898,
CONF_LONGITUDE: 14.6304,
}
},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful" assert result["reason"] == "reconfigure_successful"
@ -273,4 +239,3 @@ async def test_reconfigure_flow(
device = device_registry.async_get(device.id) device = device_registry.async_get(device.id)
assert device assert device
assert device.identifiers == {(DOMAIN, "58.2898, 14.6304")} assert device.identifiers == {(DOMAIN, "58.2898, 14.6304")}
assert len(mock_setup_entry.mock_calls) == 1

View File

@ -1,71 +1,42 @@
"""Test SMHI component setup process.""" """Test SMHI component setup process."""
from pysmhi.const import API_POINT_FORECAST from pysmhi import SMHIPointForecast
from homeassistant.components.smhi.const import DOMAIN from homeassistant.components.smhi.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import ENTITY_ID, TEST_CONFIG, TEST_CONFIG_MIGRATE from . import ENTITY_ID, TEST_CONFIG, TEST_CONFIG_MIGRATE
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker
async def test_setup_entry( async def test_load_and_unload_config_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str hass: HomeAssistant, load_int: MockConfigEntry
) -> None:
"""Test setup entry."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, title="test", data=TEST_CONFIG, version=3)
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
async def test_remove_entry(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str
) -> None: ) -> None:
"""Test remove entry.""" """Test remove entry."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert load_int.state is ConfigEntryState.LOADED
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert state assert state
await hass.config_entries.async_remove(entry.entry_id) await hass.config_entries.async_unload(load_int.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert load_int.state is ConfigEntryState.NOT_LOADED
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert not state assert state.state == STATE_UNAVAILABLE
async def test_migrate_entry( async def test_migrate_entry(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker, mock_client: SMHIPointForecast,
api_response: str,
) -> None: ) -> None:
"""Test migrate entry data.""" """Test migrate entry data."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG_MIGRATE["longitude"], TEST_CONFIG_MIGRATE["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE) entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE)
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert entry.version == 1 assert entry.version == 1
@ -94,13 +65,9 @@ async def test_migrate_entry(
async def test_migrate_from_future_version( async def test_migrate_from_future_version(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, api_response: str hass: HomeAssistant, mock_client: SMHIPointForecast
) -> None: ) -> None:
"""Test migrate entry not possible from future version.""" """Test migrate entry not possible from future version."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG_MIGRATE["longitude"], TEST_CONFIG_MIGRATE["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE, version=4) entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE, version=4)
entry.add_to_hass(hass) entry.add_to_hass(hass)
assert entry.version == 4 assert entry.version == 4

View File

@ -1,16 +1,19 @@
"""Test for the smhi weather entity.""" """Test for the smhi weather entity."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unittest.mock import patch from unittest.mock import MagicMock
from freezegun import freeze_time from freezegun import freeze_time
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
from pysmhi import SMHIForecast, SmhiForecastException from pysmhi import SMHIForecast, SmhiForecastException, SMHIPointForecast
from pysmhi.const import API_POINT_FORECAST
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from homeassistant.components.smhi.weather import CONDITION_CLASSES from homeassistant.components.smhi.const import DOMAIN
from homeassistant.components.smhi.weather import (
ATTR_SMHI_THUNDER_PROBABILITY,
CONDITION_CLASSES,
)
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_FORECAST_CONDITION, ATTR_FORECAST_CONDITION,
@ -23,6 +26,7 @@ from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN, STATE_UNKNOWN,
Platform,
UnitOfSpeed, UnitOfSpeed,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -32,31 +36,20 @@ from homeassistant.util import dt as dt_util
from . import ENTITY_ID, TEST_CONFIG from . import ENTITY_ID, TEST_CONFIG
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed
from tests.test_util.aiohttp import AiohttpClientMocker
from tests.typing import WebSocketGenerator from tests.typing import WebSocketGenerator
@pytest.mark.parametrize(
"load_platforms",
[[Platform.WEATHER]],
)
async def test_setup_hass( async def test_setup_hass(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test for successfully setting up the smhi integration.""" """Test for successfully setting up the smhi integration."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
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(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert state assert state
@ -64,27 +57,30 @@ async def test_setup_hass(
assert state.attributes == snapshot assert state.attributes == snapshot
@pytest.mark.parametrize(
"to_load",
[1],
)
@freeze_time(datetime(2023, 8, 7, 1, tzinfo=dt_util.UTC)) @freeze_time(datetime(2023, 8, 7, 1, tzinfo=dt_util.UTC))
async def test_clear_night( async def test_clear_night(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, mock_client: SMHIPointForecast,
api_response_night: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test for successfully setting up the smhi integration.""" """Test for successfully setting up the smhi integration."""
hass.config.latitude = "59.32624" hass.config.latitude = "59.32624"
hass.config.longitude = "17.84197" hass.config.longitude = "17.84197"
uri = API_POINT_FORECAST.format( config_entry = MockConfigEntry(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"] domain=DOMAIN,
data=TEST_CONFIG,
entry_id="01JMZDH8N5PFHGJNYKKYCSCWER",
unique_id="59.32624-17.84197",
version=3,
title="Test",
) )
aioclient_mock.get(uri, text=api_response_night) config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
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
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -104,39 +100,43 @@ async def test_clear_night(
async def test_properties_no_data( async def test_properties_no_data(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str, mock_client: MagicMock,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test properties when no API data available.""" """Test properties when no API data available."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3) mock_client.async_get_daily_forecast.side_effect = SmhiForecastException("boom")
entry.add_to_hass(hass) freezer.tick(timedelta(minutes=35))
async_fire_time_changed(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
with patch(
"homeassistant.components.smhi.coordinator.SMHIPointForecast.async_get_daily_forecast",
side_effect=SmhiForecastException("boom"),
):
freezer.tick(timedelta(minutes=35))
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
assert state assert state
assert state.name == "test" assert state.name == "Test"
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)" assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)"
mock_client.async_get_daily_forecast.side_effect = None
mock_client.async_get_daily_forecast.return_value = None
freezer.tick(timedelta(minutes=35))
async_fire_time_changed(hass)
await hass.async_block_till_done()
async def test_properties_unknown_symbol(hass: HomeAssistant) -> None: state = hass.states.get(ENTITY_ID)
assert state
assert state.name == "Test"
assert state.state == "fog"
assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes
assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)"
async def test_properties_unknown_symbol(
hass: HomeAssistant,
mock_client: MagicMock,
) -> None:
"""Test behaviour when unknown symbol from API.""" """Test behaviour when unknown symbol from API."""
data = SMHIForecast( data = SMHIForecast(
frozen_precipitation=0, frozen_precipitation=0,
@ -213,21 +213,13 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
testdata = [data, data2, data3] testdata = [data, data2, data3]
mock_client.async_get_daily_forecast.return_value = testdata
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3) entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass) entry.add_to_hass(hass)
with ( await hass.config_entries.async_setup(entry.entry_id)
patch( await hass.async_block_till_done()
"homeassistant.components.smhi.coordinator.SMHIPointForecast.async_get_daily_forecast",
return_value=testdata,
),
patch(
"homeassistant.components.smhi.coordinator.SMHIPointForecast.async_get_hourly_forecast",
return_value=None,
),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID) state = hass.states.get(ENTITY_ID)
@ -251,45 +243,33 @@ async def test_properties_unknown_symbol(hass: HomeAssistant) -> None:
async def test_refresh_weather_forecast_retry( async def test_refresh_weather_forecast_retry(
hass: HomeAssistant, hass: HomeAssistant,
error: Exception, error: Exception,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str, mock_client: MagicMock,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
) -> None: ) -> None:
"""Test the refresh weather forecast function.""" """Test the refresh weather forecast function."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3) mock_client.async_get_daily_forecast.side_effect = error
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) freezer.tick(timedelta(minutes=35))
async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
with patch( state = hass.states.get(ENTITY_ID)
"homeassistant.components.smhi.coordinator.SMHIPointForecast.async_get_daily_forecast",
side_effect=error,
) as mock_get_forecast:
freezer.tick(timedelta(minutes=35))
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get(ENTITY_ID) assert state
assert state.name == "Test"
assert state.state == STATE_UNAVAILABLE
assert mock_client.async_get_daily_forecast.call_count == 2
assert state freezer.tick(timedelta(minutes=35))
assert state.name == "test" async_fire_time_changed(hass)
assert state.state == STATE_UNAVAILABLE await hass.async_block_till_done()
assert mock_get_forecast.call_count == 1
freezer.tick(timedelta(minutes=35)) state = hass.states.get(ENTITY_ID)
async_fire_time_changed(hass) assert state
await hass.async_block_till_done() assert state.state == STATE_UNAVAILABLE
assert mock_client.async_get_daily_forecast.call_count == 3
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNAVAILABLE
assert mock_get_forecast.call_count == 2
def test_condition_class() -> None: def test_condition_class() -> None:
@ -361,25 +341,13 @@ def test_condition_class() -> None:
async def test_custom_speed_unit( async def test_custom_speed_unit(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str,
) -> None: ) -> None:
"""Test Wind Gust speed with custom unit.""" """Test Wind Gust speed with custom unit."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
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) state = hass.states.get(ENTITY_ID)
assert state assert state
assert state.name == "test" assert state.name == "Test"
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 22.32 assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 22.32
entity_registry.async_update_entity_options( entity_registry.async_update_entity_options(
@ -394,25 +362,17 @@ async def test_custom_speed_unit(
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.2 assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.2
@pytest.mark.parametrize(
"load_platforms",
[[Platform.WEATHER]],
)
async def test_forecast_services( async def test_forecast_services(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
await client.send_json_auto_id( await client.send_json_auto_id(
@ -458,25 +418,21 @@ async def test_forecast_services(
assert forecast1[6] == snapshot assert forecast1[6] == snapshot
@pytest.mark.parametrize(
"load_platforms",
[[Platform.WEATHER]],
)
@pytest.mark.parametrize(
"to_load",
[2],
)
async def test_forecast_services_lack_of_data( async def test_forecast_services_lack_of_data(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response_lack_data: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test forecast lacking data.""" """Test forecast lacking data."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response_lack_data)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
await client.send_json_auto_id( await client.send_json_auto_id(
@ -500,31 +456,18 @@ async def test_forecast_services_lack_of_data(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service"), "load_platforms",
[SERVICE_GET_FORECASTS], [[Platform.WEATHER]],
) )
async def test_forecast_service( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker, load_int: MockConfigEntry,
api_response: str,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
service: str,
) -> None: ) -> None:
"""Test forecast service.""" """Test forecast service."""
uri = API_POINT_FORECAST.format(
TEST_CONFIG["location"]["longitude"], TEST_CONFIG["location"]["latitude"]
)
aioclient_mock.get(uri, text=api_response)
entry = MockConfigEntry(domain="smhi", title="test", data=TEST_CONFIG, version=3)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
response = await hass.services.async_call( response = await hass.services.async_call(
WEATHER_DOMAIN, WEATHER_DOMAIN,
service, SERVICE_GET_FORECASTS,
{"entity_id": ENTITY_ID, "type": "daily"}, {"entity_id": ENTITY_ID, "type": "daily"},
blocking=True, blocking=True,
return_response=True, return_response=True,