Add snapshot tests for OpenWeatherMap sensors (#139657)

* Add snapshot tests for sensors

* Code cleanup

* Patch during async_setup only

* Use snapshot_platform, split platforms for snapshot tests

* Make mock_config_entry and mode fixtures

* Update snapshots with  latest device and state class changes

* Move setup_platform to __init__.py and patch HA object instead of library

* Remove if statements in tests

* Add client mock fixture to patch get_weather instead of internal call

* Code cleanup

* Test explicit list of modes

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
wittypluck 2025-05-09 13:06:38 +02:00 committed by GitHub
parent 031b25cd1e
commit 6350ed3415
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 2124 additions and 218 deletions

View File

@ -1 +1,24 @@
"""Tests for the OpenWeatherMap integration.""" """Shared utilities for OpenWeatherMap tests."""
from unittest.mock import patch
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_platform(
hass: HomeAssistant,
config_entry: MockConfigEntry,
platforms: list[Platform],
):
"""Set up the OpenWeatherMap platform."""
config_entry.add_to_hass(hass)
with (
patch("homeassistant.components.openweathermap.PLATFORMS", platforms),
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED

View File

@ -0,0 +1,146 @@
"""Configure tests for the OpenWeatherMap integration."""
from collections.abc import Generator
from datetime import UTC, datetime
from unittest.mock import AsyncMock
from pyopenweathermap import (
CurrentWeather,
DailyTemperature,
DailyWeatherForecast,
MinutelyWeatherForecast,
WeatherCondition,
WeatherReport,
)
from pyopenweathermap.client.owm_abstract_client import OWMClient
import pytest
from homeassistant.components.openweathermap.const import DEFAULT_LANGUAGE, DOMAIN
from homeassistant.const import (
CONF_API_KEY,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_NAME,
)
from tests.common import MockConfigEntry, patch
API_KEY = "test_api_key"
LATITUDE = 12.34
LONGITUDE = 56.78
NAME = "openweathermap"
@pytest.fixture
def mode(request: pytest.FixtureRequest) -> str:
"""Return mode passed in parameter."""
return request.param
@pytest.fixture
def mock_config_entry(mode: str) -> MockConfigEntry:
"""Fixture for creating a mock OpenWeatherMap config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
CONF_API_KEY: API_KEY,
CONF_LATITUDE: LATITUDE,
CONF_LONGITUDE: LONGITUDE,
CONF_NAME: NAME,
},
options={
CONF_MODE: mode,
CONF_LANGUAGE: DEFAULT_LANGUAGE,
},
entry_id="test",
version=5,
unique_id=f"{LATITUDE}-{LONGITUDE}",
)
@pytest.fixture
def owm_client_mock() -> Generator[AsyncMock]:
"""Mock OWMClient."""
client = AsyncMock(spec=OWMClient, autospec=True)
current_weather = CurrentWeather(
date_time=datetime.fromtimestamp(1714063536, tz=UTC),
temperature=6.84,
feels_like=2.07,
pressure=1000,
humidity=82,
dew_point=3.99,
uv_index=0.13,
cloud_coverage=75,
visibility=10000,
wind_speed=9.83,
wind_bearing=199,
wind_gust=None,
rain={"1h": 1.21},
snow=None,
condition=WeatherCondition(
id=803,
main="Clouds",
description="broken clouds",
icon="04d",
),
)
daily_weather_forecast = DailyWeatherForecast(
date_time=datetime.fromtimestamp(1714063536, tz=UTC),
summary="There will be clear sky until morning, then partly cloudy",
temperature=DailyTemperature(
day=18.76,
min=8.11,
max=21.26,
night=13.06,
evening=20.51,
morning=8.47,
),
feels_like=DailyTemperature(
day=18.76,
min=8.11,
max=21.26,
night=13.06,
evening=20.51,
morning=8.47,
),
pressure=1015,
humidity=62,
dew_point=11.34,
wind_speed=8.14,
wind_bearing=168,
wind_gust=11.81,
condition=WeatherCondition(
id=803,
main="Clouds",
description="broken clouds",
icon="04d",
),
cloud_coverage=84,
precipitation_probability=0,
uv_index=4.06,
rain=0,
snow=0,
)
minutely_weather_forecast = [
MinutelyWeatherForecast(date_time=1728672360, precipitation=0),
MinutelyWeatherForecast(date_time=1728672420, precipitation=1.23),
MinutelyWeatherForecast(date_time=1728672480, precipitation=4.5),
MinutelyWeatherForecast(date_time=1728672540, precipitation=0),
]
client.get_weather.return_value = WeatherReport(
current_weather, minutely_weather_forecast, [], [daily_weather_forecast]
)
client.validate_key.return_value = True
with (
patch(
"homeassistant.components.openweathermap.create_owm_client",
return_value=client,
),
patch(
"homeassistant.components.openweathermap.utils.create_owm_client",
return_value=client,
),
):
yield client

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
# serializer version: 1 # serializer version: 1
# name: test_get_minute_forecast[mock_service_response] # name: test_get_minute_forecast[v3.0][mock_service_response]
dict({ dict({
'weather.openweathermap': dict({ 'weather.openweathermap': dict({
'forecast': list([ 'forecast': list([
@ -23,3 +23,188 @@
}), }),
}) })
# --- # ---
# name: test_weather_states[current][weather.openweathermap-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'weather',
'entity_category': None,
'entity_id': 'weather.openweathermap',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'openweathermap',
'platform': 'openweathermap',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '12.34-56.78',
'unit_of_measurement': None,
})
# ---
# name: test_weather_states[current][weather.openweathermap-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'apparent_temperature': 2.1,
'attribution': 'Data provided by OpenWeatherMap',
'cloud_coverage': 75,
'dew_point': 4.0,
'friendly_name': 'openweathermap',
'humidity': 82,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 1000.0,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'temperature': 6.8,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_bearing': 199,
'wind_speed': 35.39,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}),
'context': <ANY>,
'entity_id': 'weather.openweathermap',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cloudy',
})
# ---
# name: test_weather_states[forecast][weather.openweathermap-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'weather',
'entity_category': None,
'entity_id': 'weather.openweathermap',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'openweathermap',
'platform': 'openweathermap',
'previous_unique_id': None,
'supported_features': <WeatherEntityFeature: 2>,
'translation_key': None,
'unique_id': '12.34-56.78',
'unit_of_measurement': None,
})
# ---
# name: test_weather_states[forecast][weather.openweathermap-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'apparent_temperature': 2.1,
'attribution': 'Data provided by OpenWeatherMap',
'cloud_coverage': 75,
'dew_point': 4.0,
'friendly_name': 'openweathermap',
'humidity': 82,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 1000.0,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'supported_features': <WeatherEntityFeature: 2>,
'temperature': 6.8,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_bearing': 199,
'wind_speed': 35.39,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}),
'context': <ANY>,
'entity_id': 'weather.openweathermap',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cloudy',
})
# ---
# name: test_weather_states[v3.0][weather.openweathermap-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'weather',
'entity_category': None,
'entity_id': 'weather.openweathermap',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'openweathermap',
'platform': 'openweathermap',
'previous_unique_id': None,
'supported_features': <WeatherEntityFeature: 3>,
'translation_key': None,
'unique_id': '12.34-56.78',
'unit_of_measurement': None,
})
# ---
# name: test_weather_states[v3.0][weather.openweathermap-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'apparent_temperature': 2.1,
'attribution': 'Data provided by OpenWeatherMap',
'cloud_coverage': 75,
'dew_point': 4.0,
'friendly_name': 'openweathermap',
'humidity': 82,
'precipitation_unit': <UnitOfPrecipitationDepth.MILLIMETERS: 'mm'>,
'pressure': 1000.0,
'pressure_unit': <UnitOfPressure.HPA: 'hPa'>,
'supported_features': <WeatherEntityFeature: 3>,
'temperature': 6.8,
'temperature_unit': <UnitOfTemperature.CELSIUS: '°C'>,
'visibility_unit': <UnitOfLength.KILOMETERS: 'km'>,
'wind_bearing': 199,
'wind_speed': 35.39,
'wind_speed_unit': <UnitOfSpeed.KILOMETERS_PER_HOUR: 'km/h'>,
}),
'context': <ANY>,
'entity_id': 'weather.openweathermap',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'cloudy',
})
# ---

View File

@ -1,17 +1,8 @@
"""Define tests for the OpenWeatherMap config flow.""" """Define tests for the OpenWeatherMap config flow."""
from datetime import UTC, datetime from unittest.mock import AsyncMock
from unittest.mock import AsyncMock, MagicMock, patch
from pyopenweathermap import ( from pyopenweathermap import RequestError
CurrentWeather,
DailyTemperature,
DailyWeatherForecast,
MinutelyWeatherForecast,
RequestError,
WeatherCondition,
WeatherReport,
)
import pytest import pytest
from homeassistant.components.openweathermap.const import ( from homeassistant.components.openweathermap.const import (
@ -32,13 +23,15 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from .conftest import LATITUDE, LONGITUDE
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
CONFIG = { CONFIG = {
CONF_NAME: "openweathermap", CONF_NAME: "openweathermap",
CONF_API_KEY: "foo", CONF_API_KEY: "foo",
CONF_LATITUDE: 50, CONF_LATITUDE: LATITUDE,
CONF_LONGITUDE: 40, CONF_LONGITUDE: LONGITUDE,
CONF_LANGUAGE: DEFAULT_LANGUAGE, CONF_LANGUAGE: DEFAULT_LANGUAGE,
CONF_MODE: OWM_MODE_V30, CONF_MODE: OWM_MODE_V30,
} }
@ -46,118 +39,11 @@ CONFIG = {
VALID_YAML_CONFIG = {CONF_API_KEY: "foo"} VALID_YAML_CONFIG = {CONF_API_KEY: "foo"}
def _create_static_weather_report() -> WeatherReport:
"""Create a static WeatherReport."""
current_weather = CurrentWeather(
date_time=datetime.fromtimestamp(1714063536, tz=UTC),
temperature=6.84,
feels_like=2.07,
pressure=1000,
humidity=82,
dew_point=3.99,
uv_index=0.13,
cloud_coverage=75,
visibility=10000,
wind_speed=9.83,
wind_bearing=199,
wind_gust=None,
rain={"1h": 1.21},
snow=None,
condition=WeatherCondition(
id=803,
main="Clouds",
description="broken clouds",
icon="04d",
),
)
daily_weather_forecast = DailyWeatherForecast(
date_time=datetime.fromtimestamp(1714063536, tz=UTC),
summary="There will be clear sky until morning, then partly cloudy",
temperature=DailyTemperature(
day=18.76,
min=8.11,
max=21.26,
night=13.06,
evening=20.51,
morning=8.47,
),
feels_like=DailyTemperature(
day=18.76,
min=8.11,
max=21.26,
night=13.06,
evening=20.51,
morning=8.47,
),
pressure=1015,
humidity=62,
dew_point=11.34,
wind_speed=8.14,
wind_bearing=168,
wind_gust=11.81,
condition=WeatherCondition(
id=803,
main="Clouds",
description="broken clouds",
icon="04d",
),
cloud_coverage=84,
precipitation_probability=0,
uv_index=4.06,
rain=0,
snow=0,
)
minutely_weather_forecast = [
MinutelyWeatherForecast(date_time=1728672360, precipitation=0),
MinutelyWeatherForecast(date_time=1728672420, precipitation=1.23),
MinutelyWeatherForecast(date_time=1728672480, precipitation=4.5),
MinutelyWeatherForecast(date_time=1728672540, precipitation=0),
]
return WeatherReport(
current_weather, minutely_weather_forecast, [], [daily_weather_forecast]
)
def _create_mocked_owm_factory(is_valid: bool):
"""Create a mocked OWM client."""
weather_report = _create_static_weather_report()
mocked_owm_client = MagicMock()
mocked_owm_client.validate_key = AsyncMock(return_value=is_valid)
mocked_owm_client.get_weather = AsyncMock(return_value=weather_report)
return mocked_owm_client
@pytest.fixture(name="owm_client_mock")
def mock_owm_client():
"""Mock config_flow OWMClient."""
with patch(
"homeassistant.components.openweathermap.create_owm_client",
) as mock:
yield mock
@pytest.fixture(name="config_flow_owm_client_mock")
def mock_config_flow_owm_client():
"""Mock config_flow OWMClient."""
with patch(
"homeassistant.components.openweathermap.utils.create_owm_client",
) as mock:
yield mock
async def test_successful_config_flow( async def test_successful_config_flow(
hass: HomeAssistant, hass: HomeAssistant,
owm_client_mock, owm_client_mock: AsyncMock,
config_flow_owm_client_mock,
) -> None: ) -> None:
"""Test that the form is served with valid input.""" """Test that the form is served with valid input."""
mock = _create_mocked_owm_factory(True)
owm_client_mock.return_value = mock
config_flow_owm_client_mock.return_value = mock
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@ -187,39 +73,32 @@ async def test_successful_config_flow(
assert result["data"][CONF_API_KEY] == CONFIG[CONF_API_KEY] assert result["data"][CONF_API_KEY] == CONFIG[CONF_API_KEY]
@pytest.mark.parametrize("mode", [OWM_MODE_V30], indirect=True)
async def test_abort_config_flow( async def test_abort_config_flow(
hass: HomeAssistant, hass: HomeAssistant,
owm_client_mock, owm_client_mock: AsyncMock,
config_flow_owm_client_mock, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test that the form is served with same data.""" """Test that the form is served with same data."""
mock = _create_mocked_owm_factory(True) mock_config_entry.add_to_hass(hass)
owm_client_mock.return_value = mock
config_flow_owm_client_mock.return_value = mock
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}
) )
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init( assert result["type"] is FlowResultType.FORM
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG assert result["step_id"] == "user"
) assert result["errors"] == {}
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_configure(result["flow_id"], CONFIG)
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
async def test_config_flow_options_change( async def test_config_flow_options_change(
hass: HomeAssistant, hass: HomeAssistant,
owm_client_mock, owm_client_mock: AsyncMock,
config_flow_owm_client_mock,
) -> None: ) -> None:
"""Test that the options form.""" """Test that the options form."""
mock = _create_mocked_owm_factory(True)
owm_client_mock.return_value = mock
config_flow_owm_client_mock.return_value = mock
config_entry = MockConfigEntry( config_entry = MockConfigEntry(
domain=DOMAIN, unique_id="openweathermap_unique_id", data=CONFIG domain=DOMAIN, unique_id="openweathermap_unique_id", data=CONFIG
) )
@ -274,10 +153,10 @@ async def test_config_flow_options_change(
async def test_form_invalid_api_key( async def test_form_invalid_api_key(
hass: HomeAssistant, hass: HomeAssistant,
config_flow_owm_client_mock, owm_client_mock: AsyncMock,
) -> None: ) -> None:
"""Test that the form is served with no input.""" """Test that the form is served with no input."""
config_flow_owm_client_mock.return_value = _create_mocked_owm_factory(False) owm_client_mock.validate_key.return_value = False
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
) )
@ -285,7 +164,7 @@ async def test_form_invalid_api_key(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_api_key"} assert result["errors"] == {"base": "invalid_api_key"}
config_flow_owm_client_mock.return_value = _create_mocked_owm_factory(True) owm_client_mock.validate_key.return_value = True
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=CONFIG result["flow_id"], user_input=CONFIG
) )
@ -295,11 +174,10 @@ async def test_form_invalid_api_key(
async def test_form_api_call_error( async def test_form_api_call_error(
hass: HomeAssistant, hass: HomeAssistant,
config_flow_owm_client_mock, owm_client_mock: AsyncMock,
) -> None: ) -> None:
"""Test setting up with api call error.""" """Test setting up with api call error."""
config_flow_owm_client_mock.return_value = _create_mocked_owm_factory(True) owm_client_mock.validate_key.side_effect = RequestError("oops")
config_flow_owm_client_mock.side_effect = RequestError("oops")
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=CONFIG DOMAIN, context={"source": SOURCE_USER}, data=CONFIG
) )
@ -307,7 +185,7 @@ async def test_form_api_call_error(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"} assert result["errors"] == {"base": "cannot_connect"}
config_flow_owm_client_mock.side_effect = None owm_client_mock.validate_key.side_effect = None
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=CONFIG result["flow_id"], user_input=CONFIG
) )

View File

@ -0,0 +1,49 @@
"""Tests for OpenWeatherMap sensors."""
from unittest.mock import MagicMock
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.openweathermap.const import (
OWM_MODE_FREE_CURRENT,
OWM_MODE_FREE_FORECAST,
OWM_MODE_V30,
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_platform
from tests.common import MockConfigEntry, snapshot_platform
@pytest.mark.parametrize("mode", [OWM_MODE_V30, OWM_MODE_FREE_CURRENT], indirect=True)
async def test_sensor_states(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
owm_client_mock: MagicMock,
mode: str,
) -> None:
"""Test sensor states are correctly collected from library with different modes and mocked function responses."""
await setup_platform(hass, mock_config_entry, [Platform.SENSOR])
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize("mode", [OWM_MODE_FREE_FORECAST], indirect=True)
async def test_mode_no_sensor(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
owm_client_mock: MagicMock,
mode: str,
) -> None:
"""Test modes that do not provide any sensor."""
await setup_platform(hass, mock_config_entry, [Platform.SENSOR])
assert len(entity_registry.entities) == 0

View File

@ -1,91 +1,40 @@
"""Test the OpenWeatherMap weather entity.""" """Test the OpenWeatherMap weather entity."""
from unittest.mock import MagicMock
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.openweathermap.const import ( from homeassistant.components.openweathermap.const import (
DEFAULT_LANGUAGE,
DOMAIN, DOMAIN,
OWM_MODE_FREE_CURRENT, OWM_MODE_FREE_CURRENT,
OWM_MODE_FREE_FORECAST,
OWM_MODE_V30, OWM_MODE_V30,
) )
from homeassistant.components.openweathermap.weather import SERVICE_GET_MINUTE_FORECAST from homeassistant.components.openweathermap.weather import SERVICE_GET_MINUTE_FORECAST
from homeassistant.config_entries import ConfigEntryState from homeassistant.const import Platform
from homeassistant.const import (
CONF_API_KEY,
CONF_LANGUAGE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_MODE,
CONF_NAME,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from .test_config_flow import _create_static_weather_report from . import setup_platform
from tests.common import AsyncMock, MockConfigEntry, patch from tests.common import MockConfigEntry, snapshot_platform
ENTITY_ID = "weather.openweathermap" ENTITY_ID = "weather.openweathermap"
API_KEY = "test_api_key"
LATITUDE = 12.34
LONGITUDE = 56.78
NAME = "openweathermap"
# Define test data for mocked weather report
static_weather_report = _create_static_weather_report()
def mock_config_entry(mode: str) -> MockConfigEntry: @pytest.mark.parametrize("mode", [OWM_MODE_V30], indirect=True)
"""Create a mock OpenWeatherMap config entry."""
return MockConfigEntry(
domain=DOMAIN,
data={
CONF_API_KEY: API_KEY,
CONF_LATITUDE: LATITUDE,
CONF_LONGITUDE: LONGITUDE,
CONF_NAME: NAME,
},
options={CONF_MODE: mode, CONF_LANGUAGE: DEFAULT_LANGUAGE},
version=5,
)
@pytest.fixture
def mock_config_entry_free_current() -> MockConfigEntry:
"""Create a mock OpenWeatherMap FREE_CURRENT config entry."""
return mock_config_entry(OWM_MODE_FREE_CURRENT)
@pytest.fixture
def mock_config_entry_v30() -> MockConfigEntry:
"""Create a mock OpenWeatherMap v3.0 config entry."""
return mock_config_entry(OWM_MODE_V30)
async def setup_mock_config_entry(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
):
"""Set up the MockConfigEntry and assert it is loaded correctly."""
mock_config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get(ENTITY_ID)
assert mock_config_entry.state is ConfigEntryState.LOADED
@patch(
"pyopenweathermap.client.onecall_client.OWMOneCallClient.get_weather",
AsyncMock(return_value=static_weather_report),
)
async def test_get_minute_forecast( async def test_get_minute_forecast(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry_v30: MockConfigEntry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
owm_client_mock: MagicMock,
mode: str,
) -> None: ) -> None:
"""Test the get_minute_forecast Service call.""" """Test the get_minute_forecast Service call."""
await setup_mock_config_entry(hass, mock_config_entry_v30)
await setup_platform(hass, mock_config_entry, [Platform.WEATHER])
result = await hass.services.async_call( result = await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_GET_MINUTE_FORECAST, SERVICE_GET_MINUTE_FORECAST,
@ -96,18 +45,19 @@ async def test_get_minute_forecast(
assert result == snapshot(name="mock_service_response") assert result == snapshot(name="mock_service_response")
@patch( @pytest.mark.parametrize(
"pyopenweathermap.client.free_client.OWMFreeClient.get_weather", "mode", [OWM_MODE_FREE_CURRENT, OWM_MODE_FREE_FORECAST], indirect=True
AsyncMock(return_value=static_weather_report),
) )
async def test_mode_fail( async def test_get_minute_forecast_unavailable(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry_free_current: MockConfigEntry, snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
owm_client_mock: MagicMock,
mode: str,
) -> None: ) -> None:
"""Test that Minute forecasting fails when mode is not v3.0.""" """Test that Minute forecasting fails when mode is not v3.0."""
await setup_mock_config_entry(hass, mock_config_entry_free_current)
# Expect a ServiceValidationError when mode is not OWM_MODE_V30 await setup_platform(hass, mock_config_entry, [Platform.WEATHER])
with pytest.raises( with pytest.raises(
ServiceValidationError, ServiceValidationError,
match="Minute forecast is available only when OpenWeatherMap mode is set to v3.0", match="Minute forecast is available only when OpenWeatherMap mode is set to v3.0",
@ -119,3 +69,19 @@ async def test_mode_fail(
blocking=True, blocking=True,
return_response=True, return_response=True,
) )
@pytest.mark.parametrize(
"mode", [OWM_MODE_V30, OWM_MODE_FREE_CURRENT, OWM_MODE_FREE_FORECAST], indirect=True
)
async def test_weather_states(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
owm_client_mock: MagicMock,
) -> None:
"""Test weather states are correctly collected from library with different modes and mocked function responses."""
await setup_platform(hass, mock_config_entry, [Platform.WEATHER])
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)