Merge branch 'dev' into block_pyserial_asyncio

This commit is contained in:
J. Nick Koston 2024-05-03 02:12:40 -05:00 committed by GitHub
commit dba07ac90d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 467 additions and 220 deletions

View File

@ -51,8 +51,9 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.issue_registry import async_delete_issue from homeassistant.helpers.issue_registry import async_delete_issue
from homeassistant.loader import async_get_bluetooth from homeassistant.loader import async_get_bluetooth
from . import models, passive_update_processor from . import passive_update_processor
from .api import ( from .api import (
_get_manager,
async_address_present, async_address_present,
async_ble_device_from_address, async_ble_device_from_address,
async_discovered_service_info, async_discovered_service_info,
@ -76,7 +77,6 @@ from .const import (
CONF_ADAPTER, CONF_ADAPTER,
CONF_DETAILS, CONF_DETAILS,
CONF_PASSIVE, CONF_PASSIVE,
DATA_MANAGER,
DOMAIN, DOMAIN,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS, FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS, LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
@ -230,10 +230,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass, integration_matcher, bluetooth_adapters, bluetooth_storage, slot_manager hass, integration_matcher, bluetooth_adapters, bluetooth_storage, slot_manager
) )
set_manager(manager) set_manager(manager)
await storage_setup_task await storage_setup_task
await manager.async_setup() await manager.async_setup()
hass.data[DATA_MANAGER] = models.MANAGER = manager
hass.async_create_background_task( hass.async_create_background_task(
_async_start_adapter_discovery(hass, manager, bluetooth_adapters), _async_start_adapter_discovery(hass, manager, bluetooth_adapters),
@ -314,7 +312,7 @@ async def async_update_device(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry for a bluetooth scanner.""" """Set up a config entry for a bluetooth scanner."""
manager: HomeAssistantBluetoothManager = hass.data[DATA_MANAGER] manager = _get_manager(hass)
address = entry.unique_id address = entry.unique_id
assert address is not None assert address is not None
adapter = await manager.async_get_adapter_from_address_or_recover(address) adapter = await manager.async_get_adapter_from_address_or_recover(address)

View File

@ -15,10 +15,12 @@ from habluetooth import (
BluetoothScannerDevice, BluetoothScannerDevice,
BluetoothScanningMode, BluetoothScanningMode,
HaBleakScannerWrapper, HaBleakScannerWrapper,
get_manager,
) )
from home_assistant_bluetooth import BluetoothServiceInfoBleak from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.helpers.singleton import singleton
from .const import DATA_MANAGER from .const import DATA_MANAGER
from .manager import HomeAssistantBluetoothManager from .manager import HomeAssistantBluetoothManager
@ -29,9 +31,10 @@ if TYPE_CHECKING:
from bleak.backends.device import BLEDevice from bleak.backends.device import BLEDevice
@singleton(DATA_MANAGER)
def _get_manager(hass: HomeAssistant) -> HomeAssistantBluetoothManager: def _get_manager(hass: HomeAssistant) -> HomeAssistantBluetoothManager:
"""Get the bluetooth manager.""" """Get the bluetooth manager."""
return cast(HomeAssistantBluetoothManager, hass.data[DATA_MANAGER]) return cast(HomeAssistantBluetoothManager, get_manager())
@hass_callback @hass_callback
@ -68,8 +71,6 @@ def async_discovered_service_info(
hass: HomeAssistant, connectable: bool = True hass: HomeAssistant, connectable: bool = True
) -> Iterable[BluetoothServiceInfoBleak]: ) -> Iterable[BluetoothServiceInfoBleak]:
"""Return the discovered devices list.""" """Return the discovered devices list."""
if DATA_MANAGER not in hass.data:
return []
return _get_manager(hass).async_discovered_service_info(connectable) return _get_manager(hass).async_discovered_service_info(connectable)
@ -78,8 +79,6 @@ def async_last_service_info(
hass: HomeAssistant, address: str, connectable: bool = True hass: HomeAssistant, address: str, connectable: bool = True
) -> BluetoothServiceInfoBleak | None: ) -> BluetoothServiceInfoBleak | None:
"""Return the last service info for an address.""" """Return the last service info for an address."""
if DATA_MANAGER not in hass.data:
return None
return _get_manager(hass).async_last_service_info(address, connectable) return _get_manager(hass).async_last_service_info(address, connectable)
@ -88,8 +87,6 @@ def async_ble_device_from_address(
hass: HomeAssistant, address: str, connectable: bool = True hass: HomeAssistant, address: str, connectable: bool = True
) -> BLEDevice | None: ) -> BLEDevice | None:
"""Return BLEDevice for an address if its present.""" """Return BLEDevice for an address if its present."""
if DATA_MANAGER not in hass.data:
return None
return _get_manager(hass).async_ble_device_from_address(address, connectable) return _get_manager(hass).async_ble_device_from_address(address, connectable)
@ -106,8 +103,6 @@ def async_address_present(
hass: HomeAssistant, address: str, connectable: bool = True hass: HomeAssistant, address: str, connectable: bool = True
) -> bool: ) -> bool:
"""Check if an address is present in the bluetooth device list.""" """Check if an address is present in the bluetooth device list."""
if DATA_MANAGER not in hass.data:
return False
return _get_manager(hass).async_address_present(address, connectable) return _get_manager(hass).async_address_present(address, connectable)

View File

@ -14,6 +14,7 @@ from bluetooth_adapters import (
adapter_model, adapter_model,
get_adapters, get_adapters,
) )
from habluetooth import get_manager
import voluptuous as vol import voluptuous as vol
from homeassistant.components import onboarding from homeassistant.components import onboarding
@ -25,7 +26,6 @@ from homeassistant.helpers.schema_config_entry_flow import (
) )
from homeassistant.helpers.typing import DiscoveryInfoType from homeassistant.helpers.typing import DiscoveryInfoType
from . import models
from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN
from .util import adapter_title from .util import adapter_title
@ -185,4 +185,4 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
@callback @callback
def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool: def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool:
"""Return options flow support for this handler.""" """Return options flow support for this handler."""
return bool(models.MANAGER and models.MANAGER.supports_passive_scan) return bool((manager := get_manager()) and manager.supports_passive_scan)

View File

@ -20,6 +20,6 @@
"bluetooth-auto-recovery==1.4.2", "bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.19.0", "bluetooth-data-tools==1.19.0",
"dbus-fast==2.21.1", "dbus-fast==2.21.1",
"habluetooth==2.8.0" "habluetooth==2.8.1"
] ]
} }

View File

@ -4,17 +4,9 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from enum import Enum from enum import Enum
from typing import TYPE_CHECKING
from home_assistant_bluetooth import BluetoothServiceInfoBleak from home_assistant_bluetooth import BluetoothServiceInfoBleak
if TYPE_CHECKING:
from .manager import HomeAssistantBluetoothManager
MANAGER: HomeAssistantBluetoothManager | None = None
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT") BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None] BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool] ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]

View File

@ -49,6 +49,7 @@ PLATFORMS = [
Platform.NOTIFY, Platform.NOTIFY,
Platform.NUMBER, Platform.NUMBER,
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH,
Platform.WEATHER, Platform.WEATHER,
] ]

View File

@ -10,7 +10,7 @@
}, },
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["pyecobee"], "loggers": ["pyecobee"],
"requirements": ["python-ecobee-api==0.2.17"], "requirements": ["python-ecobee-api==0.2.18"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_ecobee._tcp.local." "type": "_ecobee._tcp.local."

View File

@ -0,0 +1,90 @@
"""Support for using switch with ecobee thermostats."""
from __future__ import annotations
import logging
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util
from . import EcobeeData
from .const import DOMAIN
from .entity import EcobeeBaseEntity
_LOGGER = logging.getLogger(__name__)
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the ecobee thermostat switch entity."""
data: EcobeeData = hass.data[DOMAIN]
async_add_entities(
(
EcobeeVentilator20MinSwitch(data, index)
for index, thermostat in enumerate(data.ecobee.thermostats)
if thermostat["settings"]["ventilatorType"] != "none"
),
True,
)
class EcobeeVentilator20MinSwitch(EcobeeBaseEntity, SwitchEntity):
"""A Switch class, representing 20 min timer for an ecobee thermostat with ventilator attached."""
_attr_has_entity_name = True
_attr_name = "Ventilator 20m Timer"
def __init__(
self,
data: EcobeeData,
thermostat_index: int,
) -> None:
"""Initialize ecobee ventilator platform."""
super().__init__(data, thermostat_index)
self._attr_unique_id = f"{self.base_unique_id}_ventilator_20m_timer"
self._attr_is_on = False
self.update_without_throttle = False
self._operating_timezone = dt_util.get_time_zone(
self.thermostat["location"]["timeZone"]
)
async def async_update(self) -> None:
"""Get the latest state from the thermostat."""
if self.update_without_throttle:
await self.data.update(no_throttle=True)
self.update_without_throttle = False
else:
await self.data.update()
ventilator_off_date_time = self.thermostat["settings"]["ventilatorOffDateTime"]
self._attr_is_on = ventilator_off_date_time and dt_util.parse_datetime(
ventilator_off_date_time, raise_on_error=True
).replace(tzinfo=self._operating_timezone) >= dt_util.now(
self._operating_timezone
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Set ventilator 20 min timer on."""
await self.hass.async_add_executor_job(
self.data.ecobee.set_ventilator_timer, self.thermostat_index, True
)
self.update_without_throttle = True
async def async_turn_off(self, **kwargs: Any) -> None:
"""Set ventilator 20 min timer off."""
await self.hass.async_add_executor_job(
self.data.ecobee.set_ventilator_timer, self.thermostat_index, False
)
self.update_without_throttle = True

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/environment_canada", "documentation": "https://www.home-assistant.io/integrations/environment_canada",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["env_canada"], "loggers": ["env_canada"],
"requirements": ["env-canada==0.6.0"] "requirements": ["env-canada==0.6.2"]
} }

View File

@ -86,7 +86,6 @@ ABBREVIATIONS = {
"json_attr": "json_attributes", "json_attr": "json_attributes",
"json_attr_t": "json_attributes_topic", "json_attr_t": "json_attributes_topic",
"json_attr_tpl": "json_attributes_template", "json_attr_tpl": "json_attributes_template",
"lrst_t": "last_reset_topic",
"lrst_val_tpl": "last_reset_value_template", "lrst_val_tpl": "last_reset_value_template",
"max": "max", "max": "max",
"min": "min", "min": "min",

View File

@ -58,7 +58,6 @@ from .models import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_EXPIRE_AFTER = "expire_after" CONF_EXPIRE_AFTER = "expire_after"
CONF_LAST_RESET_TOPIC = "last_reset_topic"
CONF_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template" CONF_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision" CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"
@ -101,17 +100,11 @@ def validate_sensor_state_class_config(config: ConfigType) -> ConfigType:
PLATFORM_SCHEMA_MODERN = vol.All( PLATFORM_SCHEMA_MODERN = vol.All(
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
# Removed in HA Core 2023.6.0
cv.removed(CONF_LAST_RESET_TOPIC),
_PLATFORM_SCHEMA_BASE, _PLATFORM_SCHEMA_BASE,
validate_sensor_state_class_config, validate_sensor_state_class_config,
) )
DISCOVERY_SCHEMA = vol.All( DISCOVERY_SCHEMA = vol.All(
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
# Removed in HA Core 2023.6.0
cv.removed(CONF_LAST_RESET_TOPIC),
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
validate_sensor_state_class_config, validate_sensor_state_class_config,
) )

View File

@ -5,7 +5,6 @@ from __future__ import annotations
import openai import openai
import voluptuous as vol import voluptuous as vol
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import ( from homeassistant.core import (
@ -115,5 +114,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return False return False
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
conversation.async_unset_agent(hass, entry)
return True return True

View File

@ -3,7 +3,7 @@
import logging import logging
DOMAIN = "openai_conversation" DOMAIN = "openai_conversation"
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__package__)
CONF_PROMPT = "prompt" CONF_PROMPT = "prompt"
DEFAULT_PROMPT = """This smart home is controlled by Home Assistant. DEFAULT_PROMPT = """This smart home is controlled by Home Assistant.

View File

@ -44,6 +44,8 @@ class OpenAIConversationEntity(
): ):
"""OpenAI conversation agent.""" """OpenAI conversation agent."""
_attr_has_entity_name = True
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Initialize the agent.""" """Initialize the agent."""
self.hass = hass self.hass = hass

View File

@ -21,7 +21,7 @@
"universal_silabs_flasher" "universal_silabs_flasher"
], ],
"requirements": [ "requirements": [
"bellows==0.38.3", "bellows==0.38.4",
"pyserial==3.5", "pyserial==3.5",
"zha-quirks==0.0.115", "zha-quirks==0.0.115",
"zigpy-deconz==0.23.1", "zigpy-deconz==0.23.1",

View File

@ -28,7 +28,7 @@ dbus-fast==2.21.1
fnv-hash-fast==0.5.0 fnv-hash-fast==0.5.0
ha-av==10.1.1 ha-av==10.1.1
ha-ffmpeg==3.2.0 ha-ffmpeg==3.2.0
habluetooth==2.8.0 habluetooth==2.8.1
hass-nabucasa==0.78.0 hass-nabucasa==0.78.0
hassil==1.6.1 hassil==1.6.1
home-assistant-bluetooth==1.12.0 home-assistant-bluetooth==1.12.0

View File

@ -541,7 +541,7 @@ beautifulsoup4==4.12.3
# beewi-smartclim==0.0.10 # beewi-smartclim==0.0.10
# homeassistant.components.zha # homeassistant.components.zha
bellows==0.38.3 bellows==0.38.4
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.15.2 bimmer-connected[china]==0.15.2
@ -804,7 +804,7 @@ enocean==0.50
enturclient==0.2.4 enturclient==0.2.4
# homeassistant.components.environment_canada # homeassistant.components.environment_canada
env-canada==0.6.0 env-canada==0.6.2
# homeassistant.components.season # homeassistant.components.season
ephem==4.1.5 ephem==4.1.5
@ -1035,7 +1035,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0 habitipy==0.2.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
habluetooth==2.8.0 habluetooth==2.8.1
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.78.0 hass-nabucasa==0.78.0
@ -2212,7 +2212,7 @@ python-clementine-remote==1.0.1
python-digitalocean==1.13.2 python-digitalocean==1.13.2
# homeassistant.components.ecobee # homeassistant.components.ecobee
python-ecobee-api==0.2.17 python-ecobee-api==0.2.18
# homeassistant.components.etherscan # homeassistant.components.etherscan
python-etherscan-api==0.0.3 python-etherscan-api==0.0.3

View File

@ -466,7 +466,7 @@ base36==0.1.1
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
# homeassistant.components.zha # homeassistant.components.zha
bellows==0.38.3 bellows==0.38.4
# homeassistant.components.bmw_connected_drive # homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.15.2 bimmer-connected[china]==0.15.2
@ -658,7 +658,7 @@ energyzero==2.1.0
enocean==0.50 enocean==0.50
# homeassistant.components.environment_canada # homeassistant.components.environment_canada
env-canada==0.6.0 env-canada==0.6.2
# homeassistant.components.season # homeassistant.components.season
ephem==4.1.5 ephem==4.1.5
@ -849,7 +849,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0 habitipy==0.2.0
# homeassistant.components.bluetooth # homeassistant.components.bluetooth
habluetooth==2.8.0 habluetooth==2.8.1
# homeassistant.components.cloud # homeassistant.components.cloud
hass-nabucasa==0.78.0 hass-nabucasa==0.78.0
@ -1727,7 +1727,7 @@ python-awair==0.2.4
python-bsblan==0.5.18 python-bsblan==0.5.18
# homeassistant.components.ecobee # homeassistant.components.ecobee
python-ecobee-api==0.2.17 python-ecobee-api==0.2.18
# homeassistant.components.fully_kiosk # homeassistant.components.fully_kiosk
python-fullykiosk==0.0.12 python-fullykiosk==0.0.12

View File

@ -8,7 +8,7 @@ from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch
from bleak import BleakError from bleak import BleakError
from bleak.backends.scanner import AdvertisementData, BLEDevice from bleak.backends.scanner import AdvertisementData, BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS from bluetooth_adapters import DEFAULT_ADDRESS
from habluetooth import scanner from habluetooth import scanner, set_manager
from habluetooth.wrappers import HaBleakScannerWrapper from habluetooth.wrappers import HaBleakScannerWrapper
import pytest import pytest
@ -1154,6 +1154,7 @@ async def test_async_discovered_device_api(
) -> None: ) -> None:
"""Test the async_discovered_device API.""" """Test the async_discovered_device API."""
mock_bt = [] mock_bt = []
set_manager(None)
with ( with (
patch( patch(
"homeassistant.components.bluetooth.async_get_bluetooth", "homeassistant.components.bluetooth.async_get_bluetooth",
@ -1169,8 +1170,10 @@ async def test_async_discovered_device_api(
}, },
), ),
): ):
assert not bluetooth.async_discovered_service_info(hass) with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") assert not bluetooth.async_discovered_service_info(hass)
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
await async_setup_with_default_adapter(hass) await async_setup_with_default_adapter(hass)
with patch.object(hass.config_entries.flow, "async_init"): with patch.object(hass.config_entries.flow, "async_init"):
@ -2744,6 +2747,7 @@ async def test_async_ble_device_from_address(
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock, macos_adapter: None hass: HomeAssistant, mock_bleak_scanner_start: MagicMock, macos_adapter: None
) -> None: ) -> None:
"""Test the async_ble_device_from_address api.""" """Test the async_ble_device_from_address api."""
set_manager(None)
mock_bt = [] mock_bt = []
with ( with (
patch( patch(
@ -2760,11 +2764,15 @@ async def test_async_ble_device_from_address(
}, },
), ),
): ):
assert not bluetooth.async_discovered_service_info(hass) with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22") assert not bluetooth.async_discovered_service_info(hass)
assert ( with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45") is None assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
) with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert (
bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45")
is None
)
await async_setup_with_default_adapter(hass) await async_setup_with_default_adapter(hass)

View File

@ -65,6 +65,9 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
"identifier": 8675309, "identifier": 8675309,
"name": "ecobee", "name": "ecobee",
"modelNumber": "athenaSmart", "modelNumber": "athenaSmart",
"utcTime": "2022-01-01 10:00:00",
"thermostatTime": "2022-01-01 6:00:00",
"location": {"timeZone": "America/Toronto"},
"program": { "program": {
"climates": [ "climates": [
{"name": "Climate1", "climateRef": "c1"}, {"name": "Climate1", "climateRef": "c1"},
@ -92,7 +95,8 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
"humidifierMode": "manual", "humidifierMode": "manual",
"humidity": "30", "humidity": "30",
"hasHeatPump": True, "hasHeatPump": True,
"ventilatorType": "none", "ventilatorType": "hrv",
"ventilatorOffDateTime": "2022-01-01 6:00:00",
}, },
"equipmentStatus": "fan", "equipmentStatus": "fan",
"events": [ "events": [

View File

@ -4,6 +4,11 @@
"identifier": 8675309, "identifier": 8675309,
"name": "ecobee", "name": "ecobee",
"modelNumber": "athenaSmart", "modelNumber": "athenaSmart",
"utcTime": "2022-01-01 10:00:00",
"thermostatTime": "2022-01-01 6:00:00",
"location": {
"timeZone": "America/Toronto"
},
"program": { "program": {
"climates": [ "climates": [
{ "name": "Climate1", "climateRef": "c1" }, { "name": "Climate1", "climateRef": "c1" },
@ -30,6 +35,7 @@
"ventilatorType": "hrv", "ventilatorType": "hrv",
"ventilatorMinOnTimeHome": 20, "ventilatorMinOnTimeHome": 20,
"ventilatorMinOnTimeAway": 10, "ventilatorMinOnTimeAway": 10,
"ventilatorOffDateTime": "2022-01-01 6:00:00",
"isVentilatorTimerOn": false, "isVentilatorTimerOn": false,
"hasHumidifier": true, "hasHumidifier": true,
"humidifierMode": "manual", "humidifierMode": "manual",

View File

@ -0,0 +1,115 @@
"""The test for the ecobee thermostat switch module."""
import copy
from datetime import datetime, timedelta
from unittest import mock
from unittest.mock import patch
import pytest
from homeassistant.components.ecobee.switch import DATE_FORMAT
from homeassistant.components.switch import DOMAIN, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from .common import setup_platform
from tests.components.ecobee import GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP
VENTILATOR_20MIN_ID = "switch.ecobee_ventilator_20m_timer"
THERMOSTAT_ID = 0
@pytest.fixture(name="data")
def data_fixture():
"""Set up data mock."""
data = mock.Mock()
data.return_value = copy.deepcopy(GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP)
return data
async def test_ventilator_20min_attributes(hass: HomeAssistant) -> None:
"""Test the ventilator switch on home attributes are correct."""
await setup_platform(hass, DOMAIN)
state = hass.states.get(VENTILATOR_20MIN_ID)
assert state.state == "off"
async def test_ventilator_20min_when_on(hass: HomeAssistant, data) -> None:
"""Test the ventilator switch goes on."""
data.return_value["settings"]["ventilatorOffDateTime"] = (
datetime.now() + timedelta(days=1)
).strftime(DATE_FORMAT)
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
await setup_platform(hass, DOMAIN)
state = hass.states.get(VENTILATOR_20MIN_ID)
assert state.state == "on"
data.reset_mock()
async def test_ventilator_20min_when_off(hass: HomeAssistant, data) -> None:
"""Test the ventilator switch goes on."""
data.return_value["settings"]["ventilatorOffDateTime"] = (
datetime.now() - timedelta(days=1)
).strftime(DATE_FORMAT)
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
await setup_platform(hass, DOMAIN)
state = hass.states.get(VENTILATOR_20MIN_ID)
assert state.state == "off"
data.reset_mock()
async def test_ventilator_20min_when_empty(hass: HomeAssistant, data) -> None:
"""Test the ventilator switch goes on."""
data.return_value["settings"]["ventilatorOffDateTime"] = ""
with mock.patch("pyecobee.Ecobee.get_thermostat", data):
await setup_platform(hass, DOMAIN)
state = hass.states.get(VENTILATOR_20MIN_ID)
assert state.state == "off"
data.reset_mock()
async def test_turn_on_20min_ventilator(hass: HomeAssistant) -> None:
"""Test the switch 20 min timer (On)."""
with patch(
"homeassistant.components.ecobee.Ecobee.set_ventilator_timer"
) as mock_set_20min_ventilator:
await setup_platform(hass, DOMAIN)
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: VENTILATOR_20MIN_ID},
blocking=True,
)
await hass.async_block_till_done()
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, True)
async def test_turn_off_20min_ventilator(hass: HomeAssistant) -> None:
"""Test the switch 20 min timer (off)."""
with patch(
"homeassistant.components.ecobee.Ecobee.set_ventilator_timer"
) as mock_set_20min_ventilator:
await setup_platform(hass, DOMAIN)
await hass.services.async_call(
DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: VENTILATOR_20MIN_ID},
blocking=True,
)
await hass.async_block_till_done()
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, False)

View File

@ -3,15 +3,24 @@
import pytest import pytest
from homeassistant.components import history from homeassistant.components import history
from homeassistant.components.recorder import Recorder
from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE from homeassistant.const import CONF_DOMAINS, CONF_ENTITIES, CONF_EXCLUDE, CONF_INCLUDE
from homeassistant.setup import setup_component from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.typing import RecorderInstanceGenerator
@pytest.fixture @pytest.fixture
def hass_history(hass_recorder): async def mock_recorder_before_hass(
"""Home Assistant fixture with history.""" async_setup_recorder_instance: RecorderInstanceGenerator,
hass = hass_recorder() ) -> None:
"""Set up recorder."""
@pytest.fixture
async def hass_history(hass: HomeAssistant, recorder_mock: Recorder) -> None:
"""Home Assistant fixture with history."""
config = history.CONFIG_SCHEMA( config = history.CONFIG_SCHEMA(
{ {
history.DOMAIN: { history.DOMAIN: {
@ -26,6 +35,4 @@ def hass_history(hass_recorder):
} }
} }
) )
assert setup_component(hass, history.DOMAIN, config) assert await async_setup_component(hass, history.DOMAIN, config)
return hass

View File

@ -24,7 +24,6 @@ from tests.components.recorder.common import (
assert_multiple_states_equal_without_context_and_last_changed, assert_multiple_states_equal_without_context_and_last_changed,
assert_states_equal_without_context, assert_states_equal_without_context,
async_wait_recording_done, async_wait_recording_done,
wait_recording_done,
) )
from tests.typing import ClientSessionGenerator from tests.typing import ClientSessionGenerator
@ -39,25 +38,26 @@ def listeners_without_writes(listeners: dict[str, int]) -> dict[str, int]:
@pytest.mark.usefixtures("hass_history") @pytest.mark.usefixtures("hass_history")
def test_setup() -> None: async def test_setup() -> None:
"""Test setup method of history.""" """Test setup method of history."""
# Verification occurs in the fixture # Verification occurs in the fixture
def test_get_significant_states(hass_history) -> None: async def test_get_significant_states(hass: HomeAssistant, hass_history) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
hist = get_significant_states(hass, zero, four, entity_ids=list(states)) hist = get_significant_states(hass, zero, four, entity_ids=list(states))
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_minimal_response(hass_history) -> None: async def test_get_significant_states_minimal_response(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
When minimal responses is set only the first and When minimal responses is set only the first and
@ -67,8 +67,7 @@ def test_get_significant_states_minimal_response(hass_history) -> None:
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
hist = get_significant_states( hist = get_significant_states(
hass, zero, four, minimal_response=True, entity_ids=list(states) hass, zero, four, minimal_response=True, entity_ids=list(states)
) )
@ -122,15 +121,16 @@ def test_get_significant_states_minimal_response(hass_history) -> None:
) )
def test_get_significant_states_with_initial(hass_history) -> None: async def test_get_significant_states_with_initial(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
one_and_half = zero + timedelta(seconds=1.5) one_and_half = zero + timedelta(seconds=1.5)
for entity_id in states: for entity_id in states:
if entity_id == "media_player.test": if entity_id == "media_player.test":
@ -149,15 +149,16 @@ def test_get_significant_states_with_initial(hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_without_initial(hass_history) -> None: async def test_get_significant_states_without_initial(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
one = zero + timedelta(seconds=1) one = zero + timedelta(seconds=1)
one_with_microsecond = zero + timedelta(seconds=1, microseconds=1) one_with_microsecond = zero + timedelta(seconds=1, microseconds=1)
one_and_half = zero + timedelta(seconds=1.5) one_and_half = zero + timedelta(seconds=1.5)
@ -179,10 +180,11 @@ def test_get_significant_states_without_initial(hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_entity_id(hass_history) -> None: async def test_get_significant_states_entity_id(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned for one entity.""" """Test that only significant states are returned for one entity."""
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
del states["media_player.test2"] del states["media_player.test2"]
del states["media_player.test3"] del states["media_player.test3"]
del states["thermostat.test"] del states["thermostat.test"]
@ -193,10 +195,11 @@ def test_get_significant_states_entity_id(hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_multiple_entity_ids(hass_history) -> None: async def test_get_significant_states_multiple_entity_ids(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned for one entity.""" """Test that only significant states are returned for one entity."""
hass = hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
del states["media_player.test2"] del states["media_player.test2"]
del states["media_player.test3"] del states["media_player.test3"]
del states["thermostat.test2"] del states["thermostat.test2"]
@ -211,14 +214,15 @@ def test_get_significant_states_multiple_entity_ids(hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_are_ordered(hass_history) -> None: async def test_get_significant_states_are_ordered(
hass: HomeAssistant, hass_history
) -> None:
"""Test order of results from get_significant_states. """Test order of results from get_significant_states.
When entity ids are given, the results should be returned with the data When entity ids are given, the results should be returned with the data
in the same order. in the same order.
""" """
hass = hass_history zero, four, _states = await async_record_states(hass)
zero, four, _states = record_states(hass)
entity_ids = ["media_player.test", "media_player.test2"] entity_ids = ["media_player.test", "media_player.test2"]
hist = get_significant_states(hass, zero, four, entity_ids) hist = get_significant_states(hass, zero, four, entity_ids)
assert list(hist.keys()) == entity_ids assert list(hist.keys()) == entity_ids
@ -227,15 +231,14 @@ def test_get_significant_states_are_ordered(hass_history) -> None:
assert list(hist.keys()) == entity_ids assert list(hist.keys()) == entity_ids
def test_get_significant_states_only(hass_history) -> None: async def test_get_significant_states_only(hass: HomeAssistant, hass_history) -> None:
"""Test significant states when significant_states_only is set.""" """Test significant states when significant_states_only is set."""
hass = hass_history
entity_id = "sensor.test" entity_id = "sensor.test"
def set_state(state, **kwargs): async def set_state(state, **kwargs):
"""Set the state.""" """Set the state."""
hass.states.set(entity_id, state, **kwargs) hass.states.async_set(entity_id, state, **kwargs)
wait_recording_done(hass) await async_wait_recording_done(hass)
return hass.states.get(entity_id) return hass.states.get(entity_id)
start = dt_util.utcnow() - timedelta(minutes=4) start = dt_util.utcnow() - timedelta(minutes=4)
@ -243,19 +246,19 @@ def test_get_significant_states_only(hass_history) -> None:
states = [] states = []
with freeze_time(start) as freezer: with freeze_time(start) as freezer:
set_state("123", attributes={"attribute": 10.64}) await set_state("123", attributes={"attribute": 10.64})
freezer.move_to(points[0]) freezer.move_to(points[0])
# Attributes are different, state not # Attributes are different, state not
states.append(set_state("123", attributes={"attribute": 21.42})) states.append(await set_state("123", attributes={"attribute": 21.42}))
freezer.move_to(points[1]) freezer.move_to(points[1])
# state is different, attributes not # state is different, attributes not
states.append(set_state("32", attributes={"attribute": 21.42})) states.append(await set_state("32", attributes={"attribute": 21.42}))
freezer.move_to(points[2]) freezer.move_to(points[2])
# everything is different # everything is different
states.append(set_state("412", attributes={"attribute": 54.23})) states.append(await set_state("412", attributes={"attribute": 54.23}))
hist = get_significant_states( hist = get_significant_states(
hass, hass,
@ -288,13 +291,13 @@ def test_get_significant_states_only(hass_history) -> None:
) )
def check_significant_states(hass, zero, four, states, config): async def check_significant_states(hass, zero, four, states, config):
"""Check if significant states are retrieved.""" """Check if significant states are retrieved."""
hist = get_significant_states(hass, zero, four) hist = get_significant_states(hass, zero, four)
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def record_states(hass): async def async_record_states(hass):
"""Record some test states. """Record some test states.
We inject a bunch of state updates from media player, zone and We inject a bunch of state updates from media player, zone and
@ -308,10 +311,10 @@ def record_states(hass):
zone = "zone.home" zone = "zone.home"
script_c = "script.can_cancel_this_one" script_c = "script.can_cancel_this_one"
def set_state(entity_id, state, **kwargs): async def set_state(entity_id, state, **kwargs):
"""Set the state.""" """Set the state."""
hass.states.set(entity_id, state, **kwargs) hass.states.async_set(entity_id, state, **kwargs)
wait_recording_done(hass) await async_wait_recording_done(hass)
return hass.states.get(entity_id) return hass.states.get(entity_id)
zero = dt_util.utcnow() zero = dt_util.utcnow()
@ -323,55 +326,63 @@ def record_states(hass):
states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []}
with freeze_time(one) as freezer: with freeze_time(one) as freezer:
states[mp].append( states[mp].append(
set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) await set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)})
) )
states[mp2].append( states[mp2].append(
set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) await set_state(
mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}
)
) )
states[mp3].append( states[mp3].append(
set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) await set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)})
) )
states[therm].append( states[therm].append(
set_state(therm, 20, attributes={"current_temperature": 19.5}) await set_state(therm, 20, attributes={"current_temperature": 19.5})
) )
freezer.move_to(one + timedelta(microseconds=1)) freezer.move_to(one + timedelta(microseconds=1))
states[mp].append( states[mp].append(
set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) await set_state(
mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}
)
) )
freezer.move_to(two) freezer.move_to(two)
# This state will be skipped only different in time # This state will be skipped only different in time
set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) await set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)})
# This state will be skipped because domain is excluded # This state will be skipped because domain is excluded
set_state(zone, "zoning") await set_state(zone, "zoning")
states[script_c].append( states[script_c].append(
set_state(script_c, "off", attributes={"can_cancel": True}) await set_state(script_c, "off", attributes={"can_cancel": True})
) )
states[therm].append( states[therm].append(
set_state(therm, 21, attributes={"current_temperature": 19.8}) await set_state(therm, 21, attributes={"current_temperature": 19.8})
) )
states[therm2].append( states[therm2].append(
set_state(therm2, 20, attributes={"current_temperature": 19}) await set_state(therm2, 20, attributes={"current_temperature": 19})
) )
freezer.move_to(three) freezer.move_to(three)
states[mp].append( states[mp].append(
set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) await set_state(
mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}
)
) )
states[mp3].append( states[mp3].append(
set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) await set_state(
mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}
)
) )
# Attributes changed even though state is the same # Attributes changed even though state is the same
states[therm].append( states[therm].append(
set_state(therm, 21, attributes={"current_temperature": 20}) await set_state(therm, 21, attributes={"current_temperature": 20})
) )
return zero, four, states return zero, four, states
async def test_fetch_period_api( async def test_fetch_period_api(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history.""" """Test the fetch period view for history."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -383,8 +394,8 @@ async def test_fetch_period_api(
async def test_fetch_period_api_with_use_include_order( async def test_fetch_period_api_with_use_include_order(
recorder_mock: Recorder,
hass: HomeAssistant, hass: HomeAssistant,
recorder_mock: Recorder,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -402,7 +413,7 @@ async def test_fetch_period_api_with_use_include_order(
async def test_fetch_period_api_with_minimal_response( async def test_fetch_period_api_with_minimal_response(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with minimal_response.""" """Test the fetch period view for history with minimal_response."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -444,7 +455,7 @@ async def test_fetch_period_api_with_minimal_response(
async def test_fetch_period_api_with_no_timestamp( async def test_fetch_period_api_with_no_timestamp(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with no timestamp.""" """Test the fetch period view for history with no timestamp."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -454,8 +465,8 @@ async def test_fetch_period_api_with_no_timestamp(
async def test_fetch_period_api_with_include_order( async def test_fetch_period_api_with_include_order(
recorder_mock: Recorder,
hass: HomeAssistant, hass: HomeAssistant,
recorder_mock: Recorder,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
@ -482,7 +493,7 @@ async def test_fetch_period_api_with_include_order(
async def test_entity_ids_limit_via_api( async def test_entity_ids_limit_via_api(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test limiting history to entity_ids.""" """Test limiting history to entity_ids."""
await async_setup_component( await async_setup_component(
@ -508,7 +519,7 @@ async def test_entity_ids_limit_via_api(
async def test_entity_ids_limit_via_api_with_skip_initial_state( async def test_entity_ids_limit_via_api_with_skip_initial_state(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test limiting history to entity_ids with skip_initial_state.""" """Test limiting history to entity_ids with skip_initial_state."""
await async_setup_component( await async_setup_component(
@ -542,7 +553,7 @@ async def test_entity_ids_limit_via_api_with_skip_initial_state(
async def test_fetch_period_api_before_history_started( async def test_fetch_period_api_before_history_started(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history for the far past.""" """Test the fetch period view for history for the far past."""
await async_setup_component( await async_setup_component(
@ -563,7 +574,7 @@ async def test_fetch_period_api_before_history_started(
async def test_fetch_period_api_far_future( async def test_fetch_period_api_far_future(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history for the far future.""" """Test the fetch period view for history for the far future."""
await async_setup_component( await async_setup_component(
@ -584,7 +595,7 @@ async def test_fetch_period_api_far_future(
async def test_fetch_period_api_with_invalid_datetime( async def test_fetch_period_api_with_invalid_datetime(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with an invalid date time.""" """Test the fetch period view for history with an invalid date time."""
await async_setup_component( await async_setup_component(
@ -603,7 +614,7 @@ async def test_fetch_period_api_with_invalid_datetime(
async def test_fetch_period_api_invalid_end_time( async def test_fetch_period_api_invalid_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with an invalid end time.""" """Test the fetch period view for history with an invalid end time."""
await async_setup_component( await async_setup_component(
@ -625,7 +636,7 @@ async def test_fetch_period_api_invalid_end_time(
async def test_entity_ids_limit_via_api_with_end_time( async def test_entity_ids_limit_via_api_with_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test limiting history to entity_ids with end_time.""" """Test limiting history to entity_ids with end_time."""
await async_setup_component( await async_setup_component(
@ -671,7 +682,7 @@ async def test_entity_ids_limit_via_api_with_end_time(
async def test_fetch_period_api_with_no_entity_ids( async def test_fetch_period_api_with_no_entity_ids(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with minimal_response.""" """Test the fetch period view for history with minimal_response."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -724,13 +735,13 @@ async def test_fetch_period_api_with_no_entity_ids(
], ],
) )
async def test_history_with_invalid_entity_ids( async def test_history_with_invalid_entity_ids(
hass: HomeAssistant,
recorder_mock: Recorder,
hass_client: ClientSessionGenerator,
filter_entity_id, filter_entity_id,
status_code, status_code,
response_contains1, response_contains1,
response_contains2, response_contains2,
recorder_mock: Recorder,
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
) -> None: ) -> None:
"""Test sending valid and invalid entity_ids to the API.""" """Test sending valid and invalid entity_ids to the API."""
await async_setup_component( await async_setup_component(

View File

@ -27,7 +27,6 @@ from tests.components.recorder.common import (
async_recorder_block_till_done, async_recorder_block_till_done,
async_wait_recording_done, async_wait_recording_done,
old_db_schema, old_db_schema,
wait_recording_done,
) )
from tests.typing import ClientSessionGenerator, WebSocketGenerator from tests.typing import ClientSessionGenerator, WebSocketGenerator
@ -40,33 +39,34 @@ def db_schema_30():
@pytest.fixture @pytest.fixture
def legacy_hass_history(hass_history): def legacy_hass_history(hass: HomeAssistant, hass_history):
"""Home Assistant fixture to use legacy history recording.""" """Home Assistant fixture to use legacy history recording."""
instance = recorder.get_instance(hass_history) instance = recorder.get_instance(hass)
with patch.object(instance.states_meta_manager, "active", False): with patch.object(instance.states_meta_manager, "active", False):
yield hass_history yield
@pytest.mark.usefixtures("legacy_hass_history") @pytest.mark.usefixtures("legacy_hass_history")
def test_setup() -> None: async def test_setup() -> None:
"""Test setup method of history.""" """Test setup method of history."""
# Verification occurs in the fixture # Verification occurs in the fixture
def test_get_significant_states(legacy_hass_history) -> None: async def test_get_significant_states(hass: HomeAssistant, legacy_hass_history) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = legacy_hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
hist = get_significant_states(hass, zero, four, entity_ids=list(states)) hist = get_significant_states(hass, zero, four, entity_ids=list(states))
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_minimal_response(legacy_hass_history) -> None: async def test_get_significant_states_minimal_response(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
When minimal responses is set only the first and When minimal responses is set only the first and
@ -76,8 +76,7 @@ def test_get_significant_states_minimal_response(legacy_hass_history) -> None:
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = legacy_hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
hist = get_significant_states( hist = get_significant_states(
hass, zero, four, minimal_response=True, entity_ids=list(states) hass, zero, four, minimal_response=True, entity_ids=list(states)
) )
@ -132,15 +131,16 @@ def test_get_significant_states_minimal_response(legacy_hass_history) -> None:
) )
def test_get_significant_states_with_initial(legacy_hass_history) -> None: async def test_get_significant_states_with_initial(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = legacy_hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
one = zero + timedelta(seconds=1) one = zero + timedelta(seconds=1)
one_with_microsecond = zero + timedelta(seconds=1, microseconds=1) one_with_microsecond = zero + timedelta(seconds=1, microseconds=1)
one_and_half = zero + timedelta(seconds=1.5) one_and_half = zero + timedelta(seconds=1.5)
@ -162,15 +162,16 @@ def test_get_significant_states_with_initial(legacy_hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_without_initial(legacy_hass_history) -> None: async def test_get_significant_states_without_initial(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test that only significant states are returned. """Test that only significant states are returned.
We should get back every thermostat change that We should get back every thermostat change that
includes an attribute change, but only the state updates for includes an attribute change, but only the state updates for
media player (attribute changes are not significant and not returned). media player (attribute changes are not significant and not returned).
""" """
hass = legacy_hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
one = zero + timedelta(seconds=1) one = zero + timedelta(seconds=1)
one_with_microsecond = zero + timedelta(seconds=1, microseconds=1) one_with_microsecond = zero + timedelta(seconds=1, microseconds=1)
one_and_half = zero + timedelta(seconds=1.5) one_and_half = zero + timedelta(seconds=1.5)
@ -193,13 +194,13 @@ def test_get_significant_states_without_initial(legacy_hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_entity_id(hass_history) -> None: async def test_get_significant_states_entity_id(
hass: HomeAssistant, hass_history
) -> None:
"""Test that only significant states are returned for one entity.""" """Test that only significant states are returned for one entity."""
hass = hass_history
instance = recorder.get_instance(hass) instance = recorder.get_instance(hass)
with patch.object(instance.states_meta_manager, "active", False): with patch.object(instance.states_meta_manager, "active", False):
zero, four, states = record_states(hass) zero, four, states = await async_record_states(hass)
del states["media_player.test2"] del states["media_player.test2"]
del states["media_player.test3"] del states["media_player.test3"]
del states["thermostat.test"] del states["thermostat.test"]
@ -210,10 +211,11 @@ def test_get_significant_states_entity_id(hass_history) -> None:
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_multiple_entity_ids(legacy_hass_history) -> None: async def test_get_significant_states_multiple_entity_ids(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test that only significant states are returned for one entity.""" """Test that only significant states are returned for one entity."""
hass = legacy_hass_history zero, four, states = await async_record_states(hass)
zero, four, states = record_states(hass)
del states["media_player.test2"] del states["media_player.test2"]
del states["media_player.test3"] del states["media_player.test3"]
del states["thermostat.test2"] del states["thermostat.test2"]
@ -228,14 +230,15 @@ def test_get_significant_states_multiple_entity_ids(legacy_hass_history) -> None
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def test_get_significant_states_are_ordered(legacy_hass_history) -> None: async def test_get_significant_states_are_ordered(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test order of results from get_significant_states. """Test order of results from get_significant_states.
When entity ids are given, the results should be returned with the data When entity ids are given, the results should be returned with the data
in the same order. in the same order.
""" """
hass = legacy_hass_history zero, four, _states = await async_record_states(hass)
zero, four, _states = record_states(hass)
entity_ids = ["media_player.test", "media_player.test2"] entity_ids = ["media_player.test", "media_player.test2"]
hist = get_significant_states(hass, zero, four, entity_ids) hist = get_significant_states(hass, zero, four, entity_ids)
assert list(hist.keys()) == entity_ids assert list(hist.keys()) == entity_ids
@ -244,15 +247,16 @@ def test_get_significant_states_are_ordered(legacy_hass_history) -> None:
assert list(hist.keys()) == entity_ids assert list(hist.keys()) == entity_ids
def test_get_significant_states_only(legacy_hass_history) -> None: async def test_get_significant_states_only(
hass: HomeAssistant, legacy_hass_history
) -> None:
"""Test significant states when significant_states_only is set.""" """Test significant states when significant_states_only is set."""
hass = legacy_hass_history
entity_id = "sensor.test" entity_id = "sensor.test"
def set_state(state, **kwargs): async def set_state(state, **kwargs):
"""Set the state.""" """Set the state."""
hass.states.set(entity_id, state, **kwargs) hass.states.async_set(entity_id, state, **kwargs)
wait_recording_done(hass) await async_wait_recording_done(hass)
return hass.states.get(entity_id) return hass.states.get(entity_id)
start = dt_util.utcnow() - timedelta(minutes=4) start = dt_util.utcnow() - timedelta(minutes=4)
@ -260,19 +264,19 @@ def test_get_significant_states_only(legacy_hass_history) -> None:
states = [] states = []
with freeze_time(start) as freezer: with freeze_time(start) as freezer:
set_state("123", attributes={"attribute": 10.64}) await set_state("123", attributes={"attribute": 10.64})
freezer.move_to(points[0]) freezer.move_to(points[0])
# Attributes are different, state not # Attributes are different, state not
states.append(set_state("123", attributes={"attribute": 21.42})) states.append(await set_state("123", attributes={"attribute": 21.42}))
freezer.move_to(points[1]) freezer.move_to(points[1])
# state is different, attributes not # state is different, attributes not
states.append(set_state("32", attributes={"attribute": 21.42})) states.append(await set_state("32", attributes={"attribute": 21.42}))
freezer.move_to(points[2]) freezer.move_to(points[2])
# everything is different # everything is different
states.append(set_state("412", attributes={"attribute": 54.23})) states.append(await set_state("412", attributes={"attribute": 54.23}))
hist = get_significant_states( hist = get_significant_states(
hass, hass,
@ -311,7 +315,7 @@ def check_significant_states(hass, zero, four, states, config):
assert_dict_of_states_equal_without_context_and_last_changed(states, hist) assert_dict_of_states_equal_without_context_and_last_changed(states, hist)
def record_states(hass): async def async_record_states(hass):
"""Record some test states. """Record some test states.
We inject a bunch of state updates from media player, zone and We inject a bunch of state updates from media player, zone and
@ -325,10 +329,10 @@ def record_states(hass):
zone = "zone.home" zone = "zone.home"
script_c = "script.can_cancel_this_one" script_c = "script.can_cancel_this_one"
def set_state(entity_id, state, **kwargs): async def async_set_state(entity_id, state, **kwargs):
"""Set the state.""" """Set the state."""
hass.states.set(entity_id, state, **kwargs) hass.states.async_set(entity_id, state, **kwargs)
wait_recording_done(hass) await async_wait_recording_done(hass)
return hass.states.get(entity_id) return hass.states.get(entity_id)
zero = dt_util.utcnow() zero = dt_util.utcnow()
@ -340,55 +344,69 @@ def record_states(hass):
states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []} states = {therm: [], therm2: [], mp: [], mp2: [], mp3: [], script_c: []}
with freeze_time(one) as freezer: with freeze_time(one) as freezer:
states[mp].append( states[mp].append(
set_state(mp, "idle", attributes={"media_title": str(sentinel.mt1)}) await async_set_state(
mp, "idle", attributes={"media_title": str(sentinel.mt1)}
)
) )
states[mp2].append( states[mp2].append(
set_state(mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}) await async_set_state(
mp2, "YouTube", attributes={"media_title": str(sentinel.mt2)}
)
) )
states[mp3].append( states[mp3].append(
set_state(mp3, "idle", attributes={"media_title": str(sentinel.mt1)}) await async_set_state(
mp3, "idle", attributes={"media_title": str(sentinel.mt1)}
)
) )
states[therm].append( states[therm].append(
set_state(therm, 20, attributes={"current_temperature": 19.5}) await async_set_state(therm, 20, attributes={"current_temperature": 19.5})
) )
freezer.move_to(one + timedelta(microseconds=1)) freezer.move_to(one + timedelta(microseconds=1))
states[mp].append( states[mp].append(
set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}) await async_set_state(
mp, "YouTube", attributes={"media_title": str(sentinel.mt2)}
)
) )
freezer.move_to(two) freezer.move_to(two)
# This state will be skipped only different in time # This state will be skipped only different in time
set_state(mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}) await async_set_state(
mp, "YouTube", attributes={"media_title": str(sentinel.mt3)}
)
# This state will be skipped because domain is excluded # This state will be skipped because domain is excluded
set_state(zone, "zoning") await async_set_state(zone, "zoning")
states[script_c].append( states[script_c].append(
set_state(script_c, "off", attributes={"can_cancel": True}) await async_set_state(script_c, "off", attributes={"can_cancel": True})
) )
states[therm].append( states[therm].append(
set_state(therm, 21, attributes={"current_temperature": 19.8}) await async_set_state(therm, 21, attributes={"current_temperature": 19.8})
) )
states[therm2].append( states[therm2].append(
set_state(therm2, 20, attributes={"current_temperature": 19}) await async_set_state(therm2, 20, attributes={"current_temperature": 19})
) )
freezer.move_to(three) freezer.move_to(three)
states[mp].append( states[mp].append(
set_state(mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}) await async_set_state(
mp, "Netflix", attributes={"media_title": str(sentinel.mt4)}
)
) )
states[mp3].append( states[mp3].append(
set_state(mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}) await async_set_state(
mp3, "Netflix", attributes={"media_title": str(sentinel.mt3)}
)
) )
# Attributes changed even though state is the same # Attributes changed even though state is the same
states[therm].append( states[therm].append(
set_state(therm, 21, attributes={"current_temperature": 20}) await async_set_state(therm, 21, attributes={"current_temperature": 20})
) )
return zero, four, states return zero, four, states
async def test_fetch_period_api( async def test_fetch_period_api(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history.""" """Test the fetch period view for history."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -402,7 +420,7 @@ async def test_fetch_period_api(
async def test_fetch_period_api_with_minimal_response( async def test_fetch_period_api_with_minimal_response(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with minimal_response.""" """Test the fetch period view for history with minimal_response."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -445,7 +463,7 @@ async def test_fetch_period_api_with_minimal_response(
async def test_fetch_period_api_with_no_timestamp( async def test_fetch_period_api_with_no_timestamp(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history with no timestamp.""" """Test the fetch period view for history with no timestamp."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -457,7 +475,7 @@ async def test_fetch_period_api_with_no_timestamp(
async def test_fetch_period_api_with_include_order( async def test_fetch_period_api_with_include_order(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test the fetch period view for history.""" """Test the fetch period view for history."""
await async_setup_component( await async_setup_component(
@ -481,7 +499,7 @@ async def test_fetch_period_api_with_include_order(
async def test_entity_ids_limit_via_api( async def test_entity_ids_limit_via_api(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test limiting history to entity_ids.""" """Test limiting history to entity_ids."""
await async_setup_component( await async_setup_component(
@ -509,7 +527,7 @@ async def test_entity_ids_limit_via_api(
async def test_entity_ids_limit_via_api_with_skip_initial_state( async def test_entity_ids_limit_via_api_with_skip_initial_state(
recorder_mock: Recorder, hass: HomeAssistant, hass_client: ClientSessionGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_client: ClientSessionGenerator
) -> None: ) -> None:
"""Test limiting history to entity_ids with skip_initial_state.""" """Test limiting history to entity_ids with skip_initial_state."""
await async_setup_component( await async_setup_component(
@ -545,7 +563,7 @@ async def test_entity_ids_limit_via_api_with_skip_initial_state(
async def test_history_during_period( async def test_history_during_period(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period.""" """Test history_during_period."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -693,7 +711,7 @@ async def test_history_during_period(
async def test_history_during_period_impossible_conditions( async def test_history_during_period_impossible_conditions(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period returns when condition cannot be true.""" """Test history_during_period returns when condition cannot be true."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -757,10 +775,10 @@ async def test_history_during_period_impossible_conditions(
"time_zone", ["UTC", "Europe/Berlin", "America/Chicago", "US/Hawaii"] "time_zone", ["UTC", "Europe/Berlin", "America/Chicago", "US/Hawaii"]
) )
async def test_history_during_period_significant_domain( async def test_history_during_period_significant_domain(
time_zone,
recorder_mock: Recorder,
hass: HomeAssistant, hass: HomeAssistant,
recorder_mock: Recorder,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
time_zone,
) -> None: ) -> None:
"""Test history_during_period with climate domain.""" """Test history_during_period with climate domain."""
hass.config.set_time_zone(time_zone) hass.config.set_time_zone(time_zone)
@ -941,7 +959,7 @@ async def test_history_during_period_significant_domain(
async def test_history_during_period_bad_start_time( async def test_history_during_period_bad_start_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period bad state time.""" """Test history_during_period bad state time."""
await async_setup_component( await async_setup_component(
@ -966,7 +984,7 @@ async def test_history_during_period_bad_start_time(
async def test_history_during_period_bad_end_time( async def test_history_during_period_bad_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period bad end time.""" """Test history_during_period bad end time."""
now = dt_util.utcnow() now = dt_util.utcnow()

View File

@ -39,7 +39,7 @@ def test_setup() -> None:
async def test_history_during_period( async def test_history_during_period(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period.""" """Test history_during_period."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -173,7 +173,7 @@ async def test_history_during_period(
async def test_history_during_period_impossible_conditions( async def test_history_during_period_impossible_conditions(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period returns when condition cannot be true.""" """Test history_during_period returns when condition cannot be true."""
await async_setup_component(hass, "history", {}) await async_setup_component(hass, "history", {})
@ -235,10 +235,10 @@ async def test_history_during_period_impossible_conditions(
"time_zone", ["UTC", "Europe/Berlin", "America/Chicago", "US/Hawaii"] "time_zone", ["UTC", "Europe/Berlin", "America/Chicago", "US/Hawaii"]
) )
async def test_history_during_period_significant_domain( async def test_history_during_period_significant_domain(
time_zone,
recorder_mock: Recorder,
hass: HomeAssistant, hass: HomeAssistant,
recorder_mock: Recorder,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
time_zone,
) -> None: ) -> None:
"""Test history_during_period with climate domain.""" """Test history_during_period with climate domain."""
hass.config.set_time_zone(time_zone) hass.config.set_time_zone(time_zone)
@ -403,7 +403,7 @@ async def test_history_during_period_significant_domain(
async def test_history_during_period_bad_start_time( async def test_history_during_period_bad_start_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period bad state time.""" """Test history_during_period bad state time."""
await async_setup_component( await async_setup_component(
@ -427,7 +427,7 @@ async def test_history_during_period_bad_start_time(
async def test_history_during_period_bad_end_time( async def test_history_during_period_bad_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period bad end time.""" """Test history_during_period bad end time."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -454,7 +454,7 @@ async def test_history_during_period_bad_end_time(
async def test_history_stream_historical_only( async def test_history_stream_historical_only(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream.""" """Test history stream."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -525,7 +525,7 @@ async def test_history_stream_historical_only(
async def test_history_stream_significant_domain_historical_only( async def test_history_stream_significant_domain_historical_only(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test the stream with climate domain with historical states only.""" """Test the stream with climate domain with historical states only."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -726,7 +726,7 @@ async def test_history_stream_significant_domain_historical_only(
async def test_history_stream_bad_start_time( async def test_history_stream_bad_start_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream bad state time.""" """Test history stream bad state time."""
await async_setup_component( await async_setup_component(
@ -750,7 +750,7 @@ async def test_history_stream_bad_start_time(
async def test_history_stream_end_time_before_start_time( async def test_history_stream_end_time_before_start_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with an end_time before the start_time.""" """Test history stream with an end_time before the start_time."""
end_time = dt_util.utcnow() - timedelta(seconds=2) end_time = dt_util.utcnow() - timedelta(seconds=2)
@ -778,7 +778,7 @@ async def test_history_stream_end_time_before_start_time(
async def test_history_stream_bad_end_time( async def test_history_stream_bad_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream bad end time.""" """Test history stream bad end time."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -805,7 +805,7 @@ async def test_history_stream_bad_end_time(
async def test_history_stream_live_no_attributes_minimal_response( async def test_history_stream_live_no_attributes_minimal_response(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data and no_attributes and minimal_response.""" """Test history stream with history and live data and no_attributes and minimal_response."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -882,7 +882,7 @@ async def test_history_stream_live_no_attributes_minimal_response(
async def test_history_stream_live( async def test_history_stream_live(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data.""" """Test history stream with history and live data."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -985,7 +985,7 @@ async def test_history_stream_live(
async def test_history_stream_live_minimal_response( async def test_history_stream_live_minimal_response(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data and minimal_response.""" """Test history stream with history and live data and minimal_response."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1082,7 +1082,7 @@ async def test_history_stream_live_minimal_response(
async def test_history_stream_live_no_attributes( async def test_history_stream_live_no_attributes(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data and no_attributes.""" """Test history stream with history and live data and no_attributes."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1163,7 +1163,7 @@ async def test_history_stream_live_no_attributes(
async def test_history_stream_live_no_attributes_minimal_response_specific_entities( async def test_history_stream_live_no_attributes_minimal_response_specific_entities(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data and no_attributes and minimal_response with specific entities.""" """Test history stream with history and live data and no_attributes and minimal_response with specific entities."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1241,7 +1241,7 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit
async def test_history_stream_live_with_future_end_time( async def test_history_stream_live_with_future_end_time(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream with history and live data with future end time.""" """Test history stream with history and live data with future end time."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1334,8 +1334,8 @@ async def test_history_stream_live_with_future_end_time(
@pytest.mark.parametrize("include_start_time_state", [True, False]) @pytest.mark.parametrize("include_start_time_state", [True, False])
async def test_history_stream_before_history_starts( async def test_history_stream_before_history_starts(
recorder_mock: Recorder,
hass: HomeAssistant, hass: HomeAssistant,
recorder_mock: Recorder,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
include_start_time_state, include_start_time_state,
) -> None: ) -> None:
@ -1385,7 +1385,7 @@ async def test_history_stream_before_history_starts(
async def test_history_stream_for_entity_with_no_possible_changes( async def test_history_stream_for_entity_with_no_possible_changes(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream for future with no possible changes where end time is less than or equal to now.""" """Test history stream for future with no possible changes where end time is less than or equal to now."""
await async_setup_component( await async_setup_component(
@ -1436,7 +1436,7 @@ async def test_history_stream_for_entity_with_no_possible_changes(
async def test_overflow_queue( async def test_overflow_queue(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test overflowing the history stream queue.""" """Test overflowing the history stream queue."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1513,7 +1513,7 @@ async def test_overflow_queue(
async def test_history_during_period_for_invalid_entity_ids( async def test_history_during_period_for_invalid_entity_ids(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period for valid and invalid entity ids.""" """Test history_during_period for valid and invalid entity ids."""
now = dt_util.utcnow() now = dt_util.utcnow()
@ -1656,7 +1656,7 @@ async def test_history_during_period_for_invalid_entity_ids(
async def test_history_stream_for_invalid_entity_ids( async def test_history_stream_for_invalid_entity_ids(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream for invalid and valid entity ids.""" """Test history stream for invalid and valid entity ids."""
@ -1824,7 +1824,7 @@ async def test_history_stream_for_invalid_entity_ids(
async def test_history_stream_historical_only_with_start_time_state_past( async def test_history_stream_historical_only_with_start_time_state_past(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history stream.""" """Test history stream."""
await async_setup_component( await async_setup_component(

View File

@ -24,7 +24,7 @@ def db_schema_32():
async def test_history_during_period( async def test_history_during_period(
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, recorder_mock: Recorder, hass_ws_client: WebSocketGenerator
) -> None: ) -> None:
"""Test history_during_period.""" """Test history_during_period."""
now = dt_util.utcnow() now = dt_util.utcnow()

View File

@ -198,7 +198,7 @@ async def test_shutdown_closes_connections(
hass.set_state(CoreState.not_running) hass.set_state(CoreState.not_running)
instance = get_instance(hass) instance = recorder.get_instance(hass)
await instance.async_db_ready await instance.async_db_ready
await hass.async_block_till_done() await hass.async_block_till_done()
pool = instance.engine.pool pool = instance.engine.pool

View File

@ -526,6 +526,7 @@ async def hass(
load_registries: bool, load_registries: bool,
hass_storage: dict[str, Any], hass_storage: dict[str, Any],
request: pytest.FixtureRequest, request: pytest.FixtureRequest,
mock_recorder_before_hass: None,
) -> AsyncGenerator[HomeAssistant, None]: ) -> AsyncGenerator[HomeAssistant, None]:
"""Create a test instance of Home Assistant.""" """Create a test instance of Home Assistant."""
@ -1577,6 +1578,15 @@ async def recorder_mock(
return await async_setup_recorder_instance(hass, recorder_config) return await async_setup_recorder_instance(hass, recorder_config)
@pytest.fixture
def mock_recorder_before_hass() -> None:
"""Mock the recorder.
Override or parametrize this fixture with a fixture that mocks the recorder,
in the tests that need to test the recorder.
"""
@pytest.fixture(name="enable_bluetooth") @pytest.fixture(name="enable_bluetooth")
async def mock_enable_bluetooth( async def mock_enable_bluetooth(
hass: HomeAssistant, hass: HomeAssistant,