1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 00:20:30 +01:00
Files
core/tests/components/autoskope/test_device_tracker.py
mcisk b6c7b2952e Add autoskope integration (#146772)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 19:19:00 +01:00

233 lines
7.5 KiB
Python

"""Test Autoskope device tracker."""
from unittest.mock import AsyncMock
from autoskope_client.models import CannotConnect, InvalidAuth, Vehicle, VehiclePosition
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.autoskope.const import DOMAIN, UPDATE_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
async def test_all_entities(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test all entities with snapshot."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("speed", "park_mode", "has_position", "expected_icon"),
[
(50, False, True, "mdi:car-arrow-right"),
(0, True, True, "mdi:car-brake-parking"),
(2, False, True, "mdi:car"),
(0, False, False, "mdi:car-clock"),
],
ids=["moving", "parked", "idle", "no_position"],
)
async def test_vehicle_icons(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
speed: int,
park_mode: bool,
has_position: bool,
expected_icon: str,
) -> None:
"""Test device tracker icon for different vehicle states."""
position = (
VehiclePosition(
latitude=50.1109221,
longitude=8.6821267,
speed=speed,
timestamp="2025-05-28T10:00:00Z",
park_mode=park_mode,
)
if has_position
else None
)
mock_autoskope_client.get_vehicles.return_value = [
Vehicle(
id="12345",
name="Test Vehicle",
position=position,
external_voltage=12.5,
battery_voltage=3.7,
gps_quality=1.2,
imei="123456789012345",
model="Autoskope",
)
]
await setup_integration(hass, mock_config_entry)
state = hass.states.get("device_tracker.test_vehicle")
assert state is not None
assert state.attributes["icon"] == expected_icon
async def test_entity_unavailable_on_coordinator_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test entity becomes unavailable when coordinator update fails."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("device_tracker.test_vehicle")
assert state is not None
assert state.state != STATE_UNAVAILABLE
# Simulate connection error on next update
mock_autoskope_client.get_vehicles.side_effect = CannotConnect("Connection lost")
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("device_tracker.test_vehicle")
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_entity_recovers_after_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
mock_vehicles: list[Vehicle],
freezer: FrozenDateTimeFactory,
) -> None:
"""Test entity recovers after a transient coordinator error."""
await setup_integration(hass, mock_config_entry)
# Simulate error
mock_autoskope_client.get_vehicles.side_effect = CannotConnect("Connection lost")
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("device_tracker.test_vehicle").state == STATE_UNAVAILABLE
# Recover
mock_autoskope_client.get_vehicles.side_effect = None
mock_autoskope_client.get_vehicles.return_value = mock_vehicles
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("device_tracker.test_vehicle")
assert state.state != STATE_UNAVAILABLE
async def test_reauth_success(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
mock_vehicles: list[Vehicle],
freezer: FrozenDateTimeFactory,
) -> None:
"""Test entity stays available after successful re-authentication."""
await setup_integration(hass, mock_config_entry)
# First get_vehicles raises InvalidAuth, retry after authenticate succeeds
mock_autoskope_client.get_vehicles.side_effect = [
InvalidAuth("Token expired"),
mock_vehicles,
]
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("device_tracker.test_vehicle")
assert state is not None
assert state.state != STATE_UNAVAILABLE
async def test_reauth_failure(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
mock_vehicles: list[Vehicle],
freezer: FrozenDateTimeFactory,
) -> None:
"""Test entity becomes unavailable on permanent auth failure."""
await setup_integration(hass, mock_config_entry)
# get_vehicles raises InvalidAuth, and re-authentication also fails
mock_autoskope_client.get_vehicles.side_effect = InvalidAuth("Token expired")
mock_autoskope_client.authenticate.side_effect = InvalidAuth("Invalid credentials")
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
state = hass.states.get("device_tracker.test_vehicle")
assert state is not None
assert state.state == STATE_UNAVAILABLE
# Clean up side effects to prevent teardown errors
mock_autoskope_client.get_vehicles.side_effect = None
mock_autoskope_client.authenticate.side_effect = None
mock_autoskope_client.get_vehicles.return_value = mock_vehicles
async def test_vehicle_name_update(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_autoskope_client: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test device name updates in device registry when vehicle is renamed."""
await setup_integration(hass, mock_config_entry)
device_registry = dr.async_get(hass)
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")})
assert device_entry is not None
assert device_entry.name == "Test Vehicle"
# Simulate vehicle rename on Autoskope side
mock_autoskope_client.get_vehicles.return_value = [
Vehicle(
id="12345",
name="Renamed Vehicle",
position=VehiclePosition(
latitude=50.1109221,
longitude=8.6821267,
speed=0,
timestamp="2025-05-28T10:00:00Z",
park_mode=True,
),
external_voltage=12.5,
battery_voltage=3.7,
gps_quality=1.2,
imei="123456789012345",
model="Autoskope",
)
]
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Device registry should reflect the new name
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "12345")})
assert device_entry is not None
assert device_entry.name == "Renamed Vehicle"