mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
Trigger location update on certain events for the Volvo integration (#172651)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
co-authored by
Joost Lekkerkerker
parent
dd688986f1
commit
8aca6115f4
@@ -50,19 +50,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: VolvoConfigEntry) -> boo
|
||||
api = await _async_auth_and_create_api(hass, entry)
|
||||
context = await _async_create_context(api)
|
||||
|
||||
# Order is important! Faster intervals must come first.
|
||||
# Different interval coordinators are in place to keep the number
|
||||
# of requests under 5000 per day. This lets users use the same
|
||||
# API key for two vehicles (as the limit is 10000 per day).
|
||||
coordinators = (
|
||||
VolvoFastIntervalCoordinator(hass, entry, context),
|
||||
VolvoMediumIntervalCoordinator(hass, entry, context),
|
||||
VolvoSlowIntervalCoordinator(hass, entry, context),
|
||||
VolvoVerySlowIntervalCoordinator(hass, entry, context),
|
||||
)
|
||||
await asyncio.gather(*(c.async_config_entry_first_refresh() for c in coordinators))
|
||||
# of requests under 10000 per day.
|
||||
fast_coordinator = VolvoFastIntervalCoordinator(hass, entry, context)
|
||||
medium_coordinator = VolvoMediumIntervalCoordinator(hass, entry, context)
|
||||
slow_coordinator = VolvoSlowIntervalCoordinator(hass, entry, context)
|
||||
very_slow_coordinator = VolvoVerySlowIntervalCoordinator(hass, entry, context)
|
||||
|
||||
entry.runtime_data = VolvoRuntimeData(coordinators, context)
|
||||
await asyncio.gather(
|
||||
*(
|
||||
c.async_config_entry_first_refresh()
|
||||
for c in (
|
||||
fast_coordinator,
|
||||
medium_coordinator,
|
||||
slow_coordinator,
|
||||
very_slow_coordinator,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
entry.runtime_data = VolvoRuntimeData(
|
||||
fast_coordinator,
|
||||
medium_coordinator,
|
||||
slow_coordinator,
|
||||
very_slow_coordinator,
|
||||
context,
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
@@ -46,14 +46,32 @@ class VolvoContext:
|
||||
class VolvoRuntimeData:
|
||||
"""Volvo runtime data."""
|
||||
|
||||
interval_coordinators: tuple[VolvoBaseCoordinator, ...]
|
||||
fast_coordinator: VolvoFastIntervalCoordinator
|
||||
medium_coordinator: VolvoMediumIntervalCoordinator
|
||||
slow_coordinator: VolvoSlowIntervalCoordinator
|
||||
very_slow_coordinator: VolvoVerySlowIntervalCoordinator
|
||||
context: VolvoContext
|
||||
|
||||
@property
|
||||
def interval_coordinators(self) -> tuple[VolvoBaseCoordinator, ...]:
|
||||
"""Return all coordinators."""
|
||||
return (
|
||||
self.fast_coordinator,
|
||||
self.medium_coordinator,
|
||||
self.slow_coordinator,
|
||||
self.very_slow_coordinator,
|
||||
)
|
||||
|
||||
|
||||
type VolvoConfigEntry = ConfigEntry[VolvoRuntimeData]
|
||||
type CoordinatorData = dict[str, VolvoCarsApiBaseModel | None]
|
||||
|
||||
|
||||
def schedule_location_update(coordinator: VolvoBaseCoordinator) -> None:
|
||||
"""Schedule a location-only update."""
|
||||
coordinator.config_entry.runtime_data.slow_coordinator.async_schedule_location_update()
|
||||
|
||||
|
||||
def _is_invalid_api_field(field: VolvoCarsApiBaseModel | None) -> bool:
|
||||
if not field:
|
||||
return True
|
||||
@@ -180,7 +198,7 @@ class VolvoBaseCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
||||
def get_api_field(self, api_field: str | None) -> VolvoCarsApiBaseModel | None:
|
||||
"""Get the API field based on the entity description."""
|
||||
|
||||
return self.data.get(api_field) if api_field else None
|
||||
return self.data.get(api_field) if self.data and api_field else None
|
||||
|
||||
@abstractmethod
|
||||
async def _async_determine_api_calls(
|
||||
@@ -255,6 +273,23 @@ class VolvoSlowIntervalCoordinator(VolvoBaseCoordinator):
|
||||
"Volvo slow interval coordinator",
|
||||
)
|
||||
|
||||
self._location_supported = False
|
||||
self._location_update_task: asyncio.Task[None] | None = None
|
||||
|
||||
def async_schedule_location_update(self) -> None:
|
||||
"""Schedule a single location update if none is in-flight."""
|
||||
if not self._location_supported:
|
||||
return
|
||||
|
||||
if self._location_update_task and not self._location_update_task.done():
|
||||
return
|
||||
|
||||
self._location_update_task = self.config_entry.async_create_background_task(
|
||||
self.hass,
|
||||
self._async_update_location(),
|
||||
"Volvo location update",
|
||||
)
|
||||
|
||||
@override
|
||||
async def _async_determine_api_calls(
|
||||
self,
|
||||
@@ -282,9 +317,35 @@ class VolvoSlowIntervalCoordinator(VolvoBaseCoordinator):
|
||||
|
||||
if location and location.get("location") is not None:
|
||||
api_calls.append(api.async_get_location)
|
||||
self._location_supported = True
|
||||
|
||||
return api_calls
|
||||
|
||||
async def _async_update_location(self) -> None:
|
||||
"""Fetch only the location data and update listeners."""
|
||||
if not self._location_supported:
|
||||
return
|
||||
|
||||
try:
|
||||
location = await self.context.api.async_get_location()
|
||||
except (VolvoApiException, VolvoAuthException) as ex:
|
||||
_LOGGER.debug(
|
||||
"%s - Location update failed: %s",
|
||||
self.config_entry.entry_id,
|
||||
ex.message,
|
||||
)
|
||||
return
|
||||
|
||||
valid_location: dict[str, VolvoCarsApiBaseModel | None] = {
|
||||
key: field
|
||||
for key, field in location.items()
|
||||
if not _is_invalid_api_field(field)
|
||||
}
|
||||
|
||||
if valid_location:
|
||||
self.data = dict(self.data or {}) | valid_location
|
||||
self.async_update_listeners()
|
||||
|
||||
|
||||
class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator):
|
||||
"""Volvo coordinator with medium update rate."""
|
||||
@@ -307,6 +368,23 @@ class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator):
|
||||
|
||||
self._supported_capabilities: list[str] = []
|
||||
|
||||
async def _async_update_data(self) -> CoordinatorData:
|
||||
"""Fetch data and trigger location update on engine-off."""
|
||||
previous_state = self.get_api_field("engineStatus")
|
||||
data = await super()._async_update_data()
|
||||
new_state = data.get("engineStatus")
|
||||
|
||||
if (
|
||||
isinstance(previous_state, VolvoCarsValue)
|
||||
and previous_state.value == "RUNNING"
|
||||
and isinstance(new_state, VolvoCarsValue)
|
||||
and new_state.value
|
||||
and new_state.value != "RUNNING"
|
||||
):
|
||||
schedule_location_update(self)
|
||||
|
||||
return data
|
||||
|
||||
@override
|
||||
async def _async_determine_api_calls(
|
||||
self,
|
||||
@@ -388,3 +466,19 @@ class VolvoFastIntervalCoordinator(VolvoBaseCoordinator):
|
||||
api.async_get_doors_status,
|
||||
api.async_get_window_states,
|
||||
]
|
||||
|
||||
async def _async_update_data(self) -> CoordinatorData:
|
||||
"""Fetch data and trigger location update on lock."""
|
||||
previous_state = self.get_api_field("centralLock")
|
||||
data = await super()._async_update_data()
|
||||
new_state = data.get("centralLock")
|
||||
|
||||
if (
|
||||
isinstance(previous_state, VolvoCarsValue)
|
||||
and previous_state.value != "LOCKED"
|
||||
and isinstance(new_state, VolvoCarsValue)
|
||||
and new_state.value == "LOCKED"
|
||||
):
|
||||
schedule_location_update(self)
|
||||
|
||||
return data
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import VolvoConfigEntry
|
||||
from .coordinator import VolvoConfigEntry, schedule_location_update
|
||||
from .entity import VolvoEntity, VolvoEntityDescription
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -119,6 +119,7 @@ class VolvoLock(VolvoEntity, LockEntity):
|
||||
|
||||
if locked:
|
||||
api_field.value = self.entity_description.api_lock_value
|
||||
schedule_location_update(self.coordinator)
|
||||
else:
|
||||
api_field.value = self.entity_description.api_unlock_value
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""Test Volvo coordinator."""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
@@ -14,13 +16,20 @@ from volvocarsapi.models import (
|
||||
)
|
||||
|
||||
from homeassistant.components.volvo.const import DOMAIN
|
||||
from homeassistant.components.volvo.coordinator import VERY_SLOW_INTERVAL
|
||||
from homeassistant.components.volvo.coordinator import (
|
||||
FAST_INTERVAL,
|
||||
MEDIUM_INTERVAL,
|
||||
VERY_SLOW_INTERVAL,
|
||||
VolvoConfigEntry,
|
||||
schedule_location_update,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import configure_mock
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
@@ -131,13 +140,13 @@ async def test_update_coordinator_all_error(
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_coordinator_location_auth_exception(
|
||||
async def test_coordinator_location_exception(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test coordinator setup when location returns VolvoAuthException."""
|
||||
"""Test coordinator setup when location returns an exception."""
|
||||
configure_mock(
|
||||
mock_api.async_get_location, side_effect=VolvoAuthException(403, "Forbidden")
|
||||
)
|
||||
@@ -157,6 +166,235 @@ async def test_coordinator_location_auth_exception(
|
||||
assert state.state == "30000"
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_engine_off_triggers_location_update(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that engine turning off triggers a location update."""
|
||||
# Start with engine RUNNING
|
||||
configure_mock(
|
||||
mock_api.async_get_engine_status,
|
||||
return_value={"engineStatus": VolvoCarsValueField(value="RUNNING")},
|
||||
)
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
# Engine turns off on next poll
|
||||
configure_mock(
|
||||
mock_api.async_get_engine_status,
|
||||
return_value={"engineStatus": VolvoCarsValueField(value="STOPPED")},
|
||||
)
|
||||
freezer.tick(timedelta(minutes=MEDIUM_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count > location_call_count_before
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_engine_stays_running_no_extra_location(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that engine staying RUNNING does not trigger extra location update."""
|
||||
configure_mock(
|
||||
mock_api.async_get_engine_status,
|
||||
return_value={"engineStatus": VolvoCarsValueField(value="RUNNING")},
|
||||
)
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
# Engine stays RUNNING on next poll
|
||||
freezer.tick(timedelta(minutes=MEDIUM_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count == location_call_count_before
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_physical_lock_triggers_location_update(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that physical lock (key fob) triggers a location update."""
|
||||
# Start with car unlocked
|
||||
doors_unlocked = _get_doors_data(mock_api, "UNLOCKED")
|
||||
configure_mock(mock_api.async_get_doors_status, return_value=doors_unlocked)
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
# Car gets locked physically on next poll
|
||||
doors_locked = _get_doors_data(mock_api, "LOCKED")
|
||||
configure_mock(mock_api.async_get_doors_status, return_value=doors_locked)
|
||||
freezer.tick(timedelta(minutes=FAST_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count > location_call_count_before
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_physical_unlock_does_not_trigger_location_update(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that unlocking does not trigger a location update."""
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
# Car gets unlocked on next poll
|
||||
doors_unlocked = _get_doors_data(mock_api, "UNLOCKED")
|
||||
configure_mock(mock_api.async_get_doors_status, return_value=doors_unlocked)
|
||||
freezer.tick(timedelta(minutes=FAST_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count == location_call_count_before
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-05-31T10:00:00+00:00")
|
||||
async def test_location_update_not_supported(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that async_update_location is a no-op when location is unsupported."""
|
||||
configure_mock(
|
||||
mock_api.async_get_location, side_effect=VolvoAuthException(403, "Forbidden")
|
||||
)
|
||||
configure_mock(
|
||||
mock_api.async_get_engine_status,
|
||||
return_value={"engineStatus": VolvoCarsValueField(value="RUNNING")},
|
||||
)
|
||||
assert await setup_integration()
|
||||
mock_api.async_get_location.reset_mock()
|
||||
|
||||
configure_mock(
|
||||
mock_api.async_get_engine_status,
|
||||
return_value={"engineStatus": VolvoCarsValueField(value="STOPPED")},
|
||||
)
|
||||
freezer.tick(timedelta(minutes=MEDIUM_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
mock_api.async_get_location.assert_not_called()
|
||||
|
||||
|
||||
async def test_coordinator_setup_auth_exception(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test coordinator setup when determine API calls raises auth exception."""
|
||||
configure_mock(
|
||||
mock_api.async_get_energy_capabilities,
|
||||
side_effect=VolvoAuthException(401, "Unauthorized"),
|
||||
)
|
||||
|
||||
assert not await setup_integration()
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
async def test_coordinator_setup_not_ready_exception(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test coordinator setup when determine API calls raises API exception."""
|
||||
configure_mock(
|
||||
mock_api.async_get_energy_capabilities, side_effect=VolvoApiException
|
||||
)
|
||||
|
||||
assert not await setup_integration()
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exception",
|
||||
[
|
||||
VolvoApiException("Location failed"),
|
||||
VolvoAuthException(401, "Unauthorized"),
|
||||
],
|
||||
)
|
||||
async def test_update_location_exception_logs_debug(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
exception: VolvoApiException | VolvoAuthException,
|
||||
) -> None:
|
||||
"""Test async_update_location logs debug when location call fails."""
|
||||
assert await setup_integration()
|
||||
|
||||
entry: VolvoConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
configure_mock(mock_api.async_get_location, side_effect=exception)
|
||||
|
||||
with caplog.at_level(
|
||||
logging.DEBUG, logger="homeassistant.components.volvo.coordinator"
|
||||
):
|
||||
await entry.runtime_data.slow_coordinator._async_update_location()
|
||||
|
||||
assert "Location update failed" in caplog.text
|
||||
|
||||
|
||||
async def test_schedule_location_update_is_coalesced(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test location update scheduling coalesces in-flight updates."""
|
||||
assert await setup_integration()
|
||||
|
||||
entry: VolvoConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
coordinator = entry.runtime_data.interval_coordinators[0]
|
||||
|
||||
location_data = mock_api.async_get_location.return_value
|
||||
started = asyncio.Event()
|
||||
release = asyncio.Event()
|
||||
|
||||
async def _delayed_location_update() -> dict[str, VolvoCarsValueField]:
|
||||
started.set()
|
||||
await release.wait()
|
||||
return location_data
|
||||
|
||||
configure_mock(mock_api.async_get_location, side_effect=_delayed_location_update)
|
||||
|
||||
schedule_location_update(coordinator)
|
||||
schedule_location_update(coordinator)
|
||||
schedule_location_update(coordinator)
|
||||
|
||||
await started.wait()
|
||||
|
||||
# Only one in-flight background task should be allowed.
|
||||
assert mock_api.async_get_location.call_count == 1
|
||||
|
||||
release.set()
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
schedule_location_update(coordinator)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# A new schedule should run after the previous task has finished.
|
||||
assert mock_api.async_get_location.call_count == 2
|
||||
|
||||
|
||||
def _mock_api_failure(mock_api: VolvoCarsApi) -> AsyncMock:
|
||||
"""Mock the Volvo API so that it raises an exception for all calls."""
|
||||
|
||||
@@ -179,3 +417,14 @@ def _mock_api_failure(mock_api: VolvoCarsApi) -> AsyncMock:
|
||||
mock_api.async_get_window_states.side_effect = VolvoApiException()
|
||||
|
||||
return mock_api
|
||||
|
||||
|
||||
def _get_doors_data(
|
||||
mock_api: VolvoCarsApi, lock_value: str
|
||||
) -> dict[str, VolvoCarsValueField]:
|
||||
"""Build doors data with a specific centralLock value."""
|
||||
# Reuse the structure from the original mock but override centralLock
|
||||
original = mock_api.async_get_doors_status.return_value
|
||||
result = dict(original)
|
||||
result["centralLock"] = VolvoCarsValueField(value=lock_value)
|
||||
return result
|
||||
|
||||
@@ -162,3 +162,51 @@ async def test_lock_unavailable_when_api_field_missing(
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("full_model")
|
||||
async def test_ha_lock_triggers_location_update(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that locking via HA triggers a location update."""
|
||||
|
||||
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.LOCK]):
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN,
|
||||
SERVICE_LOCK,
|
||||
{ATTR_ENTITY_ID: "lock.volvo_xc40_lock"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count > location_call_count_before
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("full_model")
|
||||
async def test_ha_unlock_does_not_trigger_location_update(
|
||||
hass: HomeAssistant,
|
||||
setup_integration: Callable[[], Awaitable[bool]],
|
||||
mock_api: VolvoCarsApi,
|
||||
) -> None:
|
||||
"""Test that unlocking via HA does not trigger a location update."""
|
||||
|
||||
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.LOCK]):
|
||||
assert await setup_integration()
|
||||
|
||||
location_call_count_before: int = mock_api.async_get_location.call_count
|
||||
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN,
|
||||
SERVICE_UNLOCK,
|
||||
{ATTR_ENTITY_ID: "lock.volvo_xc40_lock"},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api.async_get_location.call_count == location_call_count_before
|
||||
|
||||
Reference in New Issue
Block a user