mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Add tests to Teslemetry (#108884)
* Add tests * Add partial sleep test * Remove useless AsyncMock * Review feedback * Patch imports * Fix mock_test
This commit is contained in:
parent
a01e73a575
commit
b28e8a3cf0
@ -1371,11 +1371,6 @@ omit =
|
|||||||
homeassistant/components/telnet/switch.py
|
homeassistant/components/telnet/switch.py
|
||||||
homeassistant/components/temper/sensor.py
|
homeassistant/components/temper/sensor.py
|
||||||
homeassistant/components/tensorflow/image_processing.py
|
homeassistant/components/tensorflow/image_processing.py
|
||||||
homeassistant/components/teslemetry/__init__.py
|
|
||||||
homeassistant/components/teslemetry/climate.py
|
|
||||||
homeassistant/components/teslemetry/coordinator.py
|
|
||||||
homeassistant/components/teslemetry/entity.py
|
|
||||||
homeassistant/components/teslemetry/context.py
|
|
||||||
homeassistant/components/tfiac/climate.py
|
homeassistant/components/tfiac/climate.py
|
||||||
homeassistant/components/thermoworks_smoke/sensor.py
|
homeassistant/components/thermoworks_smoke/sensor.py
|
||||||
homeassistant/components/thethingsnetwork/*
|
homeassistant/components/thethingsnetwork/*
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from tesla_fleet_api import Teslemetry
|
from tesla_fleet_api import Teslemetry, VehicleSpecific
|
||||||
from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError
|
from tesla_fleet_api.exceptions import InvalidToken, PaymentRequired, TeslaFleetError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
continue
|
continue
|
||||||
vin = product["vin"]
|
vin = product["vin"]
|
||||||
|
|
||||||
api = teslemetry.vehicle.specific(vin)
|
api = VehicleSpecific(teslemetry.vehicle, vin)
|
||||||
coordinator = TeslemetryVehicleDataCoordinator(hass, api)
|
coordinator = TeslemetryVehicleDataCoordinator(hass, api)
|
||||||
data.append(
|
data.append(
|
||||||
TeslemetryVehicleData(
|
TeslemetryVehicleData(
|
||||||
|
@ -13,4 +13,4 @@ def handle_command():
|
|||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except TeslaFleetError as e:
|
except TeslaFleetError as e:
|
||||||
raise HomeAssistantError from e
|
raise HomeAssistantError("Teslemetry command failed") from e
|
||||||
|
@ -1 +1,50 @@
|
|||||||
"""Tests for the Teslemetry integration."""
|
"""Tests for the Teslemetry integration."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.const import DOMAIN
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from .const import CONFIG
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_platform(hass: HomeAssistant, platforms: list[Platform] | None = None):
|
||||||
|
"""Set up the Teslemetry platform."""
|
||||||
|
|
||||||
|
mock_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=CONFIG,
|
||||||
|
)
|
||||||
|
mock_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
if platforms is None:
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
else:
|
||||||
|
with patch("homeassistant.components.teslemetry.PLATFORMS", platforms):
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return mock_entry
|
||||||
|
|
||||||
|
|
||||||
|
def assert_entities(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry_id: str,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Test that all entities match their snapshot."""
|
||||||
|
entity_entries = er.async_entries_for_config_entry(entity_registry, entry_id)
|
||||||
|
|
||||||
|
assert entity_entries
|
||||||
|
for entity_entry in entity_entries:
|
||||||
|
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
|
||||||
|
assert (state := hass.states.get(entity_entry.entity_id))
|
||||||
|
assert state == snapshot(name=f"{entity_entry.entity_id}-state")
|
||||||
|
47
tests/components/teslemetry/conftest.py
Normal file
47
tests/components/teslemetry/conftest.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""Fixtures for Tessie."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .const import PRODUCTS, RESPONSE_OK, VEHICLE_DATA, WAKE_UP_ONLINE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_products():
|
||||||
|
"""Mock Tesla Fleet Api products method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.Teslemetry.products", return_value=PRODUCTS
|
||||||
|
) as mock_products:
|
||||||
|
yield mock_products
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_vehicle_data():
|
||||||
|
"""Mock Tesla Fleet API Vehicle Specific vehicle_data method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.VehicleSpecific.vehicle_data",
|
||||||
|
return_value=VEHICLE_DATA,
|
||||||
|
) as mock_vehicle_data:
|
||||||
|
yield mock_vehicle_data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_wake_up():
|
||||||
|
"""Mock Tesla Fleet API Vehicle Specific wake_up method."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.VehicleSpecific.wake_up",
|
||||||
|
return_value=WAKE_UP_ONLINE,
|
||||||
|
) as mock_wake_up:
|
||||||
|
yield mock_wake_up
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_request():
|
||||||
|
"""Mock Tesla Fleet API Vehicle Specific class."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.Teslemetry._request",
|
||||||
|
return_value=RESPONSE_OK,
|
||||||
|
) as mock_request:
|
||||||
|
yield mock_request
|
@ -1,5 +1,16 @@
|
|||||||
"""Constants for the teslemetry tests."""
|
"""Constants for the teslemetry tests."""
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.const import DOMAIN, TeslemetryState
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||||
|
|
||||||
|
from tests.common import load_json_object_fixture
|
||||||
|
|
||||||
CONFIG = {CONF_ACCESS_TOKEN: "1234567890"}
|
CONFIG = {CONF_ACCESS_TOKEN: "1234567890"}
|
||||||
|
|
||||||
|
WAKE_UP_ONLINE = {"response": {"state": TeslemetryState.ONLINE}, "error": None}
|
||||||
|
WAKE_UP_ASLEEP = {"response": {"state": TeslemetryState.ASLEEP}, "error": None}
|
||||||
|
|
||||||
|
PRODUCTS = load_json_object_fixture("products.json", DOMAIN)
|
||||||
|
VEHICLE_DATA = load_json_object_fixture("vehicle_data.json", DOMAIN)
|
||||||
|
|
||||||
|
RESPONSE_OK = {"response": {}, "error": None}
|
||||||
|
99
tests/components/teslemetry/fixtures/products.json
Normal file
99
tests/components/teslemetry/fixtures/products.json
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"id": 1234,
|
||||||
|
"user_id": 1234,
|
||||||
|
"vehicle_id": 1234,
|
||||||
|
"vin": "VINVINVIN",
|
||||||
|
"color": null,
|
||||||
|
"access_type": "OWNER",
|
||||||
|
"display_name": "Test",
|
||||||
|
"option_codes": null,
|
||||||
|
"cached_data": null,
|
||||||
|
"granular_access": { "hide_private": false },
|
||||||
|
"tokens": ["abc", "def"],
|
||||||
|
"state": "asleep",
|
||||||
|
"in_service": false,
|
||||||
|
"id_s": "1234",
|
||||||
|
"calendar_enabled": true,
|
||||||
|
"api_version": 71,
|
||||||
|
"backseat_token": null,
|
||||||
|
"backseat_token_updated_at": null,
|
||||||
|
"ble_autopair_enrolled": false,
|
||||||
|
"vehicle_config": {
|
||||||
|
"aux_park_lamps": "Eu",
|
||||||
|
"badge_version": 1,
|
||||||
|
"can_accept_navigation_requests": true,
|
||||||
|
"can_actuate_trunks": true,
|
||||||
|
"car_special_type": "base",
|
||||||
|
"car_type": "model3",
|
||||||
|
"charge_port_type": "CCS",
|
||||||
|
"cop_user_set_temp_supported": false,
|
||||||
|
"dashcam_clip_save_supported": true,
|
||||||
|
"default_charge_to_max": false,
|
||||||
|
"driver_assist": "TeslaAP3",
|
||||||
|
"ece_restrictions": false,
|
||||||
|
"efficiency_package": "M32021",
|
||||||
|
"eu_vehicle": true,
|
||||||
|
"exterior_color": "DeepBlue",
|
||||||
|
"exterior_trim": "Black",
|
||||||
|
"exterior_trim_override": "",
|
||||||
|
"has_air_suspension": false,
|
||||||
|
"has_ludicrous_mode": false,
|
||||||
|
"has_seat_cooling": false,
|
||||||
|
"headlamp_type": "Global",
|
||||||
|
"interior_trim_type": "White2",
|
||||||
|
"key_version": 2,
|
||||||
|
"motorized_charge_port": true,
|
||||||
|
"paint_color_override": "0,9,25,0.7,0.04",
|
||||||
|
"performance_package": "Base",
|
||||||
|
"plg": true,
|
||||||
|
"pws": true,
|
||||||
|
"rear_drive_unit": "PM216MOSFET",
|
||||||
|
"rear_seat_heaters": 1,
|
||||||
|
"rear_seat_type": 0,
|
||||||
|
"rhd": true,
|
||||||
|
"roof_color": "RoofColorGlass",
|
||||||
|
"seat_type": null,
|
||||||
|
"spoiler_type": "None",
|
||||||
|
"sun_roof_installed": null,
|
||||||
|
"supports_qr_pairing": false,
|
||||||
|
"third_row_seats": "None",
|
||||||
|
"timestamp": 1705701487912,
|
||||||
|
"trim_badging": "74d",
|
||||||
|
"use_range_badging": true,
|
||||||
|
"utc_offset": 36000,
|
||||||
|
"webcam_selfie_supported": true,
|
||||||
|
"webcam_supported": true,
|
||||||
|
"wheel_type": "Pinwheel18CapKit"
|
||||||
|
},
|
||||||
|
"command_signing": "allowed",
|
||||||
|
"release_notes_supported": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"energy_site_id": 2345,
|
||||||
|
"resource_type": "wall_connector",
|
||||||
|
"id": "ID1234",
|
||||||
|
"asset_site_id": "abcdef",
|
||||||
|
"warp_site_number": "ID1234",
|
||||||
|
"go_off_grid_test_banner_enabled": null,
|
||||||
|
"storm_mode_enabled": null,
|
||||||
|
"powerwall_onboarding_settings_set": null,
|
||||||
|
"powerwall_tesla_electric_interested_in": null,
|
||||||
|
"vpp_tour_enabled": null,
|
||||||
|
"sync_grid_alert_enabled": false,
|
||||||
|
"breaker_alert_enabled": false,
|
||||||
|
"components": {
|
||||||
|
"battery": false,
|
||||||
|
"solar": false,
|
||||||
|
"grid": false,
|
||||||
|
"load_meter": false,
|
||||||
|
"wall_connectors": [
|
||||||
|
{ "device_id": "abcdef", "din": "12345", "is_active": true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"features": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"count": 2
|
||||||
|
}
|
269
tests/components/teslemetry/fixtures/vehicle_data.json
Normal file
269
tests/components/teslemetry/fixtures/vehicle_data.json
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"id": 1234,
|
||||||
|
"user_id": 1234,
|
||||||
|
"vehicle_id": 1234,
|
||||||
|
"vin": "VINVINVIN",
|
||||||
|
"color": null,
|
||||||
|
"access_type": "OWNER",
|
||||||
|
"granular_access": {
|
||||||
|
"hide_private": false
|
||||||
|
},
|
||||||
|
"tokens": ["abc", "def"],
|
||||||
|
"state": "online",
|
||||||
|
"in_service": false,
|
||||||
|
"id_s": "1234",
|
||||||
|
"calendar_enabled": true,
|
||||||
|
"api_version": 71,
|
||||||
|
"backseat_token": null,
|
||||||
|
"backseat_token_updated_at": null,
|
||||||
|
"ble_autopair_enrolled": false,
|
||||||
|
"charge_state": {
|
||||||
|
"battery_heater_on": false,
|
||||||
|
"battery_level": 77,
|
||||||
|
"battery_range": 266.87,
|
||||||
|
"charge_amps": 16,
|
||||||
|
"charge_current_request": 16,
|
||||||
|
"charge_current_request_max": 16,
|
||||||
|
"charge_enable_request": true,
|
||||||
|
"charge_energy_added": 0,
|
||||||
|
"charge_limit_soc": 80,
|
||||||
|
"charge_limit_soc_max": 100,
|
||||||
|
"charge_limit_soc_min": 50,
|
||||||
|
"charge_limit_soc_std": 80,
|
||||||
|
"charge_miles_added_ideal": 0,
|
||||||
|
"charge_miles_added_rated": 0,
|
||||||
|
"charge_port_cold_weather_mode": false,
|
||||||
|
"charge_port_color": "<invalid>",
|
||||||
|
"charge_port_door_open": true,
|
||||||
|
"charge_port_latch": "Engaged",
|
||||||
|
"charge_rate": 0,
|
||||||
|
"charger_actual_current": 0,
|
||||||
|
"charger_phases": null,
|
||||||
|
"charger_pilot_current": 16,
|
||||||
|
"charger_power": 0,
|
||||||
|
"charger_voltage": 2,
|
||||||
|
"charging_state": "Stopped",
|
||||||
|
"conn_charge_cable": "IEC",
|
||||||
|
"est_battery_range": 275.04,
|
||||||
|
"fast_charger_brand": "<invalid>",
|
||||||
|
"fast_charger_present": false,
|
||||||
|
"fast_charger_type": "ACSingleWireCAN",
|
||||||
|
"ideal_battery_range": 266.87,
|
||||||
|
"max_range_charge_counter": 0,
|
||||||
|
"minutes_to_full_charge": 0,
|
||||||
|
"not_enough_power_to_heat": null,
|
||||||
|
"off_peak_charging_enabled": false,
|
||||||
|
"off_peak_charging_times": "all_week",
|
||||||
|
"off_peak_hours_end_time": 900,
|
||||||
|
"preconditioning_enabled": false,
|
||||||
|
"preconditioning_times": "all_week",
|
||||||
|
"scheduled_charging_mode": "Off",
|
||||||
|
"scheduled_charging_pending": false,
|
||||||
|
"scheduled_charging_start_time": null,
|
||||||
|
"scheduled_charging_start_time_app": 600,
|
||||||
|
"scheduled_departure_time": 1704837600,
|
||||||
|
"scheduled_departure_time_minutes": 480,
|
||||||
|
"supercharger_session_trip_planner": false,
|
||||||
|
"time_to_full_charge": 0,
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"trip_charging": false,
|
||||||
|
"usable_battery_level": 77,
|
||||||
|
"user_charge_enable_request": null
|
||||||
|
},
|
||||||
|
"climate_state": {
|
||||||
|
"allow_cabin_overheat_protection": true,
|
||||||
|
"auto_seat_climate_left": false,
|
||||||
|
"auto_seat_climate_right": true,
|
||||||
|
"auto_steering_wheel_heat": false,
|
||||||
|
"battery_heater": false,
|
||||||
|
"battery_heater_no_power": null,
|
||||||
|
"cabin_overheat_protection": "On",
|
||||||
|
"cabin_overheat_protection_actively_cooling": false,
|
||||||
|
"climate_keeper_mode": "off",
|
||||||
|
"cop_activation_temperature": "High",
|
||||||
|
"defrost_mode": 0,
|
||||||
|
"driver_temp_setting": 22,
|
||||||
|
"fan_status": 0,
|
||||||
|
"hvac_auto_request": "On",
|
||||||
|
"inside_temp": 29.8,
|
||||||
|
"is_auto_conditioning_on": false,
|
||||||
|
"is_climate_on": false,
|
||||||
|
"is_front_defroster_on": false,
|
||||||
|
"is_preconditioning": false,
|
||||||
|
"is_rear_defroster_on": false,
|
||||||
|
"left_temp_direction": 251,
|
||||||
|
"max_avail_temp": 28,
|
||||||
|
"min_avail_temp": 15,
|
||||||
|
"outside_temp": 30,
|
||||||
|
"passenger_temp_setting": 22,
|
||||||
|
"remote_heater_control_enabled": false,
|
||||||
|
"right_temp_direction": 251,
|
||||||
|
"seat_heater_left": 0,
|
||||||
|
"seat_heater_rear_center": 0,
|
||||||
|
"seat_heater_rear_left": 0,
|
||||||
|
"seat_heater_rear_right": 0,
|
||||||
|
"seat_heater_right": 0,
|
||||||
|
"side_mirror_heaters": false,
|
||||||
|
"steering_wheel_heat_level": 0,
|
||||||
|
"steering_wheel_heater": false,
|
||||||
|
"supports_fan_only_cabin_overheat_protection": true,
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"wiper_blade_heater": false
|
||||||
|
},
|
||||||
|
"drive_state": {
|
||||||
|
"active_route_latitude": -27.855946,
|
||||||
|
"active_route_longitude": 153.345056,
|
||||||
|
"active_route_traffic_minutes_delay": 0,
|
||||||
|
"power": 0,
|
||||||
|
"shift_state": null,
|
||||||
|
"speed": null,
|
||||||
|
"timestamp": 1705707520649
|
||||||
|
},
|
||||||
|
"gui_settings": {
|
||||||
|
"gui_24_hour_time": false,
|
||||||
|
"gui_charge_rate_units": "kW",
|
||||||
|
"gui_distance_units": "km/hr",
|
||||||
|
"gui_range_display": "Rated",
|
||||||
|
"gui_temperature_units": "C",
|
||||||
|
"gui_tirepressure_units": "Psi",
|
||||||
|
"show_range_units": false,
|
||||||
|
"timestamp": 1705707520649
|
||||||
|
},
|
||||||
|
"vehicle_config": {
|
||||||
|
"aux_park_lamps": "Eu",
|
||||||
|
"badge_version": 1,
|
||||||
|
"can_accept_navigation_requests": true,
|
||||||
|
"can_actuate_trunks": true,
|
||||||
|
"car_special_type": "base",
|
||||||
|
"car_type": "model3",
|
||||||
|
"charge_port_type": "CCS",
|
||||||
|
"cop_user_set_temp_supported": false,
|
||||||
|
"dashcam_clip_save_supported": true,
|
||||||
|
"default_charge_to_max": false,
|
||||||
|
"driver_assist": "TeslaAP3",
|
||||||
|
"ece_restrictions": false,
|
||||||
|
"efficiency_package": "M32021",
|
||||||
|
"eu_vehicle": true,
|
||||||
|
"exterior_color": "DeepBlue",
|
||||||
|
"exterior_trim": "Black",
|
||||||
|
"exterior_trim_override": "",
|
||||||
|
"has_air_suspension": false,
|
||||||
|
"has_ludicrous_mode": false,
|
||||||
|
"has_seat_cooling": false,
|
||||||
|
"headlamp_type": "Global",
|
||||||
|
"interior_trim_type": "White2",
|
||||||
|
"key_version": 2,
|
||||||
|
"motorized_charge_port": true,
|
||||||
|
"paint_color_override": "0,9,25,0.7,0.04",
|
||||||
|
"performance_package": "Base",
|
||||||
|
"plg": true,
|
||||||
|
"pws": true,
|
||||||
|
"rear_drive_unit": "PM216MOSFET",
|
||||||
|
"rear_seat_heaters": 1,
|
||||||
|
"rear_seat_type": 0,
|
||||||
|
"rhd": true,
|
||||||
|
"roof_color": "RoofColorGlass",
|
||||||
|
"seat_type": null,
|
||||||
|
"spoiler_type": "None",
|
||||||
|
"sun_roof_installed": null,
|
||||||
|
"supports_qr_pairing": false,
|
||||||
|
"third_row_seats": "None",
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"trim_badging": "74d",
|
||||||
|
"use_range_badging": true,
|
||||||
|
"utc_offset": 36000,
|
||||||
|
"webcam_selfie_supported": true,
|
||||||
|
"webcam_supported": true,
|
||||||
|
"wheel_type": "Pinwheel18CapKit"
|
||||||
|
},
|
||||||
|
"vehicle_state": {
|
||||||
|
"api_version": 71,
|
||||||
|
"autopark_state_v2": "unavailable",
|
||||||
|
"calendar_supported": true,
|
||||||
|
"car_version": "2023.44.30.8 06f534d46010",
|
||||||
|
"center_display_state": 0,
|
||||||
|
"dashcam_clip_save_available": true,
|
||||||
|
"dashcam_state": "Recording",
|
||||||
|
"df": 0,
|
||||||
|
"dr": 0,
|
||||||
|
"fd_window": 0,
|
||||||
|
"feature_bitmask": "fbdffbff,187f",
|
||||||
|
"fp_window": 0,
|
||||||
|
"ft": 0,
|
||||||
|
"is_user_present": false,
|
||||||
|
"locked": false,
|
||||||
|
"media_info": {
|
||||||
|
"audio_volume": 2.6667,
|
||||||
|
"audio_volume_increment": 0.333333,
|
||||||
|
"audio_volume_max": 10.333333,
|
||||||
|
"media_playback_status": "Stopped",
|
||||||
|
"now_playing_album": "",
|
||||||
|
"now_playing_artist": "",
|
||||||
|
"now_playing_duration": 0,
|
||||||
|
"now_playing_elapsed": 0,
|
||||||
|
"now_playing_source": "Spotify",
|
||||||
|
"now_playing_station": "",
|
||||||
|
"now_playing_title": ""
|
||||||
|
},
|
||||||
|
"media_state": {
|
||||||
|
"remote_control_enabled": true
|
||||||
|
},
|
||||||
|
"notifications_supported": true,
|
||||||
|
"odometer": 6481.019282,
|
||||||
|
"parsed_calendar_supported": true,
|
||||||
|
"pf": 0,
|
||||||
|
"pr": 0,
|
||||||
|
"rd_window": 0,
|
||||||
|
"remote_start": false,
|
||||||
|
"remote_start_enabled": true,
|
||||||
|
"remote_start_supported": true,
|
||||||
|
"rp_window": 0,
|
||||||
|
"rt": 0,
|
||||||
|
"santa_mode": 0,
|
||||||
|
"sentry_mode": false,
|
||||||
|
"sentry_mode_available": true,
|
||||||
|
"service_mode": false,
|
||||||
|
"service_mode_plus": false,
|
||||||
|
"software_update": {
|
||||||
|
"download_perc": 0,
|
||||||
|
"expected_duration_sec": 2700,
|
||||||
|
"install_perc": 1,
|
||||||
|
"status": "",
|
||||||
|
"version": " "
|
||||||
|
},
|
||||||
|
"speed_limit_mode": {
|
||||||
|
"active": false,
|
||||||
|
"current_limit_mph": 69,
|
||||||
|
"max_limit_mph": 120,
|
||||||
|
"min_limit_mph": 50,
|
||||||
|
"pin_code_set": true
|
||||||
|
},
|
||||||
|
"timestamp": 1705707520649,
|
||||||
|
"tpms_hard_warning_fl": false,
|
||||||
|
"tpms_hard_warning_fr": false,
|
||||||
|
"tpms_hard_warning_rl": false,
|
||||||
|
"tpms_hard_warning_rr": false,
|
||||||
|
"tpms_last_seen_pressure_time_fl": 1705700812,
|
||||||
|
"tpms_last_seen_pressure_time_fr": 1705700793,
|
||||||
|
"tpms_last_seen_pressure_time_rl": 1705700794,
|
||||||
|
"tpms_last_seen_pressure_time_rr": 1705700823,
|
||||||
|
"tpms_pressure_fl": 2.775,
|
||||||
|
"tpms_pressure_fr": 2.8,
|
||||||
|
"tpms_pressure_rl": 2.775,
|
||||||
|
"tpms_pressure_rr": 2.775,
|
||||||
|
"tpms_rcp_front_value": 2.9,
|
||||||
|
"tpms_rcp_rear_value": 2.9,
|
||||||
|
"tpms_soft_warning_fl": false,
|
||||||
|
"tpms_soft_warning_fr": false,
|
||||||
|
"tpms_soft_warning_rl": false,
|
||||||
|
"tpms_soft_warning_rr": false,
|
||||||
|
"valet_mode": false,
|
||||||
|
"valet_pin_needed": false,
|
||||||
|
"vehicle_name": "Test",
|
||||||
|
"vehicle_self_test_progress": 0,
|
||||||
|
"vehicle_self_test_requested": false,
|
||||||
|
"webcam_available": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
tests/components/teslemetry/snapshots/test_climate.ambr
Normal file
73
tests/components/teslemetry/snapshots/test_climate.ambr
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_climate[climate.test_climate-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 28.0,
|
||||||
|
'min_temp': 15.0,
|
||||||
|
'preset_modes': list([
|
||||||
|
'off',
|
||||||
|
'keep',
|
||||||
|
'dog',
|
||||||
|
'camp',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'climate',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'climate.test_climate',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Climate',
|
||||||
|
'platform': 'teslemetry',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': <ClimateEntityFeature: 17>,
|
||||||
|
'translation_key': <TeslemetryClimateSide.DRIVER: 'driver_temp'>,
|
||||||
|
'unique_id': 'VINVINVIN-driver_temp',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_climate[climate.test_climate-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_temperature': 30.0,
|
||||||
|
'friendly_name': 'Test Climate',
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 28.0,
|
||||||
|
'min_temp': 15.0,
|
||||||
|
'preset_mode': 'off',
|
||||||
|
'preset_modes': list([
|
||||||
|
'off',
|
||||||
|
'keep',
|
||||||
|
'dog',
|
||||||
|
'camp',
|
||||||
|
]),
|
||||||
|
'supported_features': <ClimateEntityFeature: 17>,
|
||||||
|
'temperature': 22.0,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'climate.test_climate',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
131
tests/components/teslemetry/test_climate.py
Normal file
131
tests/components/teslemetry/test_climate.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
"""Test the Teslemetry climate platform."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
import pytest
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
from tesla_fleet_api.exceptions import InvalidCommand, VehicleOffline
|
||||||
|
|
||||||
|
from homeassistant.components.climate import (
|
||||||
|
ATTR_HVAC_MODE,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
|
ATTR_TEMPERATURE,
|
||||||
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
HVACMode,
|
||||||
|
)
|
||||||
|
from homeassistant.components.teslemetry.coordinator import SYNC_INTERVAL
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from . import assert_entities, setup_platform
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_climate(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that the climate entity is correct."""
|
||||||
|
|
||||||
|
entry = await setup_platform(hass, [Platform.CLIMATE])
|
||||||
|
|
||||||
|
assert_entities(hass, entry.entry_id, entity_registry, snapshot)
|
||||||
|
|
||||||
|
entity_id = "climate.test_climate"
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
|
||||||
|
# Turn On
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVACMode.HEAT_COOL},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == HVACMode.HEAT_COOL
|
||||||
|
|
||||||
|
# Set Temp
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
{ATTR_ENTITY_ID: [entity_id], ATTR_TEMPERATURE: 20},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.attributes[ATTR_TEMPERATURE] == 20
|
||||||
|
|
||||||
|
# Set Preset
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{ATTR_ENTITY_ID: [entity_id], ATTR_PRESET_MODE: "keep"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.attributes[ATTR_PRESET_MODE] == "keep"
|
||||||
|
|
||||||
|
# Turn Off
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
{ATTR_ENTITY_ID: [entity_id], ATTR_HVAC_MODE: HVACMode.OFF},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.state == HVACMode.OFF
|
||||||
|
|
||||||
|
|
||||||
|
async def test_errors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Tests service error is handled."""
|
||||||
|
|
||||||
|
await setup_platform(hass, platforms=[Platform.CLIMATE])
|
||||||
|
entity_id = "climate.test_climate"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.teslemetry.VehicleSpecific.auto_conditioning_start",
|
||||||
|
side_effect=InvalidCommand,
|
||||||
|
) as mock_on, pytest.raises(HomeAssistantError) as error:
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: [entity_id]},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_on.assert_called_once()
|
||||||
|
assert error.from_exception == InvalidCommand
|
||||||
|
|
||||||
|
|
||||||
|
async def test_asleep_or_offline(
|
||||||
|
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Tests asleep is handled."""
|
||||||
|
|
||||||
|
await setup_platform(hass, [Platform.CLIMATE])
|
||||||
|
entity_id = "climate.test_climate"
|
||||||
|
mock_vehicle_data.assert_called_once()
|
||||||
|
|
||||||
|
# Put the vehicle alseep
|
||||||
|
mock_vehicle_data.reset_mock()
|
||||||
|
mock_vehicle_data.side_effect = VehicleOffline
|
||||||
|
freezer.tick(timedelta(seconds=SYNC_INTERVAL))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_vehicle_data.assert_called_once()
|
||||||
|
|
||||||
|
# Run a command that will wake up the vehicle, but not immediately
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: [entity_id]}, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
@ -1,6 +1,6 @@
|
|||||||
"""Test the Teslemetry config flow."""
|
"""Test the Teslemetry config flow."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from aiohttp import ClientConnectionError
|
from aiohttp import ClientConnectionError
|
||||||
import pytest
|
import pytest
|
||||||
@ -16,13 +16,12 @@ from .const import CONFIG
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def teslemetry_config_entry_mock():
|
def mock_test():
|
||||||
"""Mock Teslemetry api class."""
|
"""Mock Teslemetry api class."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.teslemetry.config_flow.Teslemetry",
|
"homeassistant.components.teslemetry.Teslemetry.test", return_value=True
|
||||||
) as teslemetry_config_entry_mock:
|
) as mock_test:
|
||||||
teslemetry_config_entry_mock.return_value.test = AsyncMock()
|
yield mock_test
|
||||||
yield teslemetry_config_entry_mock
|
|
||||||
|
|
||||||
|
|
||||||
async def test_form(
|
async def test_form(
|
||||||
@ -60,16 +59,14 @@ async def test_form(
|
|||||||
(TeslaFleetError, {"base": "unknown"}),
|
(TeslaFleetError, {"base": "unknown"}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_form_errors(
|
async def test_form_errors(hass: HomeAssistant, side_effect, error, mock_test) -> None:
|
||||||
hass: HomeAssistant, side_effect, error, teslemetry_config_entry_mock
|
|
||||||
) -> None:
|
|
||||||
"""Test errors are handled."""
|
"""Test errors are handled."""
|
||||||
|
|
||||||
result1 = await hass.config_entries.flow.async_init(
|
result1 = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
|
|
||||||
teslemetry_config_entry_mock.return_value.test.side_effect = side_effect
|
mock_test.side_effect = side_effect
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result1["flow_id"],
|
result1["flow_id"],
|
||||||
CONFIG,
|
CONFIG,
|
||||||
@ -79,7 +76,7 @@ async def test_form_errors(
|
|||||||
assert result2["errors"] == error
|
assert result2["errors"] == error
|
||||||
|
|
||||||
# Complete the flow
|
# Complete the flow
|
||||||
teslemetry_config_entry_mock.return_value.test.side_effect = None
|
mock_test.side_effect = None
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
CONFIG,
|
CONFIG,
|
||||||
|
118
tests/components/teslemetry/test_init.py
Normal file
118
tests/components/teslemetry/test_init.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
"""Test the Tessie init."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from tesla_fleet_api.exceptions import (
|
||||||
|
InvalidToken,
|
||||||
|
PaymentRequired,
|
||||||
|
TeslaFleetError,
|
||||||
|
VehicleOffline,
|
||||||
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.teslemetry.coordinator import SYNC_INTERVAL
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from . import setup_platform
|
||||||
|
from .const import WAKE_UP_ASLEEP, WAKE_UP_ONLINE
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_unload(hass: HomeAssistant) -> None:
|
||||||
|
"""Test load and unload."""
|
||||||
|
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_auth_failure(hass: HomeAssistant, mock_products) -> None:
|
||||||
|
"""Test init with an authentication error."""
|
||||||
|
|
||||||
|
mock_products.side_effect = InvalidToken
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_subscription_failure(hass: HomeAssistant, mock_products) -> None:
|
||||||
|
"""Test init with an client response error."""
|
||||||
|
|
||||||
|
mock_products.side_effect = PaymentRequired
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_other_failure(hass: HomeAssistant, mock_products) -> None:
|
||||||
|
"""Test init with an client response error."""
|
||||||
|
|
||||||
|
mock_products.side_effect = TeslaFleetError
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
# Coordinator
|
||||||
|
|
||||||
|
|
||||||
|
async def test_first_refresh(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_wake_up,
|
||||||
|
mock_vehicle_data,
|
||||||
|
mock_products,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test first coordinator refresh but vehicle is asleep."""
|
||||||
|
|
||||||
|
# Mock vehicle is asleep
|
||||||
|
mock_wake_up.return_value = WAKE_UP_ASLEEP
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
mock_wake_up.assert_called_once()
|
||||||
|
|
||||||
|
# Reset mock and set vehicle to online
|
||||||
|
mock_wake_up.reset_mock()
|
||||||
|
mock_wake_up.return_value = WAKE_UP_ONLINE
|
||||||
|
|
||||||
|
# Wait for the retry
|
||||||
|
freezer.tick(timedelta(seconds=60))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Verify we have loaded
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
mock_wake_up.assert_called_once()
|
||||||
|
mock_vehicle_data.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_first_refresh_error(hass: HomeAssistant, mock_wake_up) -> None:
|
||||||
|
"""Test first coordinator refresh with an error."""
|
||||||
|
mock_wake_up.side_effect = TeslaFleetError
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_refresh_offline(
|
||||||
|
hass: HomeAssistant, mock_vehicle_data, freezer: FrozenDateTimeFactory
|
||||||
|
) -> None:
|
||||||
|
"""Test coordinator refresh with an error."""
|
||||||
|
entry = await setup_platform(hass, [Platform.CLIMATE])
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
mock_vehicle_data.assert_called_once()
|
||||||
|
mock_vehicle_data.reset_mock()
|
||||||
|
|
||||||
|
mock_vehicle_data.side_effect = VehicleOffline
|
||||||
|
freezer.tick(timedelta(seconds=SYNC_INTERVAL))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
mock_vehicle_data.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_refresh_error(hass: HomeAssistant, mock_vehicle_data) -> None:
|
||||||
|
"""Test coordinator refresh with an error."""
|
||||||
|
mock_vehicle_data.side_effect = TeslaFleetError
|
||||||
|
entry = await setup_platform(hass)
|
||||||
|
assert entry.state is ConfigEntryState.SETUP_RETRY
|
Loading…
x
Reference in New Issue
Block a user