From 09be56964d520f6e89b735c613b8b75c58f290cd Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 6 May 2024 19:41:48 +0200 Subject: [PATCH] AccuWeather tests refactoring (#116923) * Add mock_accuweather_client * Improve tests * Fix exceptions * Remove unneeded update_listener() * Fix arguments for fixtures --------- Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com> --- .../components/accuweather/__init__.py | 7 - tests/components/accuweather/__init__.py | 37 +-- tests/components/accuweather/conftest.py | 36 +++ .../accuweather/test_config_flow.py | 145 ++++++------ .../accuweather/test_diagnostics.py | 3 + tests/components/accuweather/test_init.py | 78 +++---- tests/components/accuweather/test_sensor.py | 212 +++++++----------- .../accuweather/test_system_health.py | 35 ++- tests/components/accuweather/test_weather.py | 139 +++++------- 9 files changed, 305 insertions(+), 387 deletions(-) create mode 100644 tests/components/accuweather/conftest.py diff --git a/homeassistant/components/accuweather/__init__.py b/homeassistant/components/accuweather/__init__.py index d52ef5e0ec6..869664f0255 100644 --- a/homeassistant/components/accuweather/__init__.py +++ b/homeassistant/components/accuweather/__init__.py @@ -64,8 +64,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator_observation.async_config_entry_first_refresh() await coordinator_daily_forecast.async_config_entry_first_refresh() - entry.async_on_unload(entry.add_update_listener(update_listener)) - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AccuWeatherData( coordinator_observation=coordinator_observation, coordinator_daily_forecast=coordinator_daily_forecast, @@ -92,8 +90,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Update listener.""" - await hass.config_entries.async_reload(entry.entry_id) diff --git a/tests/components/accuweather/__init__.py b/tests/components/accuweather/__init__.py index a08b894ebb4..21cdb2ac558 100644 --- a/tests/components/accuweather/__init__.py +++ b/tests/components/accuweather/__init__.py @@ -1,17 +1,11 @@ """Tests for AccuWeather.""" -from unittest.mock import PropertyMock, patch - from homeassistant.components.accuweather.const import DOMAIN -from tests.common import ( - MockConfigEntry, - load_json_array_fixture, - load_json_object_fixture, -) +from tests.common import MockConfigEntry -async def init_integration(hass, unsupported_icon=False) -> MockConfigEntry: +async def init_integration(hass) -> MockConfigEntry: """Set up the AccuWeather integration in Home Assistant.""" entry = MockConfigEntry( domain=DOMAIN, @@ -25,29 +19,8 @@ async def init_integration(hass, unsupported_icon=False) -> MockConfigEntry: }, ) - current = load_json_object_fixture("accuweather/current_conditions_data.json") - forecast = load_json_array_fixture("accuweather/forecast_data.json") - - if unsupported_icon: - current["WeatherIcon"] = 999 - - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() return entry diff --git a/tests/components/accuweather/conftest.py b/tests/components/accuweather/conftest.py new file mode 100644 index 00000000000..959557606c6 --- /dev/null +++ b/tests/components/accuweather/conftest.py @@ -0,0 +1,36 @@ +"""Common fixtures for the AccuWeather tests.""" + +from collections.abc import Generator +from unittest.mock import AsyncMock, patch + +import pytest + +from homeassistant.components.accuweather.const import DOMAIN + +from tests.common import load_json_array_fixture, load_json_object_fixture + + +@pytest.fixture +def mock_accuweather_client() -> Generator[AsyncMock, None, None]: + """Mock a AccuWeather client.""" + current = load_json_object_fixture("current_conditions_data.json", DOMAIN) + forecast = load_json_array_fixture("forecast_data.json", DOMAIN) + location = load_json_object_fixture("location_data.json", DOMAIN) + + with ( + patch( + "homeassistant.components.accuweather.AccuWeather", autospec=True + ) as mock_client, + patch( + "homeassistant.components.accuweather.config_flow.AccuWeather", + new=mock_client, + ), + ): + client = mock_client.return_value + client.async_get_location.return_value = location + client.async_get_current_conditions.return_value = current + client.async_get_daily_forecast.return_value = forecast + client.location_key = "0123456" + client.requests_remaining = 10 + + yield client diff --git a/tests/components/accuweather/test_config_flow.py b/tests/components/accuweather/test_config_flow.py index 07b126e0856..abe1be61905 100644 --- a/tests/components/accuweather/test_config_flow.py +++ b/tests/components/accuweather/test_config_flow.py @@ -1,6 +1,6 @@ """Define tests for the AccuWeather config flow.""" -from unittest.mock import patch +from unittest.mock import AsyncMock from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError @@ -10,7 +10,7 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CON from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from tests.common import MockConfigEntry, load_json_object_fixture +from tests.common import MockConfigEntry VALID_CONFIG = { CONF_NAME: "abcd", @@ -48,95 +48,90 @@ async def test_api_key_too_short(hass: HomeAssistant) -> None: assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} -async def test_invalid_api_key(hass: HomeAssistant) -> None: +async def test_invalid_api_key( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test that errors are shown when API key is invalid.""" - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - side_effect=InvalidApiKeyError("Invalid API key"), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=VALID_CONFIG, - ) + mock_accuweather_client.async_get_location.side_effect = InvalidApiKeyError( + "Invalid API key" + ) - assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) + + assert result["errors"] == {CONF_API_KEY: "invalid_api_key"} -async def test_api_error(hass: HomeAssistant) -> None: +async def test_api_error( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test API error.""" - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - side_effect=ApiError("Invalid response from AccuWeather API"), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=VALID_CONFIG, - ) + mock_accuweather_client.async_get_location.side_effect = ApiError( + "Invalid response from AccuWeather API" + ) - assert result["errors"] == {"base": "cannot_connect"} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) + + assert result["errors"] == {"base": "cannot_connect"} -async def test_requests_exceeded_error(hass: HomeAssistant) -> None: +async def test_requests_exceeded_error( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test requests exceeded error.""" - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - side_effect=RequestsExceededError( - "The allowed number of requests has been exceeded" - ), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=VALID_CONFIG, - ) + mock_accuweather_client.async_get_location.side_effect = RequestsExceededError( + "The allowed number of requests has been exceeded" + ) - assert result["errors"] == {CONF_API_KEY: "requests_exceeded"} + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) + + assert result["errors"] == {CONF_API_KEY: "requests_exceeded"} -async def test_integration_already_exists(hass: HomeAssistant) -> None: +async def test_integration_already_exists( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test we only allow a single config flow.""" - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - return_value=load_json_object_fixture("accuweather/location_data.json"), - ): - MockConfigEntry( - domain=DOMAIN, - unique_id="123456", - data=VALID_CONFIG, - ).add_to_hass(hass) + MockConfigEntry( + domain=DOMAIN, + unique_id="123456", + data=VALID_CONFIG, + ).add_to_hass(hass) - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=VALID_CONFIG, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "single_instance_allowed" + assert result["type"] is FlowResultType.ABORT + assert result["reason"] == "single_instance_allowed" -async def test_create_entry(hass: HomeAssistant) -> None: +async def test_create_entry( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test that the user step works.""" - with ( - patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - return_value=load_json_object_fixture("accuweather/location_data.json"), - ), - patch( - "homeassistant.components.accuweather.async_setup_entry", return_value=True - ), - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_USER}, - data=VALID_CONFIG, - ) + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_USER}, + data=VALID_CONFIG, + ) - assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "abcd" - assert result["data"][CONF_NAME] == "abcd" - assert result["data"][CONF_LATITUDE] == 55.55 - assert result["data"][CONF_LONGITUDE] == 122.12 - assert result["data"][CONF_API_KEY] == "32-character-string-1234567890qw" + assert result["type"] is FlowResultType.CREATE_ENTRY + assert result["title"] == "abcd" + assert result["data"][CONF_NAME] == "abcd" + assert result["data"][CONF_LATITUDE] == 55.55 + assert result["data"][CONF_LONGITUDE] == 122.12 + assert result["data"][CONF_API_KEY] == "32-character-string-1234567890qw" diff --git a/tests/components/accuweather/test_diagnostics.py b/tests/components/accuweather/test_diagnostics.py index 593cde0f0a3..bc97ae1fe14 100644 --- a/tests/components/accuweather/test_diagnostics.py +++ b/tests/components/accuweather/test_diagnostics.py @@ -1,5 +1,7 @@ """Test AccuWeather diagnostics.""" +from unittest.mock import AsyncMock + from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant @@ -13,6 +15,7 @@ from tests.typing import ClientSessionGenerator async def test_entry_diagnostics( hass: HomeAssistant, hass_client: ClientSessionGenerator, + mock_accuweather_client: AsyncMock, snapshot: SnapshotAssertion, ) -> None: """Test config entry diagnostics.""" diff --git a/tests/components/accuweather/test_init.py b/tests/components/accuweather/test_init.py index 08ad4a66dec..340676905d6 100644 --- a/tests/components/accuweather/test_init.py +++ b/tests/components/accuweather/test_init.py @@ -1,8 +1,9 @@ """Test init of AccuWeather integration.""" -from unittest.mock import patch +from unittest.mock import AsyncMock from accuweather import ApiError +from freezegun.api import FrozenDateTimeFactory from homeassistant.components.accuweather.const import ( DOMAIN, @@ -14,19 +15,15 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er -from homeassistant.util.dt import utcnow from . import init_integration -from tests.common import ( - MockConfigEntry, - async_fire_time_changed, - load_json_array_fixture, - load_json_object_fixture, -) +from tests.common import MockConfigEntry, async_fire_time_changed -async def test_async_setup_entry(hass: HomeAssistant) -> None: +async def test_async_setup_entry( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test a successful setup entry.""" await init_integration(hass) @@ -36,7 +33,9 @@ async def test_async_setup_entry(hass: HomeAssistant) -> None: assert state.state == "sunny" -async def test_config_not_ready(hass: HomeAssistant) -> None: +async def test_config_not_ready( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test for setup failure if connection to AccuWeather is missing.""" entry = MockConfigEntry( domain=DOMAIN, @@ -50,16 +49,18 @@ async def test_config_not_ready(hass: HomeAssistant) -> None: }, ) - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - side_effect=ApiError("API Error"), - ): - entry.add_to_hass(hass) - await hass.config_entries.async_setup(entry.entry_id) - assert entry.state is ConfigEntryState.SETUP_RETRY + mock_accuweather_client.async_get_current_conditions.side_effect = ApiError( + "API Error" + ) + + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state is ConfigEntryState.SETUP_RETRY -async def test_unload_entry(hass: HomeAssistant) -> None: +async def test_unload_entry( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test successful unload of entry.""" entry = await init_integration(hass) @@ -73,41 +74,36 @@ async def test_unload_entry(hass: HomeAssistant) -> None: assert not hass.data.get(DOMAIN) -async def test_update_interval(hass: HomeAssistant) -> None: +async def test_update_interval( + hass: HomeAssistant, + mock_accuweather_client: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: """Test correct update interval.""" entry = await init_integration(hass) assert entry.state is ConfigEntryState.LOADED - current = load_json_object_fixture("accuweather/current_conditions_data.json") - forecast = load_json_array_fixture("accuweather/forecast_data.json") + assert mock_accuweather_client.async_get_current_conditions.call_count == 1 + assert mock_accuweather_client.async_get_daily_forecast.call_count == 1 - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ) as mock_forecast, - ): - assert mock_current.call_count == 0 - assert mock_forecast.call_count == 0 + freezer.tick(UPDATE_INTERVAL_OBSERVATION) + async_fire_time_changed(hass) + await hass.async_block_till_done() - async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_OBSERVATION) - await hass.async_block_till_done() + assert mock_accuweather_client.async_get_current_conditions.call_count == 2 - assert mock_current.call_count == 1 + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST) + async_fire_time_changed(hass) + await hass.async_block_till_done() - async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST) - await hass.async_block_till_done() - - assert mock_forecast.call_count == 1 + assert mock_accuweather_client.async_get_daily_forecast.call_count == 2 async def test_remove_ozone_sensors( - hass: HomeAssistant, entity_registry: er.EntityRegistry + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_accuweather_client: AsyncMock, ) -> None: """Test remove ozone sensors from registry.""" entity_registry.async_get_or_create( diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py index 127e4d74cd8..e16f1e863da 100644 --- a/tests/components/accuweather/test_sensor.py +++ b/tests/components/accuweather/test_sensor.py @@ -1,14 +1,17 @@ """Test sensor of AccuWeather integration.""" -from datetime import timedelta -from unittest.mock import PropertyMock, patch +from unittest.mock import AsyncMock, patch from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError from aiohttp.client_exceptions import ClientConnectorError +from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion -from homeassistant.components.accuweather.const import UPDATE_INTERVAL_DAILY_FORECAST +from homeassistant.components.accuweather.const import ( + UPDATE_INTERVAL_DAILY_FORECAST, + UPDATE_INTERVAL_OBSERVATION, +) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, @@ -21,23 +24,18 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from homeassistant.util.dt import utcnow from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from . import init_integration -from tests.common import ( - async_fire_time_changed, - load_json_array_fixture, - load_json_object_fixture, - snapshot_platform, -) +from tests.common import async_fire_time_changed, snapshot_platform async def test_sensor( hass: HomeAssistant, entity_registry_enabled_by_default: None, entity_registry: er.EntityRegistry, + mock_accuweather_client: AsyncMock, snapshot: SnapshotAssertion, ) -> None: """Test states of the sensor.""" @@ -46,64 +44,59 @@ async def test_sensor( await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id) -async def test_availability(hass: HomeAssistant) -> None: +async def test_availability( + hass: HomeAssistant, + mock_accuweather_client: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: """Ensure that we mark the entities unavailable correctly when service is offline.""" + entity_id = "sensor.home_cloud_ceiling" await init_integration(hass) - state = hass.states.get("sensor.home_cloud_ceiling") + state = hass.states.get(entity_id) assert state assert state.state != STATE_UNAVAILABLE assert state.state == "3200.0" - future = utcnow() + timedelta(minutes=60) - with patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - side_effect=ConnectionError(), - ): - async_fire_time_changed(hass, future) - await hass.async_block_till_done() + mock_accuweather_client.async_get_current_conditions.side_effect = ConnectionError - state = hass.states.get("sensor.home_cloud_ceiling") - assert state - assert state.state == STATE_UNAVAILABLE + freezer.tick(UPDATE_INTERVAL_OBSERVATION) + async_fire_time_changed(hass) + await hass.async_block_till_done() - future = utcnow() + timedelta(minutes=120) - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=load_json_object_fixture( - "accuweather/current_conditions_data.json" - ), - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - async_fire_time_changed(hass, future) - await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNAVAILABLE - state = hass.states.get("sensor.home_cloud_ceiling") - assert state - assert state.state != STATE_UNAVAILABLE - assert state.state == "3200.0" + mock_accuweather_client.async_get_current_conditions.side_effect = None + + freezer.tick(UPDATE_INTERVAL_OBSERVATION) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "3200.0" @pytest.mark.parametrize( "exception", [ - ApiError, + ApiError("API Error"), ConnectionError, ClientConnectorError, - InvalidApiKeyError, - RequestsExceededError, + InvalidApiKeyError("Invalid API key"), + RequestsExceededError("Requests exceeded"), ], ) -async def test_availability_forecast(hass: HomeAssistant, exception: Exception) -> None: +async def test_availability_forecast( + hass: HomeAssistant, + exception: Exception, + mock_accuweather_client: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: """Ensure that we mark the entities unavailable correctly when service is offline.""" - current = load_json_object_fixture("accuweather/current_conditions_data.json") - forecast = load_json_array_fixture("accuweather/forecast_data.json") entity_id = "sensor.home_hours_of_sun_day_2" await init_integration(hass) @@ -113,45 +106,21 @@ async def test_availability_forecast(hass: HomeAssistant, exception: Exception) assert state.state != STATE_UNAVAILABLE assert state.state == "5.7" - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - side_effect=exception, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST) - await hass.async_block_till_done() + mock_accuweather_client.async_get_daily_forecast.side_effect = exception + + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST) + async_fire_time_changed(hass) + await hass.async_block_till_done() state = hass.states.get(entity_id) assert state assert state.state == STATE_UNAVAILABLE - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_DAILY_FORECAST * 2) - await hass.async_block_till_done() + mock_accuweather_client.async_get_daily_forecast.side_effect = None + + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST) + async_fire_time_changed(hass) + await hass.async_block_till_done() state = hass.states.get(entity_id) assert state @@ -159,35 +128,29 @@ async def test_availability_forecast(hass: HomeAssistant, exception: Exception) assert state.state == "5.7" -async def test_manual_update_entity(hass: HomeAssistant) -> None: +async def test_manual_update_entity( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test manual update entity via service homeassistant/update_entity.""" await init_integration(hass) await async_setup_component(hass, "homeassistant", {}) - current = load_json_object_fixture("accuweather/current_conditions_data.json") + assert mock_accuweather_client.async_get_current_conditions.call_count == 1 - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - await hass.services.async_call( - "homeassistant", - "update_entity", - {ATTR_ENTITY_ID: ["sensor.home_cloud_ceiling"]}, - blocking=True, - ) - assert mock_current.call_count == 1 + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.home_cloud_ceiling"]}, + blocking=True, + ) + + assert mock_accuweather_client.async_get_current_conditions.call_count == 2 -async def test_sensor_imperial_units(hass: HomeAssistant) -> None: +async def test_sensor_imperial_units( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test states of the sensor without forecast.""" hass.config.units = US_CUSTOMARY_SYSTEM await init_integration(hass) @@ -210,37 +173,30 @@ async def test_sensor_imperial_units(hass: HomeAssistant) -> None: ) -async def test_state_update(hass: HomeAssistant) -> None: +async def test_state_update( + hass: HomeAssistant, + mock_accuweather_client: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: """Ensure the sensor state changes after updating the data.""" + entity_id = "sensor.home_cloud_ceiling" + await init_integration(hass) - state = hass.states.get("sensor.home_cloud_ceiling") + state = hass.states.get(entity_id) assert state assert state.state != STATE_UNAVAILABLE assert state.state == "3200.0" - future = utcnow() + timedelta(minutes=60) + mock_accuweather_client.async_get_current_conditions.return_value["Ceiling"][ + "Metric" + ]["Value"] = 3300 - current_condition = load_json_object_fixture( - "accuweather/current_conditions_data.json" - ) - current_condition["Ceiling"]["Metric"]["Value"] = 3300 + freezer.tick(UPDATE_INTERVAL_OBSERVATION) + async_fire_time_changed(hass) + await hass.async_block_till_done() - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current_condition, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - async_fire_time_changed(hass, future) - await hass.async_block_till_done() - - state = hass.states.get("sensor.home_cloud_ceiling") - assert state - assert state.state != STATE_UNAVAILABLE - assert state.state == "3300" + state = hass.states.get(entity_id) + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "3300" diff --git a/tests/components/accuweather/test_system_health.py b/tests/components/accuweather/test_system_health.py index 562c572c830..3f00cf95242 100644 --- a/tests/components/accuweather/test_system_health.py +++ b/tests/components/accuweather/test_system_health.py @@ -1,34 +1,32 @@ """Test AccuWeather system health.""" import asyncio -from unittest.mock import Mock +from unittest.mock import AsyncMock from aiohttp import ClientError -from homeassistant.components.accuweather import AccuWeatherData from homeassistant.components.accuweather.const import DOMAIN from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component +from . import init_integration + from tests.common import get_system_health_info from tests.test_util.aiohttp import AiohttpClientMocker async def test_accuweather_system_health( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + mock_accuweather_client: AsyncMock, ) -> None: """Test AccuWeather system health.""" aioclient_mock.get("https://dataservice.accuweather.com/", text="") - hass.config.components.add(DOMAIN) + + await init_integration(hass) assert await async_setup_component(hass, "system_health", {}) await hass.async_block_till_done() - hass.data[DOMAIN] = {} - hass.data[DOMAIN]["0123xyz"] = AccuWeatherData( - coordinator_observation=Mock(accuweather=Mock(requests_remaining="42")), - coordinator_daily_forecast=Mock(), - ) - info = await get_system_health_info(hass, DOMAIN) for key, val in info.items(): @@ -37,25 +35,22 @@ async def test_accuweather_system_health( assert info == { "can_reach_server": "ok", - "remaining_requests": "42", + "remaining_requests": 10, } async def test_accuweather_system_health_fail( - hass: HomeAssistant, aioclient_mock: AiohttpClientMocker + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + mock_accuweather_client: AsyncMock, ) -> None: """Test AccuWeather system health.""" aioclient_mock.get("https://dataservice.accuweather.com/", exc=ClientError) - hass.config.components.add(DOMAIN) + + await init_integration(hass) assert await async_setup_component(hass, "system_health", {}) await hass.async_block_till_done() - hass.data[DOMAIN] = {} - hass.data[DOMAIN]["0123xyz"] = AccuWeatherData( - coordinator_observation=Mock(accuweather=Mock(requests_remaining="0")), - coordinator_daily_forecast=Mock(), - ) - info = await get_system_health_info(hass, DOMAIN) for key, val in info.items(): @@ -64,5 +59,5 @@ async def test_accuweather_system_health_fail( assert info == { "can_reach_server": {"type": "failed", "error": "unreachable"}, - "remaining_requests": "0", + "remaining_requests": 10, } diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py index d97a5d3da3c..1a6201c20a2 100644 --- a/tests/components/accuweather/test_weather.py +++ b/tests/components/accuweather/test_weather.py @@ -1,7 +1,7 @@ """Test weather of AccuWeather integration.""" from datetime import timedelta -from unittest.mock import PropertyMock, patch +from unittest.mock import AsyncMock, patch from freezegun.api import FrozenDateTimeFactory import pytest @@ -18,21 +18,18 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.setup import async_setup_component -from homeassistant.util.dt import utcnow from . import init_integration -from tests.common import ( - async_fire_time_changed, - load_json_array_fixture, - load_json_object_fixture, - snapshot_platform, -) +from tests.common import async_fire_time_changed, snapshot_platform from tests.typing import WebSocketGenerator async def test_weather( - hass: HomeAssistant, entity_registry: er.EntityRegistry, snapshot: SnapshotAssertion + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, + mock_accuweather_client: AsyncMock, ) -> None: """Test states of the weather without forecast.""" with patch("homeassistant.components.accuweather.PLATFORMS", [Platform.WEATHER]): @@ -40,81 +37,71 @@ async def test_weather( await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id) -async def test_availability(hass: HomeAssistant) -> None: +async def test_availability( + hass: HomeAssistant, + mock_accuweather_client: AsyncMock, + freezer: FrozenDateTimeFactory, +) -> None: """Ensure that we mark the entities unavailable correctly when service is offline.""" + entity_id = "weather.home" await init_integration(hass) - state = hass.states.get("weather.home") + state = hass.states.get(entity_id) assert state assert state.state != STATE_UNAVAILABLE assert state.state == "sunny" - future = utcnow() + timedelta(minutes=60) - with patch( - "homeassistant.components.accuweather.AccuWeather._async_get_data", - side_effect=ConnectionError(), - ): - async_fire_time_changed(hass, future) - await hass.async_block_till_done() + mock_accuweather_client.async_get_current_conditions.side_effect = ConnectionError - state = hass.states.get("weather.home") - assert state - assert state.state == STATE_UNAVAILABLE + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST) + async_fire_time_changed(hass) + await hass.async_block_till_done() - future = utcnow() + timedelta(minutes=120) - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=load_json_object_fixture( - "accuweather/current_conditions_data.json" - ), - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - async_fire_time_changed(hass, future) - await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNAVAILABLE - state = hass.states.get("weather.home") - assert state - assert state.state != STATE_UNAVAILABLE - assert state.state == "sunny" + mock_accuweather_client.async_get_current_conditions.side_effect = None + + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "sunny" -async def test_manual_update_entity(hass: HomeAssistant) -> None: +async def test_manual_update_entity( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test manual update entity via service homeassistant/update_entity.""" await init_integration(hass) await async_setup_component(hass, "homeassistant", {}) - current = load_json_object_fixture("accuweather/current_conditions_data.json") + assert mock_accuweather_client.async_get_current_conditions.call_count == 1 - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ) as mock_current, - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - await hass.services.async_call( - "homeassistant", - "update_entity", - {ATTR_ENTITY_ID: ["weather.home"]}, - blocking=True, - ) - assert mock_current.call_count == 1 + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["weather.home"]}, + blocking=True, + ) + + assert mock_accuweather_client.async_get_current_conditions.call_count == 2 -async def test_unsupported_condition_icon_data(hass: HomeAssistant) -> None: +async def test_unsupported_condition_icon_data( + hass: HomeAssistant, mock_accuweather_client: AsyncMock +) -> None: """Test with unsupported condition icon data.""" - await init_integration(hass, unsupported_icon=True) + mock_accuweather_client.async_get_current_conditions.return_value["WeatherIcon"] = ( + 999 + ) + + await init_integration(hass) state = hass.states.get("weather.home") assert state.attributes.get(ATTR_FORECAST_CONDITION) is None @@ -130,6 +117,7 @@ async def test_unsupported_condition_icon_data(hass: HomeAssistant) -> None: async def test_forecast_service( hass: HomeAssistant, snapshot: SnapshotAssertion, + mock_accuweather_client: AsyncMock, service: str, ) -> None: """Test multiple forecast.""" @@ -153,6 +141,7 @@ async def test_forecast_subscription( hass_ws_client: WebSocketGenerator, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion, + mock_accuweather_client: AsyncMock, ) -> None: """Test multiple forecast.""" client = await hass_ws_client(hass) @@ -179,27 +168,9 @@ async def test_forecast_subscription( assert forecast1 != [] assert forecast1 == snapshot - current = load_json_object_fixture("accuweather/current_conditions_data.json") - forecast = load_json_array_fixture("accuweather/forecast_data.json") - - with ( - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", - return_value=current, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.async_get_daily_forecast", - return_value=forecast, - ), - patch( - "homeassistant.components.accuweather.AccuWeather.requests_remaining", - new_callable=PropertyMock, - return_value=10, - ), - ): - freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST + timedelta(seconds=1)) - await hass.async_block_till_done() - msg = await client.receive_json() + freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST + timedelta(seconds=1)) + await hass.async_block_till_done() + msg = await client.receive_json() assert msg["id"] == subscription_id assert msg["type"] == "event"