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>
This commit is contained in:
Maciej Bieniek 2024-05-06 19:41:48 +02:00 committed by GitHub
parent 72d6b4d1c9
commit 09be56964d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 305 additions and 387 deletions

View file

@ -64,8 +64,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator_observation.async_config_entry_first_refresh() await coordinator_observation.async_config_entry_first_refresh()
await coordinator_daily_forecast.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( hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AccuWeatherData(
coordinator_observation=coordinator_observation, coordinator_observation=coordinator_observation,
coordinator_daily_forecast=coordinator_daily_forecast, 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) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
await hass.config_entries.async_reload(entry.entry_id)

View file

@ -1,17 +1,11 @@
"""Tests for AccuWeather.""" """Tests for AccuWeather."""
from unittest.mock import PropertyMock, patch
from homeassistant.components.accuweather.const import DOMAIN from homeassistant.components.accuweather.const import DOMAIN
from tests.common import ( from tests.common import MockConfigEntry
MockConfigEntry,
load_json_array_fixture,
load_json_object_fixture,
)
async def init_integration(hass, unsupported_icon=False) -> MockConfigEntry: async def init_integration(hass) -> MockConfigEntry:
"""Set up the AccuWeather integration in Home Assistant.""" """Set up the AccuWeather integration in Home Assistant."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, 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") entry.add_to_hass(hass)
forecast = load_json_array_fixture("accuweather/forecast_data.json") await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
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()
return entry return entry

View file

@ -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

View file

@ -1,6 +1,6 @@
"""Define tests for the AccuWeather config flow.""" """Define tests for the AccuWeather config flow."""
from unittest.mock import patch from unittest.mock import AsyncMock
from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError 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.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry, load_json_object_fixture from tests.common import MockConfigEntry
VALID_CONFIG = { VALID_CONFIG = {
CONF_NAME: "abcd", 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"} 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.""" """Test that errors are shown when API key is invalid."""
with patch( mock_accuweather_client.async_get_location.side_effect = InvalidApiKeyError(
"homeassistant.components.accuweather.AccuWeather._async_get_data", "Invalid API key"
side_effect=InvalidApiKeyError("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"} 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.""" """Test API error."""
with patch( mock_accuweather_client.async_get_location.side_effect = ApiError(
"homeassistant.components.accuweather.AccuWeather._async_get_data", "Invalid response from AccuWeather API"
side_effect=ApiError("Invalid response from AccuWeather API"), )
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data=VALID_CONFIG,
)
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.""" """Test requests exceeded error."""
with patch( mock_accuweather_client.async_get_location.side_effect = RequestsExceededError(
"homeassistant.components.accuweather.AccuWeather._async_get_data", "The allowed number of requests has been exceeded"
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,
)
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.""" """Test we only allow a single config flow."""
with patch( MockConfigEntry(
"homeassistant.components.accuweather.AccuWeather._async_get_data", domain=DOMAIN,
return_value=load_json_object_fixture("accuweather/location_data.json"), unique_id="123456",
): data=VALID_CONFIG,
MockConfigEntry( ).add_to_hass(hass)
domain=DOMAIN,
unique_id="123456",
data=VALID_CONFIG,
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": SOURCE_USER}, context={"source": SOURCE_USER},
data=VALID_CONFIG, data=VALID_CONFIG,
) )
assert result["type"] is FlowResultType.ABORT assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed" 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.""" """Test that the user step works."""
with ( result = await hass.config_entries.flow.async_init(
patch( DOMAIN,
"homeassistant.components.accuweather.AccuWeather._async_get_data", context={"source": SOURCE_USER},
return_value=load_json_object_fixture("accuweather/location_data.json"), data=VALID_CONFIG,
), )
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,
)
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "abcd" assert result["title"] == "abcd"
assert result["data"][CONF_NAME] == "abcd" assert result["data"][CONF_NAME] == "abcd"
assert result["data"][CONF_LATITUDE] == 55.55 assert result["data"][CONF_LATITUDE] == 55.55
assert result["data"][CONF_LONGITUDE] == 122.12 assert result["data"][CONF_LONGITUDE] == 122.12
assert result["data"][CONF_API_KEY] == "32-character-string-1234567890qw" assert result["data"][CONF_API_KEY] == "32-character-string-1234567890qw"

View file

@ -1,5 +1,7 @@
"""Test AccuWeather diagnostics.""" """Test AccuWeather diagnostics."""
from unittest.mock import AsyncMock
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -13,6 +15,7 @@ from tests.typing import ClientSessionGenerator
async def test_entry_diagnostics( async def test_entry_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
mock_accuweather_client: AsyncMock,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test config entry diagnostics.""" """Test config entry diagnostics."""

View file

@ -1,8 +1,9 @@
"""Test init of AccuWeather integration.""" """Test init of AccuWeather integration."""
from unittest.mock import patch from unittest.mock import AsyncMock
from accuweather import ApiError from accuweather import ApiError
from freezegun.api import FrozenDateTimeFactory
from homeassistant.components.accuweather.const import ( from homeassistant.components.accuweather.const import (
DOMAIN, DOMAIN,
@ -14,19 +15,15 @@ from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE 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 homeassistant.util.dt import utcnow
from . import init_integration from . import init_integration
from tests.common import ( from tests.common import MockConfigEntry, async_fire_time_changed
MockConfigEntry,
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
)
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.""" """Test a successful setup entry."""
await init_integration(hass) await init_integration(hass)
@ -36,7 +33,9 @@ async def test_async_setup_entry(hass: HomeAssistant) -> None:
assert state.state == "sunny" 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.""" """Test for setup failure if connection to AccuWeather is missing."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -50,16 +49,18 @@ async def test_config_not_ready(hass: HomeAssistant) -> None:
}, },
) )
with patch( mock_accuweather_client.async_get_current_conditions.side_effect = ApiError(
"homeassistant.components.accuweather.AccuWeather._async_get_data", "API Error"
side_effect=ApiError("API Error"), )
):
entry.add_to_hass(hass) entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.SETUP_RETRY 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.""" """Test successful unload of entry."""
entry = await init_integration(hass) entry = await init_integration(hass)
@ -73,41 +74,36 @@ async def test_unload_entry(hass: HomeAssistant) -> None:
assert not hass.data.get(DOMAIN) 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.""" """Test correct update interval."""
entry = await init_integration(hass) entry = await init_integration(hass)
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
current = load_json_object_fixture("accuweather/current_conditions_data.json") assert mock_accuweather_client.async_get_current_conditions.call_count == 1
forecast = load_json_array_fixture("accuweather/forecast_data.json") assert mock_accuweather_client.async_get_daily_forecast.call_count == 1
with ( freezer.tick(UPDATE_INTERVAL_OBSERVATION)
patch( async_fire_time_changed(hass)
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", await hass.async_block_till_done()
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
async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL_OBSERVATION) assert mock_accuweather_client.async_get_current_conditions.call_count == 2
await hass.async_block_till_done()
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) assert mock_accuweather_client.async_get_daily_forecast.call_count == 2
await hass.async_block_till_done()
assert mock_forecast.call_count == 1
async def test_remove_ozone_sensors( async def test_remove_ozone_sensors(
hass: HomeAssistant, entity_registry: er.EntityRegistry hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_accuweather_client: AsyncMock,
) -> None: ) -> None:
"""Test remove ozone sensors from registry.""" """Test remove ozone sensors from registry."""
entity_registry.async_get_or_create( entity_registry.async_get_or_create(

View file

@ -1,14 +1,17 @@
"""Test sensor of AccuWeather integration.""" """Test sensor of AccuWeather integration."""
from datetime import timedelta from unittest.mock import AsyncMock, patch
from unittest.mock import PropertyMock, patch
from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError from accuweather import ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp.client_exceptions import ClientConnectorError from aiohttp.client_exceptions import ClientConnectorError
from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy import SnapshotAssertion 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 ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
@ -21,23 +24,18 @@ from homeassistant.const import (
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 homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
from . import init_integration from . import init_integration
from tests.common import ( from tests.common import async_fire_time_changed, snapshot_platform
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
snapshot_platform,
)
async def test_sensor( async def test_sensor(
hass: HomeAssistant, hass: HomeAssistant,
entity_registry_enabled_by_default: None, entity_registry_enabled_by_default: None,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
mock_accuweather_client: AsyncMock,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test states of the sensor.""" """Test states of the sensor."""
@ -46,64 +44,59 @@ async def test_sensor(
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id) 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.""" """Ensure that we mark the entities unavailable correctly when service is offline."""
entity_id = "sensor.home_cloud_ceiling"
await init_integration(hass) await init_integration(hass)
state = hass.states.get("sensor.home_cloud_ceiling") state = hass.states.get(entity_id)
assert state assert state
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
assert state.state == "3200.0" assert state.state == "3200.0"
future = utcnow() + timedelta(minutes=60) mock_accuweather_client.async_get_current_conditions.side_effect = ConnectionError
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()
state = hass.states.get("sensor.home_cloud_ceiling") freezer.tick(UPDATE_INTERVAL_OBSERVATION)
assert state async_fire_time_changed(hass)
assert state.state == STATE_UNAVAILABLE await hass.async_block_till_done()
future = utcnow() + timedelta(minutes=120) state = hass.states.get(entity_id)
with ( assert state
patch( assert state.state == STATE_UNAVAILABLE
"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("sensor.home_cloud_ceiling") mock_accuweather_client.async_get_current_conditions.side_effect = None
assert state
assert state.state != STATE_UNAVAILABLE freezer.tick(UPDATE_INTERVAL_OBSERVATION)
assert state.state == "3200.0" 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( @pytest.mark.parametrize(
"exception", "exception",
[ [
ApiError, ApiError("API Error"),
ConnectionError, ConnectionError,
ClientConnectorError, ClientConnectorError,
InvalidApiKeyError, InvalidApiKeyError("Invalid API key"),
RequestsExceededError, 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.""" """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" entity_id = "sensor.home_hours_of_sun_day_2"
await init_integration(hass) 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 != STATE_UNAVAILABLE
assert state.state == "5.7" assert state.state == "5.7"
with ( mock_accuweather_client.async_get_daily_forecast.side_effect = exception
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST)
return_value=current, async_fire_time_changed(hass)
), await hass.async_block_till_done()
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()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state assert state
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
with ( mock_accuweather_client.async_get_daily_forecast.side_effect = None
patch(
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST)
return_value=current, async_fire_time_changed(hass)
), await hass.async_block_till_done()
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()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state assert state
@ -159,35 +128,29 @@ async def test_availability_forecast(hass: HomeAssistant, exception: Exception)
assert state.state == "5.7" 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.""" """Test manual update entity via service homeassistant/update_entity."""
await init_integration(hass) await init_integration(hass)
await async_setup_component(hass, "homeassistant", {}) 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 ( await hass.services.async_call(
patch( "homeassistant",
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", "update_entity",
return_value=current, {ATTR_ENTITY_ID: ["sensor.home_cloud_ceiling"]},
) as mock_current, blocking=True,
patch( )
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock, assert mock_accuweather_client.async_get_current_conditions.call_count == 2
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
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.""" """Test states of the sensor without forecast."""
hass.config.units = US_CUSTOMARY_SYSTEM hass.config.units = US_CUSTOMARY_SYSTEM
await init_integration(hass) 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.""" """Ensure the sensor state changes after updating the data."""
entity_id = "sensor.home_cloud_ceiling"
await init_integration(hass) await init_integration(hass)
state = hass.states.get("sensor.home_cloud_ceiling") state = hass.states.get(entity_id)
assert state assert state
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
assert state.state == "3200.0" 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( freezer.tick(UPDATE_INTERVAL_OBSERVATION)
"accuweather/current_conditions_data.json" async_fire_time_changed(hass)
) await hass.async_block_till_done()
current_condition["Ceiling"]["Metric"]["Value"] = 3300
with ( state = hass.states.get(entity_id)
patch( assert state
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", assert state.state != STATE_UNAVAILABLE
return_value=current_condition, assert state.state == "3300"
),
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"

View file

@ -1,34 +1,32 @@
"""Test AccuWeather system health.""" """Test AccuWeather system health."""
import asyncio import asyncio
from unittest.mock import Mock from unittest.mock import AsyncMock
from aiohttp import ClientError from aiohttp import ClientError
from homeassistant.components.accuweather import AccuWeatherData
from homeassistant.components.accuweather.const import DOMAIN from homeassistant.components.accuweather.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from . import init_integration
from tests.common import get_system_health_info from tests.common import get_system_health_info
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
async def test_accuweather_system_health( async def test_accuweather_system_health(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
mock_accuweather_client: AsyncMock,
) -> None: ) -> None:
"""Test AccuWeather system health.""" """Test AccuWeather system health."""
aioclient_mock.get("https://dataservice.accuweather.com/", text="") 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", {}) assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done() 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) info = await get_system_health_info(hass, DOMAIN)
for key, val in info.items(): for key, val in info.items():
@ -37,25 +35,22 @@ async def test_accuweather_system_health(
assert info == { assert info == {
"can_reach_server": "ok", "can_reach_server": "ok",
"remaining_requests": "42", "remaining_requests": 10,
} }
async def test_accuweather_system_health_fail( async def test_accuweather_system_health_fail(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
mock_accuweather_client: AsyncMock,
) -> None: ) -> None:
"""Test AccuWeather system health.""" """Test AccuWeather system health."""
aioclient_mock.get("https://dataservice.accuweather.com/", exc=ClientError) 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", {}) assert await async_setup_component(hass, "system_health", {})
await hass.async_block_till_done() 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) info = await get_system_health_info(hass, DOMAIN)
for key, val in info.items(): for key, val in info.items():
@ -64,5 +59,5 @@ async def test_accuweather_system_health_fail(
assert info == { assert info == {
"can_reach_server": {"type": "failed", "error": "unreachable"}, "can_reach_server": {"type": "failed", "error": "unreachable"},
"remaining_requests": "0", "remaining_requests": 10,
} }

View file

@ -1,7 +1,7 @@
"""Test weather of AccuWeather integration.""" """Test weather of AccuWeather integration."""
from datetime import timedelta from datetime import timedelta
from unittest.mock import PropertyMock, patch from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
@ -18,21 +18,18 @@ from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
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 homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from . import init_integration from . import init_integration
from tests.common import ( from tests.common import async_fire_time_changed, snapshot_platform
async_fire_time_changed,
load_json_array_fixture,
load_json_object_fixture,
snapshot_platform,
)
from tests.typing import WebSocketGenerator from tests.typing import WebSocketGenerator
async def test_weather( 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: ) -> None:
"""Test states of the weather without forecast.""" """Test states of the weather without forecast."""
with patch("homeassistant.components.accuweather.PLATFORMS", [Platform.WEATHER]): 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) 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.""" """Ensure that we mark the entities unavailable correctly when service is offline."""
entity_id = "weather.home"
await init_integration(hass) await init_integration(hass)
state = hass.states.get("weather.home") state = hass.states.get(entity_id)
assert state assert state
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
assert state.state == "sunny" assert state.state == "sunny"
future = utcnow() + timedelta(minutes=60) mock_accuweather_client.async_get_current_conditions.side_effect = ConnectionError
with patch(
"homeassistant.components.accuweather.AccuWeather._async_get_data",
side_effect=ConnectionError(),
):
async_fire_time_changed(hass, future)
await hass.async_block_till_done()
state = hass.states.get("weather.home") freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST)
assert state async_fire_time_changed(hass)
assert state.state == STATE_UNAVAILABLE await hass.async_block_till_done()
future = utcnow() + timedelta(minutes=120) state = hass.states.get(entity_id)
with ( assert state
patch( assert state.state == STATE_UNAVAILABLE
"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("weather.home") mock_accuweather_client.async_get_current_conditions.side_effect = None
assert state
assert state.state != STATE_UNAVAILABLE freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST)
assert state.state == "sunny" 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.""" """Test manual update entity via service homeassistant/update_entity."""
await init_integration(hass) await init_integration(hass)
await async_setup_component(hass, "homeassistant", {}) 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 ( await hass.services.async_call(
patch( "homeassistant",
"homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", "update_entity",
return_value=current, {ATTR_ENTITY_ID: ["weather.home"]},
) as mock_current, blocking=True,
patch( )
"homeassistant.components.accuweather.AccuWeather.requests_remaining",
new_callable=PropertyMock, assert mock_accuweather_client.async_get_current_conditions.call_count == 2
return_value=10,
),
):
await hass.services.async_call(
"homeassistant",
"update_entity",
{ATTR_ENTITY_ID: ["weather.home"]},
blocking=True,
)
assert mock_current.call_count == 1
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.""" """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") state = hass.states.get("weather.home")
assert state.attributes.get(ATTR_FORECAST_CONDITION) is None 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( async def test_forecast_service(
hass: HomeAssistant, hass: HomeAssistant,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
mock_accuweather_client: AsyncMock,
service: str, service: str,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
@ -153,6 +141,7 @@ async def test_forecast_subscription(
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
freezer: FrozenDateTimeFactory, freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
mock_accuweather_client: AsyncMock,
) -> None: ) -> None:
"""Test multiple forecast.""" """Test multiple forecast."""
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
@ -179,27 +168,9 @@ async def test_forecast_subscription(
assert forecast1 != [] assert forecast1 != []
assert forecast1 == snapshot assert forecast1 == snapshot
current = load_json_object_fixture("accuweather/current_conditions_data.json") freezer.tick(UPDATE_INTERVAL_DAILY_FORECAST + timedelta(seconds=1))
forecast = load_json_array_fixture("accuweather/forecast_data.json") await hass.async_block_till_done()
msg = await client.receive_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()
assert msg["id"] == subscription_id assert msg["id"] == subscription_id
assert msg["type"] == "event" assert msg["type"] == "event"