Code quality improvements for Teslemetry (#123444)

pull/126917/head
Brett Adams 2024-09-27 23:06:09 +10:00 committed by GitHub
parent 66ab90b518
commit cad87f51a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 218 additions and 124 deletions

View File

@ -107,6 +107,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
device=device,
)
)
elif "energy_site_id" in product and Scope.ENERGY_DEVICE_DATA in scopes:
site_id = product["energy_site_id"]
if not (

View File

@ -120,7 +120,8 @@ class TeslemetryClimateEntity(TeslemetryVehicleEntity, ClimateEntity):
async def async_turn_on(self) -> None:
"""Set the climate state to on."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.auto_conditioning_start())
@ -129,7 +130,8 @@ class TeslemetryClimateEntity(TeslemetryVehicleEntity, ClimateEntity):
async def async_turn_off(self) -> None:
"""Set the climate state to off."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.auto_conditioning_stop())
@ -261,10 +263,11 @@ class TeslemetryCabinOverheatProtectionEntity(TeslemetryVehicleEntity, ClimateEn
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set the climate temperature."""
if not (temp := kwargs.get(ATTR_TEMPERATURE)):
return
self.raise_for_scope(Scope.VEHICLE_CMDS)
if (cop_mode := TEMP_LEVELS.get(temp)) is None:
if (temp := kwargs.get(ATTR_TEMPERATURE)) is None or (
cop_mode := TEMP_LEVELS.get(temp)
) is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_cop_temp",
@ -297,7 +300,7 @@ class TeslemetryCabinOverheatProtectionEntity(TeslemetryVehicleEntity, ClimateEn
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set the climate mode and state."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await self._async_set_cop(hvac_mode)
self.async_write_ha_state()

View File

@ -52,7 +52,6 @@ class TeslemetryVehicleDataCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching data from the Teslemetry API."""
updated_once: bool
pre2021: bool
last_active: datetime
def __init__(

View File

@ -79,7 +79,7 @@ class TeslemetryWindowEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Vent windows."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(
self.api.window_control(command=WindowCommand.VENT)
@ -89,7 +89,7 @@ class TeslemetryWindowEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close windows."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(
self.api.window_control(command=WindowCommand.CLOSE)
@ -122,7 +122,7 @@ class TeslemetryChargePortEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open charge port."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.charge_port_door_open())
self._attr_is_closed = False
@ -130,7 +130,7 @@ class TeslemetryChargePortEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close charge port."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.charge_port_door_close())
self._attr_is_closed = True
@ -157,7 +157,7 @@ class TeslemetryFrontTrunkEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open front trunk."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.actuate_trunk(Trunk.FRONT))
self._attr_is_closed = False
@ -193,7 +193,7 @@ class TeslemetryRearTrunkEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open rear trunk."""
if self.is_closed is not False:
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.actuate_trunk(Trunk.REAR))
self._attr_is_closed = False
@ -202,7 +202,7 @@ class TeslemetryRearTrunkEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close rear trunk."""
if self.is_closed is not True:
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.actuate_trunk(Trunk.REAR))
self._attr_is_closed = True
@ -240,7 +240,7 @@ class TeslemetrySunroofEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open sunroof."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.VENT))
self._attr_is_closed = False
@ -248,7 +248,7 @@ class TeslemetrySunroofEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close sunroof."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.CLOSE))
self._attr_is_closed = True
@ -256,7 +256,7 @@ class TeslemetrySunroofEntity(TeslemetryVehicleEntity, CoverEntity):
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Close sunroof."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.sun_roof_control(SunRoofCommand.STOP))
self._attr_is_closed = False

View File

@ -4,6 +4,7 @@ from abc import abstractmethod
from typing import Any
from tesla_fleet_api import EnergySpecific, VehicleSpecific
from tesla_fleet_api.const import Scope
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.device_registry import DeviceInfo
@ -31,6 +32,7 @@ class TeslemetryEntity(
"""Parent class for all Teslemetry entities."""
_attr_has_entity_name = True
scoped: bool
def __init__(
self,
@ -38,12 +40,10 @@ class TeslemetryEntity(
| TeslemetryEnergyHistoryCoordinator
| TeslemetryEnergySiteLiveCoordinator
| TeslemetryEnergySiteInfoCoordinator,
api: VehicleSpecific | EnergySpecific,
key: str,
) -> None:
"""Initialize common aspects of a Teslemetry entity."""
super().__init__(coordinator)
self.api = api
self.key = key
self._attr_translation_key = self.key
self._async_update_attrs()
@ -87,16 +87,22 @@ class TeslemetryEntity(
def _async_update_attrs(self) -> None:
"""Update the attributes of the entity."""
def raise_for_scope(self):
def raise_for_scope(self, scope: Scope):
"""Raise an error if a scope is not available."""
if not self.scoped:
raise ServiceValidationError("Missing required scope")
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="missing_scope",
translation_placeholders={"scope": scope},
)
class TeslemetryVehicleEntity(TeslemetryEntity):
"""Parent class for Teslemetry Vehicle entities."""
_last_update: int = 0
api: VehicleSpecific
vehicle: TeslemetryVehicleData
def __init__(
self,
@ -105,11 +111,11 @@ class TeslemetryVehicleEntity(TeslemetryEntity):
) -> None:
"""Initialize common aspects of a Teslemetry entity."""
self._attr_unique_id = f"{data.vin}-{key}"
self.api = data.api
self.vehicle = data
self._attr_unique_id = f"{data.vin}-{key}"
self._attr_device_info = data.device
super().__init__(data.coordinator, data.api, key)
super().__init__(data.coordinator, key)
@property
def _value(self) -> Any | None:
@ -124,31 +130,39 @@ class TeslemetryVehicleEntity(TeslemetryEntity):
class TeslemetryEnergyLiveEntity(TeslemetryEntity):
"""Parent class for Teslemetry Energy Site Live entities."""
api: EnergySpecific
def __init__(
self,
data: TeslemetryEnergyData,
key: str,
) -> None:
"""Initialize common aspects of a Teslemetry Energy Site Live entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
super().__init__(data.live_coordinator, data.api, key)
super().__init__(data.live_coordinator, key)
class TeslemetryEnergyInfoEntity(TeslemetryEntity):
"""Parent class for Teslemetry Energy Site Info Entities."""
api: EnergySpecific
def __init__(
self,
data: TeslemetryEnergyData,
key: str,
) -> None:
"""Initialize common aspects of a Teslemetry Energy Site Info entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
super().__init__(data.info_coordinator, data.api, key)
super().__init__(data.info_coordinator, key)
class TeslemetryEnergyHistoryEntity(TeslemetryEntity):
@ -160,18 +174,19 @@ class TeslemetryEnergyHistoryEntity(TeslemetryEntity):
key: str,
) -> None:
"""Initialize common aspects of a Teslemetry Energy Site Info entity."""
self.api = data.api
self._attr_unique_id = f"{data.id}-{key}"
self._attr_device_info = data.device
super().__init__(data.history_coordinator, data.api, key)
super().__init__(data.history_coordinator, key)
class TeslemetryWallConnectorEntity(
TeslemetryEntity, CoordinatorEntity[TeslemetryEnergySiteLiveCoordinator]
):
class TeslemetryWallConnectorEntity(TeslemetryEntity):
"""Parent class for Teslemetry Wall Connector Entities."""
_attr_has_entity_name = True
api: EnergySpecific
def __init__(
self,
@ -180,6 +195,8 @@ class TeslemetryWallConnectorEntity(
key: str,
) -> None:
"""Initialize common aspects of a Teslemetry entity."""
self.api = data.api
self.din = din
self._attr_unique_id = f"{data.id}-{din}-{key}"
@ -200,7 +217,7 @@ class TeslemetryWallConnectorEntity(
model=model,
)
super().__init__(data.live_coordinator, data.api, key)
super().__init__(data.live_coordinator, key)
@property
def _value(self) -> int:

View File

@ -7,7 +7,7 @@ from tesla_fleet_api.exceptions import TeslaFleetError
from homeassistant.exceptions import HomeAssistantError
from .const import LOGGER, TeslemetryState
from .const import DOMAIN, LOGGER, TeslemetryState
async def wake_up_vehicle(vehicle) -> None:
@ -22,12 +22,19 @@ async def wake_up_vehicle(vehicle) -> None:
cmd = await vehicle.api.vehicle()
state = cmd["response"]["state"]
except TeslaFleetError as e:
raise HomeAssistantError(str(e)) from e
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="wake_up_failed",
translation_placeholders={"message": e.message},
) from e
vehicle.coordinator.data["state"] = state
if state != TeslemetryState.ONLINE:
times += 1
if times >= 4: # Give up after 30 seconds total
raise HomeAssistantError("Could not wake up vehicle")
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="wake_up_timeout",
)
await asyncio.sleep(times * 5)
@ -36,18 +43,26 @@ async def handle_command(command) -> dict[str, Any]:
try:
result = await command
except TeslaFleetError as e:
raise HomeAssistantError(f"Teslemetry command failed, {e.message}") from e
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_exception",
translation_placeholders={"message": e.message},
) from e
LOGGER.debug("Command result: %s", result)
return result
async def handle_vehicle_command(command) -> dict[str, Any]:
async def handle_vehicle_command(command) -> Any:
"""Handle a vehicle command."""
result = await handle_command(command)
if (response := result.get("response")) is None:
if error := result.get("error"):
# No response with error
raise HomeAssistantError(error)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_error",
translation_placeholders={"error": error},
)
# No response without error (unexpected)
raise HomeAssistantError(f"Unknown response: {response}")
if (result := response.get("result")) is not True:
@ -56,8 +71,14 @@ async def handle_vehicle_command(command) -> dict[str, Any]:
# Reason is acceptable
return result
# Result of false with reason
raise HomeAssistantError(reason)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_reason",
translation_placeholders={"reason": reason},
)
# Result of false without reason (unexpected)
raise HomeAssistantError("Command failed with no reason")
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="command_no_result"
)
# Response with result of true
return result

View File

@ -53,7 +53,7 @@ class TeslemetryVehicleLockEntity(TeslemetryVehicleEntity, LockEntity):
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the doors."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.door_lock())
self._attr_is_locked = True
@ -61,7 +61,7 @@ class TeslemetryVehicleLockEntity(TeslemetryVehicleEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the doors."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.door_unlock())
self._attr_is_locked = False
@ -96,7 +96,7 @@ class TeslemetryCableLockEntity(TeslemetryVehicleEntity, LockEntity):
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock charge cable lock."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.charge_port_door_open())
self._attr_is_locked = False

View File

@ -115,7 +115,7 @@ class TeslemetryMediaEntity(TeslemetryVehicleEntity, MediaPlayerEntity):
async def async_set_volume_level(self, volume: float) -> None:
"""Set volume level, range 0..1."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(
self.api.adjust_volume(int(volume * self._volume_max))
@ -126,7 +126,7 @@ class TeslemetryMediaEntity(TeslemetryVehicleEntity, MediaPlayerEntity):
async def async_media_play(self) -> None:
"""Send play command."""
if self.state != MediaPlayerState.PLAYING:
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.media_toggle_playback())
self._attr_state = MediaPlayerState.PLAYING
@ -135,7 +135,7 @@ class TeslemetryMediaEntity(TeslemetryVehicleEntity, MediaPlayerEntity):
async def async_media_pause(self) -> None:
"""Send pause command."""
if self.state == MediaPlayerState.PLAYING:
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.media_toggle_playback())
self._attr_state = MediaPlayerState.PAUSED
@ -143,12 +143,12 @@ class TeslemetryMediaEntity(TeslemetryVehicleEntity, MediaPlayerEntity):
async def async_media_next_track(self) -> None:
"""Send next track command."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.media_next_track())
async def async_media_previous_track(self) -> None:
"""Send previous track command."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.media_prev_track())

View File

@ -164,7 +164,7 @@ class TeslemetryVehicleNumberEntity(TeslemetryVehicleEntity, NumberEntity):
async def async_set_native_value(self, value: float) -> None:
"""Set new value."""
value = int(value)
self.raise_for_scope()
self.raise_for_scope(self.entity_description.scopes[0])
await self.wake_up_if_asleep()
await handle_vehicle_command(self.entity_description.func(self.api, value))
self._attr_native_value = value
@ -200,7 +200,7 @@ class TeslemetryEnergyInfoNumberSensorEntity(TeslemetryEnergyInfoEntity, NumberE
async def async_set_native_value(self, value: float) -> None:
"""Set new value."""
value = int(value)
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(self.entity_description.func(self.api, value))
self._attr_native_value = value
self.async_write_ha_state()

View File

@ -144,7 +144,7 @@ class TeslemetrySeatHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
level = self._attr_options.index(option)
# AC must be on to turn on seat heater
@ -189,7 +189,7 @@ class TeslemetryWheelHeaterSelectEntity(TeslemetryVehicleEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
self.raise_for_scope()
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
level = self._attr_options.index(option)
# AC must be on to turn on steering wheel heater
@ -226,7 +226,7 @@ class TeslemetryOperationSelectEntity(TeslemetryEnergyInfoEntity, SelectEntity):
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(self.api.operation(option))
self._attr_current_option = option
self.async_write_ha_state()
@ -256,7 +256,7 @@ class TeslemetryExportRuleSelectEntity(TeslemetryEnergyInfoEntity, SelectEntity)
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(
self.api.grid_import_export(customer_preferred_export_rule=option)
)

View File

@ -567,8 +567,26 @@
"no_energy_site_data_for_device": {
"message": "No energy site data for device ID: {device_id}"
},
"command_exception": {
"message": "Command returned exception: {message}"
},
"command_error": {
"message": "Command returned error: {error}"
},
"command_reason": {
"message": "Command was rejected: {reason}"
},
"command_no_result": {
"message": "Command had no result"
},
"wake_up_failed": {
"message": "Failed to wake up vehicle: {message}"
},
"wake_up_timeout": {
"message": "Timed out trying to wake up vehicle"
},
"missing_scope": {
"message": "Missing required scope: {scope}"
}
},
"services": {

View File

@ -157,7 +157,7 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
self.raise_for_scope()
self.raise_for_scope(self.entity_description.scopes[0])
await self.wake_up_if_asleep()
await handle_vehicle_command(self.entity_description.on_func(self.api))
self._attr_is_on = True
@ -165,7 +165,7 @@ class TeslemetryVehicleSwitchEntity(TeslemetryVehicleEntity, TeslemetrySwitchEnt
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the Switch."""
self.raise_for_scope()
self.raise_for_scope(self.entity_description.scopes[0])
await self.wake_up_if_asleep()
await handle_vehicle_command(self.entity_description.off_func(self.api))
self._attr_is_on = False
@ -207,7 +207,7 @@ class TeslemetryChargeFromGridSwitchEntity(
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(
self.api.grid_import_export(
disallow_charge_from_grid_with_solar_installed=False
@ -218,7 +218,7 @@ class TeslemetryChargeFromGridSwitchEntity(
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the Switch."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(
self.api.grid_import_export(
disallow_charge_from_grid_with_solar_installed=True
@ -249,14 +249,14 @@ class TeslemetryStormModeSwitchEntity(
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(self.api.storm_mode(enabled=True))
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the Switch."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await handle_command(self.api.storm_mode(enabled=False))
self._attr_is_on = False
self.async_write_ha_state()

View File

@ -103,7 +103,7 @@ class TeslemetryUpdateEntity(TeslemetryVehicleEntity, UpdateEntity):
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
self.raise_for_scope()
self.raise_for_scope(Scope.ENERGY_CMDS)
await self.wake_up_if_asleep()
await handle_vehicle_command(self.api.schedule_software_update(offset_sec=60))
self._attr_in_progress = True

View File

@ -2,7 +2,7 @@
from unittest.mock import patch
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.teslemetry.const import DOMAIN
from homeassistant.const import Platform

View File

@ -1,4 +1,10 @@
# serializer version: 1
# name: test_asleep_or_offline[HomeAssistantError]
'Timed out trying to wake up vehicle'
# ---
# name: test_asleep_or_offline[InvalidCommand]
'Failed to wake up vehicle: The data request or command is unknown.'
# ---
# name: test_climate[climate.test_cabin_overheat_protection-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@ -499,3 +505,6 @@
'state': 'unknown',
})
# ---
# name: test_invalid_error[error]
'Command returned exception: The data request or command is unknown.'
# ---

View File

@ -1,8 +1,10 @@
"""Test the Teslemetry binary sensor platform."""
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
@ -33,7 +35,7 @@ async def test_binary_sensor_refresh(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Tests that the binary sensor entities are correct."""
@ -51,7 +53,7 @@ async def test_binary_sensor_refresh(
async def test_binary_sensor_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the binary sensor entities are correct when offline."""

View File

@ -3,7 +3,7 @@
from unittest.mock import patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID, Platform

View File

@ -4,7 +4,7 @@ from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import InvalidCommand, VehicleOffline
from homeassistant.components.climate import (
@ -196,7 +196,7 @@ async def test_climate_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the climate entity is correct."""
@ -210,7 +210,7 @@ async def test_climate_offline(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the climate entity is correct."""
@ -219,7 +219,7 @@ async def test_climate_offline(
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
async def test_invalid_error(hass: HomeAssistant) -> None:
async def test_invalid_error(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
"""Tests service error is handled."""
await setup_platform(hass, platforms=[Platform.CLIMATE])
@ -239,10 +239,7 @@ async def test_invalid_error(hass: HomeAssistant) -> None:
blocking=True,
)
mock_on.assert_called_once()
assert (
str(error.value)
== "Teslemetry command failed, The data request or command is unknown."
)
assert str(error.value) == snapshot(name="error")
@pytest.mark.parametrize("response", COMMAND_ERRORS)
@ -291,10 +288,11 @@ async def test_ignored_error(
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_asleep_or_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_wake_up,
mock_vehicle,
mock_vehicle_data: AsyncMock,
mock_wake_up: AsyncMock,
mock_vehicle: AsyncMock,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None:
"""Tests asleep is handled."""
@ -320,7 +318,7 @@ async def test_asleep_or_offline(
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
assert str(error.value) == "The data request or command is unknown."
assert str(error.value) == snapshot(name="InvalidCommand")
mock_wake_up.assert_called_once()
mock_wake_up.side_effect = None
@ -339,7 +337,7 @@ async def test_asleep_or_offline(
{ATTR_ENTITY_ID: [entity_id]},
blocking=True,
)
assert str(error.value) == "Could not wake up vehicle"
assert str(error.value) == snapshot(name="HomeAssistantError")
mock_wake_up.assert_called_once()
mock_vehicle.assert_called()

View File

@ -1,6 +1,6 @@
"""Test the Teslemetry config flow."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from aiohttp import ClientConnectionError
import pytest
@ -60,7 +60,10 @@ async def test_form(
],
)
async def test_form_errors(
hass: HomeAssistant, side_effect, error, mock_metadata
hass: HomeAssistant,
side_effect: TeslaFleetError,
error: dict[str, str],
mock_metadata: AsyncMock,
) -> None:
"""Test errors are handled."""
@ -86,7 +89,7 @@ async def test_form_errors(
assert result3["type"] is FlowResultType.CREATE_ENTRY
async def test_reauth(hass: HomeAssistant, mock_metadata) -> None:
async def test_reauth(hass: HomeAssistant, mock_metadata: AsyncMock) -> None:
"""Test reauth flow."""
mock_entry = MockConfigEntry(
@ -127,7 +130,10 @@ async def test_reauth(hass: HomeAssistant, mock_metadata) -> None:
],
)
async def test_reauth_errors(
hass: HomeAssistant, mock_metadata, side_effect, error
hass: HomeAssistant,
mock_metadata: AsyncMock,
side_effect: TeslaFleetError,
error: dict[str, str],
) -> None:
"""Test reauth flows that fail."""
@ -178,7 +184,7 @@ async def test_unique_id_abort(
assert result2["type"] is FlowResultType.ABORT
async def test_migrate_from_1_1(hass: HomeAssistant, mock_metadata) -> None:
async def test_migrate_from_1_1(hass: HomeAssistant, mock_metadata: AsyncMock) -> None:
"""Test config migration."""
mock_entry = MockConfigEntry(
@ -199,7 +205,9 @@ async def test_migrate_from_1_1(hass: HomeAssistant, mock_metadata) -> None:
assert entry.unique_id == METADATA["uid"]
async def test_migrate_error_from_1_1(hass: HomeAssistant, mock_metadata) -> None:
async def test_migrate_error_from_1_1(
hass: HomeAssistant, mock_metadata: AsyncMock
) -> None:
"""Test config migration handles errors."""
mock_metadata.side_effect = TeslaFleetError
@ -220,7 +228,9 @@ async def test_migrate_error_from_1_1(hass: HomeAssistant, mock_metadata) -> Non
assert entry.state is ConfigEntryState.MIGRATION_ERROR
async def test_migrate_error_from_future(hass: HomeAssistant, mock_metadata) -> None:
async def test_migrate_error_from_future(
hass: HomeAssistant, mock_metadata: AsyncMock
) -> None:
"""Test a future version isn't migrated."""
mock_metadata.side_effect = TeslaFleetError

View File

@ -1,9 +1,9 @@
"""Test the Teslemetry cover platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.cover import (
@ -43,7 +43,7 @@ async def test_cover_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the cover entities are correct with alternate values."""
@ -57,7 +57,7 @@ async def test_cover_noscope(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_metadata,
mock_metadata: AsyncMock,
) -> None:
"""Tests that the cover entities are correct without scopes."""
@ -68,7 +68,7 @@ async def test_cover_noscope(
async def test_cover_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the cover entities are correct when offline."""

View File

@ -1,6 +1,6 @@
"""Test the Teslemetry device tracker platform."""
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.const import STATE_UNKNOWN, Platform

View File

@ -4,7 +4,7 @@ from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import (
InvalidToken,
SubscriptionRequired,
@ -48,7 +48,10 @@ async def test_load_unload(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
async def test_init_error(
hass: HomeAssistant, mock_products, side_effect, state
hass: HomeAssistant,
mock_products: AsyncMock,
side_effect: TeslaFleetError,
state: ConfigEntryState,
) -> None:
"""Test init with errors."""
@ -86,7 +89,7 @@ async def test_vehicle_refresh_asleep(
async def test_vehicle_refresh_offline(
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
hass: HomeAssistant, mock_vehicle_data: AsyncMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test coordinator refresh with an error."""
entry = await setup_platform(hass, [Platform.CLIMATE])
@ -103,7 +106,10 @@ async def test_vehicle_refresh_offline(
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
async def test_vehicle_refresh_error(
hass: HomeAssistant, mock_vehicle_data, side_effect, state
hass: HomeAssistant,
mock_vehicle_data: AsyncMock,
side_effect: TeslaFleetError,
state: ConfigEntryState,
) -> None:
"""Test coordinator refresh with an error."""
mock_vehicle_data.side_effect = side_effect
@ -112,7 +118,7 @@ async def test_vehicle_refresh_error(
async def test_vehicle_sleep(
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
hass: HomeAssistant, mock_vehicle_data: AsyncMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test coordinator refresh with an error."""
await setup_platform(hass, [Platform.CLIMATE])
@ -171,7 +177,10 @@ async def test_vehicle_sleep(
# Test Energy Live Coordinator
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
async def test_energy_live_refresh_error(
hass: HomeAssistant, mock_live_status, side_effect, state
hass: HomeAssistant,
mock_live_status: AsyncMock,
side_effect: TeslaFleetError,
state: ConfigEntryState,
) -> None:
"""Test coordinator refresh with an error."""
mock_live_status.side_effect = side_effect
@ -182,7 +191,10 @@ async def test_energy_live_refresh_error(
# Test Energy Site Coordinator
@pytest.mark.parametrize(("side_effect", "state"), ERRORS)
async def test_energy_site_refresh_error(
hass: HomeAssistant, mock_site_info, side_effect, state
hass: HomeAssistant,
mock_site_info: AsyncMock,
side_effect: TeslaFleetError,
state: ConfigEntryState,
) -> None:
"""Test coordinator refresh with an error."""
mock_site_info.side_effect = side_effect

View File

@ -1,9 +1,9 @@
"""Test the Teslemetry lock platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.lock import (
@ -34,7 +34,7 @@ async def test_lock(
async def test_lock_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the lock entities are correct when offline."""

View File

@ -1,8 +1,8 @@
"""Test the Teslemetry media player platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.media_player import (
@ -38,7 +38,7 @@ async def test_media_player_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the media player entities are correct."""
@ -49,7 +49,7 @@ async def test_media_player_alt(
async def test_media_player_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the media player entities are correct when offline."""
@ -63,7 +63,7 @@ async def test_media_player_noscope(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_metadata,
mock_metadata: AsyncMock,
) -> None:
"""Tests that the media player entities are correct without required scope."""

View File

@ -1,9 +1,9 @@
"""Test the Teslemetry number platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.number import (
@ -33,7 +33,7 @@ async def test_number(
async def test_number_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the number entities are correct when offline."""
@ -44,7 +44,9 @@ async def test_number_offline(
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_number_services(hass: HomeAssistant, mock_vehicle_data) -> None:
async def test_number_services(
hass: HomeAssistant, mock_vehicle_data: AsyncMock
) -> None:
"""Tests that the number services work."""
mock_vehicle_data.return_value = VEHICLE_DATA_ALT
await setup_platform(hass, [Platform.NUMBER])

View File

@ -1,9 +1,9 @@
"""Test the Teslemetry select platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.const import EnergyExportMode, EnergyOperationMode
from tesla_fleet_api.exceptions import VehicleOffline
@ -35,7 +35,7 @@ async def test_select(
async def test_select_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the select entities are correct when offline."""

View File

@ -1,8 +1,10 @@
"""Test the Teslemetry sensor platform."""
from unittest.mock import AsyncMock
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
from homeassistant.const import Platform
@ -21,7 +23,7 @@ async def test_sensors(
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the sensor entities are correct."""

View File

@ -1,9 +1,9 @@
"""Test the Teslemetry switch platform."""
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
import pytest
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.switch import (
@ -40,7 +40,7 @@ async def test_switch_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the switch entities are correct."""
@ -51,7 +51,7 @@ async def test_switch_alt(
async def test_switch_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the switch entities are correct when offline."""

View File

@ -1,10 +1,10 @@
"""Test the Teslemetry update platform."""
import copy
from unittest.mock import patch
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion
from syrupy.assertion import SnapshotAssertion
from tesla_fleet_api.exceptions import VehicleOffline
from homeassistant.components.teslemetry.coordinator import VEHICLE_INTERVAL
@ -35,7 +35,7 @@ async def test_update_alt(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the update entities are correct."""
@ -46,7 +46,7 @@ async def test_update_alt(
async def test_update_offline(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
) -> None:
"""Tests that the update entities are correct when offline."""
@ -58,7 +58,7 @@ async def test_update_offline(
async def test_update_services(
hass: HomeAssistant,
mock_vehicle_data,
mock_vehicle_data: AsyncMock,
freezer: FrozenDateTimeFactory,
snapshot: SnapshotAssertion,
) -> None: