pytest-homeassistant-custom-component 0.13.332__tar.gz → 0.13.334__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_homeassistant_custom_component-0.13.332/src/pytest_homeassistant_custom_component.egg-info → pytest_homeassistant_custom_component-0.13.334}/PKG-INFO +9 -7
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/README.md +1 -1
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/common.py +65 -22
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/common.py +84 -48
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/common.py +1 -3
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/const.py +2 -2
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_json.py +0 -2
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_recorder.py +0 -2
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/patch_time.py +0 -2
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/plugins.py +43 -21
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/syrupy.py +5 -5
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/typing.py +0 -2
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334/src/pytest_homeassistant_custom_component.egg-info}/PKG-INFO +9 -7
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/requires.txt +7 -5
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/LICENSE +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/LICENSE_HA_CORE.md +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/setup.cfg +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/setup.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/asyncio_legacy.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/diagnostics/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/components/recorder/db_schema_0.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/ignore_uncaught_exceptions.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/test_util/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/test_util/aiohttp.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/custom_components/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component/testing_config/custom_components/test_constant_deprecation/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/SOURCES.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/dependency_links.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/entry_points.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/src/pytest_homeassistant_custom_component.egg-info/top_level.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_common.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_config_flow.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_diagnostics.py +0 -0
- {pytest_homeassistant_custom_component-0.13.332 → pytest_homeassistant_custom_component-0.13.334}/tests/test_sensor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-homeassistant-custom-component
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.334
|
|
4
4
|
Summary: Experimental package to automatically extract test plugins for Home Assistant custom components
|
|
5
5
|
Home-page: https://github.com/MatthewFlamm/pytest-homeassistant-custom-component
|
|
6
6
|
Author: Matthew Flamm
|
|
@@ -18,12 +18,12 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
License-File: LICENSE_HA_CORE.md
|
|
20
20
|
Requires-Dist: sqlalchemy
|
|
21
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: ast-serialize==0.3.0
|
|
22
|
+
Requires-Dist: coverage==7.14.0
|
|
22
23
|
Requires-Dist: freezegun==1.5.5
|
|
23
24
|
Requires-Dist: license-expression==30.4.3
|
|
24
25
|
Requires-Dist: mock-open==1.4.0
|
|
25
|
-
Requires-Dist: pydantic==2.13.
|
|
26
|
-
Requires-Dist: pylint-per-file-ignores==3.2.1
|
|
26
|
+
Requires-Dist: pydantic==2.13.4
|
|
27
27
|
Requires-Dist: pipdeptree==2.26.1
|
|
28
28
|
Requires-Dist: pytest-asyncio==1.3.0
|
|
29
29
|
Requires-Dist: pytest-aiohttp==1.1.0
|
|
@@ -36,11 +36,13 @@ Requires-Dist: pytest-unordered==0.7.0
|
|
|
36
36
|
Requires-Dist: pytest-picked==0.5.1
|
|
37
37
|
Requires-Dist: pytest-xdist==3.8.0
|
|
38
38
|
Requires-Dist: pytest==9.0.3
|
|
39
|
+
Requires-Dist: requests==2.34.2
|
|
39
40
|
Requires-Dist: requests-mock==1.12.1
|
|
40
41
|
Requires-Dist: respx==0.23.1
|
|
41
|
-
Requires-Dist: syrupy==5.
|
|
42
|
+
Requires-Dist: syrupy==5.2.0
|
|
42
43
|
Requires-Dist: tqdm==4.67.1
|
|
43
|
-
Requires-Dist:
|
|
44
|
+
Requires-Dist: unidiff==0.7.5
|
|
45
|
+
Requires-Dist: homeassistant==2026.6.0b0
|
|
44
46
|
Requires-Dist: aiohasupervisor==0.4.3
|
|
45
47
|
Requires-Dist: SQLAlchemy==2.0.49
|
|
46
48
|
Requires-Dist: paho-mqtt==2.1.0
|
|
@@ -59,7 +61,7 @@ Dynamic: summary
|
|
|
59
61
|
|
|
60
62
|
# pytest-homeassistant-custom-component
|
|
61
63
|
|
|
62
|
-

|
|
63
65
|
|
|
64
66
|
Package to automatically extract testing plugins from Home Assistant for custom component testing.
|
|
65
67
|
The goal is to provide the same functionality as the tests in home-assistant/core.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pytest-homeassistant-custom-component
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
Package to automatically extract testing plugins from Home Assistant for custom component testing.
|
|
6
6
|
The goal is to provide the same functionality as the tests in home-assistant/core.
|
|
@@ -4,8 +4,6 @@ Test the helper method for writing tests.
|
|
|
4
4
|
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
import asyncio
|
|
10
8
|
from collections.abc import (
|
|
11
9
|
AsyncGenerator,
|
|
@@ -30,12 +28,13 @@ import pathlib
|
|
|
30
28
|
import time
|
|
31
29
|
import traceback
|
|
32
30
|
from types import FrameType, ModuleType
|
|
33
|
-
from typing import Any, Literal, NoReturn
|
|
31
|
+
from typing import TYPE_CHECKING, Any, Literal, NoReturn
|
|
34
32
|
from unittest.mock import AsyncMock, Mock, patch
|
|
35
33
|
|
|
36
34
|
from aiohttp.test_utils import unused_port as get_test_instance_port
|
|
37
35
|
from annotatedyaml import load_yaml_dict, loader as yaml_loader
|
|
38
36
|
import attr
|
|
37
|
+
from paho.mqtt.client import MQTTMessage
|
|
39
38
|
import pytest
|
|
40
39
|
from syrupy.assertion import SnapshotAssertion
|
|
41
40
|
import voluptuous as vol
|
|
@@ -106,6 +105,7 @@ from homeassistant.helpers.entity_platform import (
|
|
|
106
105
|
)
|
|
107
106
|
from homeassistant.helpers.json import JSONEncoder, _orjson_default_encoder, json_dumps
|
|
108
107
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
108
|
+
from homeassistant.setup import async_setup_component
|
|
109
109
|
from homeassistant.util import dt as dt_util, ulid as ulid_util, uuid as uuid_util
|
|
110
110
|
from homeassistant.util.async_ import (
|
|
111
111
|
_SHUTDOWN_RUN_CALLBACK_THREADSAFE,
|
|
@@ -128,6 +128,9 @@ from .testing_config.custom_components.test_constant_deprecation import (
|
|
|
128
128
|
import_deprecated_constant,
|
|
129
129
|
)
|
|
130
130
|
|
|
131
|
+
if TYPE_CHECKING:
|
|
132
|
+
import paho.mqtt.client as mqtt
|
|
133
|
+
|
|
131
134
|
__all__ = [
|
|
132
135
|
"async_get_device_automation_capabilities",
|
|
133
136
|
"get_test_instance_port",
|
|
@@ -327,7 +330,8 @@ async def async_test_home_assistant(
|
|
|
327
330
|
StoreWithoutWriteLoad,
|
|
328
331
|
),
|
|
329
332
|
patch(
|
|
330
|
-
|
|
333
|
+
# Floor & label registry are different
|
|
334
|
+
"homeassistant.helpers.storage.Store",
|
|
331
335
|
StoreWithoutWriteLoad,
|
|
332
336
|
),
|
|
333
337
|
patch(
|
|
@@ -458,13 +462,9 @@ def async_fire_mqtt_message(
|
|
|
458
462
|
payload: bytes | str,
|
|
459
463
|
qos: int = 0,
|
|
460
464
|
retain: bool = False,
|
|
465
|
+
properties: mqtt.Properties | None = None,
|
|
461
466
|
) -> None:
|
|
462
467
|
"""Fire the MQTT message."""
|
|
463
|
-
# Local import to avoid processing MQTT modules when running a testcase
|
|
464
|
-
# which does not use MQTT.
|
|
465
|
-
|
|
466
|
-
from paho.mqtt.client import MQTTMessage # noqa: PLC0415
|
|
467
|
-
|
|
468
468
|
from homeassistant.components.mqtt import MqttData # noqa: PLC0415
|
|
469
469
|
|
|
470
470
|
if isinstance(payload, str):
|
|
@@ -475,6 +475,7 @@ def async_fire_mqtt_message(
|
|
|
475
475
|
msg.qos = qos
|
|
476
476
|
msg.retain = retain
|
|
477
477
|
msg.timestamp = time.monotonic()
|
|
478
|
+
msg.properties = properties
|
|
478
479
|
|
|
479
480
|
mqtt_data: MqttData = hass.data["mqtt"]
|
|
480
481
|
assert mqtt_data.client
|
|
@@ -773,7 +774,6 @@ def mock_device_registry(
|
|
|
773
774
|
registry.deleted_devices = dr.DeviceRegistryItems()
|
|
774
775
|
|
|
775
776
|
hass.data[dr.DATA_REGISTRY] = registry
|
|
776
|
-
dr.async_get.cache_clear()
|
|
777
777
|
return registry
|
|
778
778
|
|
|
779
779
|
|
|
@@ -1184,8 +1184,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|
|
1184
1184
|
async def start_reconfigure_flow(
|
|
1185
1185
|
self,
|
|
1186
1186
|
hass: HomeAssistant,
|
|
1187
|
-
*,
|
|
1188
|
-
show_advanced_options: bool = False,
|
|
1189
1187
|
) -> ConfigFlowResult:
|
|
1190
1188
|
"""Start a reconfiguration flow."""
|
|
1191
1189
|
if self.entry_id not in hass.config_entries._entries:
|
|
@@ -1197,7 +1195,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|
|
1197
1195
|
context={
|
|
1198
1196
|
"source": config_entries.SOURCE_RECONFIGURE,
|
|
1199
1197
|
"entry_id": self.entry_id,
|
|
1200
|
-
"show_advanced_options": show_advanced_options,
|
|
1201
1198
|
},
|
|
1202
1199
|
)
|
|
1203
1200
|
|
|
@@ -1205,8 +1202,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|
|
1205
1202
|
self,
|
|
1206
1203
|
hass: HomeAssistant,
|
|
1207
1204
|
subentry_id: str,
|
|
1208
|
-
*,
|
|
1209
|
-
show_advanced_options: bool = False,
|
|
1210
1205
|
) -> ConfigFlowResult:
|
|
1211
1206
|
"""Start a subentry reconfiguration flow."""
|
|
1212
1207
|
if self.entry_id not in hass.config_entries._entries:
|
|
@@ -1220,7 +1215,6 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|
|
1220
1215
|
context={
|
|
1221
1216
|
"source": config_entries.SOURCE_RECONFIGURE,
|
|
1222
1217
|
"subentry_id": subentry_id,
|
|
1223
|
-
"show_advanced_options": show_advanced_options,
|
|
1224
1218
|
},
|
|
1225
1219
|
)
|
|
1226
1220
|
|
|
@@ -1262,7 +1256,7 @@ def patch_yaml_files(files_dict, endswith=True):
|
|
|
1262
1256
|
if fname in files_dict:
|
|
1263
1257
|
_LOGGER.debug("patch_yaml_files match %s", fname)
|
|
1264
1258
|
res = StringIO(files_dict[fname])
|
|
1265
|
-
|
|
1259
|
+
res.name = fname
|
|
1266
1260
|
return res
|
|
1267
1261
|
|
|
1268
1262
|
# Match using endswith
|
|
@@ -1270,7 +1264,7 @@ def patch_yaml_files(files_dict, endswith=True):
|
|
|
1270
1264
|
if fname.endswith(ends):
|
|
1271
1265
|
_LOGGER.debug("patch_yaml_files end match %s: %s", ends, fname)
|
|
1272
1266
|
res = StringIO(files_dict[ends])
|
|
1273
|
-
|
|
1267
|
+
res.name = fname
|
|
1274
1268
|
return res
|
|
1275
1269
|
|
|
1276
1270
|
# Fallback for hass.components (i.e. services.yaml)
|
|
@@ -1458,12 +1452,12 @@ class MockEntity(entity.Entity):
|
|
|
1458
1452
|
|
|
1459
1453
|
@property
|
|
1460
1454
|
def entity_registry_enabled_default(self) -> bool:
|
|
1461
|
-
"""Return if the entity should be enabled when first added
|
|
1455
|
+
"""Return if the entity should be enabled in the registry when first added."""
|
|
1462
1456
|
return self._handle("entity_registry_enabled_default")
|
|
1463
1457
|
|
|
1464
1458
|
@property
|
|
1465
1459
|
def entity_registry_visible_default(self) -> bool:
|
|
1466
|
-
"""Return if the entity should be visible when first added
|
|
1460
|
+
"""Return if the entity should be visible in the registry when first added."""
|
|
1467
1461
|
return self._handle("entity_registry_visible_default")
|
|
1468
1462
|
|
|
1469
1463
|
@property
|
|
@@ -1639,7 +1633,8 @@ def mock_integration(
|
|
|
1639
1633
|
|
|
1640
1634
|
def mock_import_platform(platform_name: str) -> NoReturn:
|
|
1641
1635
|
raise ImportError(
|
|
1642
|
-
|
|
1636
|
+
"Mocked unable to import platform"
|
|
1637
|
+
f" '{integration.pkg_path}.{platform_name}'",
|
|
1643
1638
|
name=f"{integration.pkg_path}.{platform_name}",
|
|
1644
1639
|
)
|
|
1645
1640
|
|
|
@@ -1864,6 +1859,8 @@ def import_and_test_deprecated_alias(
|
|
|
1864
1859
|
alias_name: str,
|
|
1865
1860
|
replacement: Any,
|
|
1866
1861
|
breaks_in_ha_version: str,
|
|
1862
|
+
*,
|
|
1863
|
+
replacement_name: str | None = None,
|
|
1867
1864
|
) -> None:
|
|
1868
1865
|
"""Import and test deprecated alias replaced by a value.
|
|
1869
1866
|
|
|
@@ -1873,7 +1870,9 @@ def import_and_test_deprecated_alias(
|
|
|
1873
1870
|
- Assert the deprecated alias is included in the modules.__dir__()
|
|
1874
1871
|
- Assert the deprecated alias is included in the modules.__all__()
|
|
1875
1872
|
"""
|
|
1876
|
-
replacement_name =
|
|
1873
|
+
replacement_name = (
|
|
1874
|
+
replacement_name or f"{replacement.__module__}.{replacement.__name__}"
|
|
1875
|
+
)
|
|
1877
1876
|
value = import_deprecated_constant(module, alias_name)
|
|
1878
1877
|
assert value == replacement
|
|
1879
1878
|
assert (
|
|
@@ -2033,3 +2032,47 @@ def get_sensor_display_state(
|
|
|
2033
2032
|
numerical_value = float(value)
|
|
2034
2033
|
value = f"{numerical_value:z.{precision}f}"
|
|
2035
2034
|
return value
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
async def assert_platform_setup_creates_issue(
|
|
2038
|
+
hass: HomeAssistant,
|
|
2039
|
+
platform_domain: str,
|
|
2040
|
+
integration_domain: str,
|
|
2041
|
+
issue_registry: ir.IssueRegistry,
|
|
2042
|
+
caplog: pytest.LogCaptureFixture,
|
|
2043
|
+
) -> None:
|
|
2044
|
+
"""Assert that setting up a platform creates an issue."""
|
|
2045
|
+
caplog.clear()
|
|
2046
|
+
with assert_setup_component(1, platform_domain):
|
|
2047
|
+
assert await async_setup_component(
|
|
2048
|
+
hass,
|
|
2049
|
+
platform_domain,
|
|
2050
|
+
{platform_domain: {"platform": integration_domain}},
|
|
2051
|
+
)
|
|
2052
|
+
await hass.async_block_till_done()
|
|
2053
|
+
await hass.async_start()
|
|
2054
|
+
await hass.async_block_till_done()
|
|
2055
|
+
|
|
2056
|
+
assert len(hass.states.async_all(platform_domain)) == 0
|
|
2057
|
+
assert (
|
|
2058
|
+
f"Configuring the {integration_domain} integration under the {platform_domain} platform key is not"
|
|
2059
|
+
f" supported, it must be configured under its own {integration_domain} key instead"
|
|
2060
|
+
in caplog.text
|
|
2061
|
+
)
|
|
2062
|
+
|
|
2063
|
+
issue = issue_registry.async_get_issue(
|
|
2064
|
+
"homeassistant",
|
|
2065
|
+
f"platform_integration_no_support_{platform_domain}_{integration_domain}",
|
|
2066
|
+
)
|
|
2067
|
+
|
|
2068
|
+
assert issue
|
|
2069
|
+
assert issue.issue_domain == integration_domain
|
|
2070
|
+
assert issue.learn_more_url is not None
|
|
2071
|
+
assert issue.translation_key == "platform_config_not_supported"
|
|
2072
|
+
assert issue.severity == ir.IssueSeverity.ERROR
|
|
2073
|
+
assert issue.translation_placeholders == {
|
|
2074
|
+
"platform_domain": platform_domain,
|
|
2075
|
+
"integration_domain": integration_domain,
|
|
2076
|
+
"platform_key": f"platform: {integration_domain}",
|
|
2077
|
+
"yaml_example": f"```yaml\n{platform_domain}:\n - platform: {integration_domain}\n```",
|
|
2078
|
+
}
|
|
@@ -223,7 +223,10 @@ class BasicTriggerStateDescription(TypedDict):
|
|
|
223
223
|
|
|
224
224
|
|
|
225
225
|
class TriggerStateDescription(BasicTriggerStateDescription):
|
|
226
|
-
"""Test state and expected service call count
|
|
226
|
+
"""Test state and expected service call count.
|
|
227
|
+
|
|
228
|
+
Covers both included and excluded entities.
|
|
229
|
+
"""
|
|
227
230
|
|
|
228
231
|
excluded_state: StateDescription # State for entities not meant to be targeted
|
|
229
232
|
# State for the *other* targeted entities (the ones not under direct test).
|
|
@@ -241,7 +244,9 @@ class ConditionStateDescription(TypedDict):
|
|
|
241
244
|
excluded_state: StateDescription # State for entities not meant to be targeted
|
|
242
245
|
|
|
243
246
|
condition_true: bool # If the condition is expected to evaluate to true
|
|
244
|
-
|
|
247
|
+
# If the condition is expected to evaluate to true
|
|
248
|
+
# for the first targeted entity
|
|
249
|
+
condition_true_first_entity: bool
|
|
245
250
|
|
|
246
251
|
|
|
247
252
|
def _parametrize_condition_states(
|
|
@@ -840,7 +845,7 @@ def parametrize_numerical_attribute_changed_trigger_states(
|
|
|
840
845
|
attribute_value_scale: float = 1.0,
|
|
841
846
|
attribute_required: bool = False,
|
|
842
847
|
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
843
|
-
"""Parametrize states
|
|
848
|
+
"""Parametrize states for numerical-changed triggers.
|
|
844
849
|
|
|
845
850
|
Generates state sequences for a trigger that fires whenever an attribute
|
|
846
851
|
crosses or matches a "changed" threshold (modes "any" / "above" / "below").
|
|
@@ -986,7 +991,7 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
|
|
986
991
|
attribute_value_scale: float = 1.0,
|
|
987
992
|
attribute_required: bool = False,
|
|
988
993
|
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
989
|
-
"""Parametrize states
|
|
994
|
+
"""Parametrize states for numerical crossed-threshold triggers.
|
|
990
995
|
|
|
991
996
|
Generates state sequences for a trigger that fires when an attribute
|
|
992
997
|
crosses a threshold boundary. The trigger is exercised across four
|
|
@@ -1061,8 +1066,8 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
|
|
1061
1066
|
threshold_unit,
|
|
1062
1067
|
),
|
|
1063
1068
|
target_states=[
|
|
1064
|
-
(state, {attribute:
|
|
1065
|
-
(state, {attribute:
|
|
1069
|
+
(state, {attribute: 10 * s} | unit_attributes),
|
|
1070
|
+
(state, {attribute: 90 * s} | unit_attributes),
|
|
1066
1071
|
],
|
|
1067
1072
|
other_states=[
|
|
1068
1073
|
other_invalid_attr,
|
|
@@ -1091,8 +1096,8 @@ def parametrize_numerical_attribute_crossed_threshold_trigger_states(
|
|
|
1091
1096
|
],
|
|
1092
1097
|
other_states=[
|
|
1093
1098
|
other_invalid_attr,
|
|
1094
|
-
(state, {attribute:
|
|
1095
|
-
(state, {attribute:
|
|
1099
|
+
(state, {attribute: 10 * s} | unit_attributes),
|
|
1100
|
+
(state, {attribute: 90 * s} | unit_attributes),
|
|
1096
1101
|
],
|
|
1097
1102
|
extra_excluded_states=extra_excluded_states,
|
|
1098
1103
|
required_filter_attributes=required_filter_attributes,
|
|
@@ -1154,11 +1159,11 @@ def parametrize_numerical_state_value_changed_trigger_states(
|
|
|
1154
1159
|
trigger_options: dict[str, Any] | None = None,
|
|
1155
1160
|
unit_attributes: dict | None = None,
|
|
1156
1161
|
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
1157
|
-
"""Parametrize states
|
|
1162
|
+
"""Parametrize states for numerical state-value changed triggers.
|
|
1158
1163
|
|
|
1159
|
-
Unlike parametrize_numerical_attribute_changed_trigger_states,
|
|
1160
|
-
entities where the tracked numerical value is in
|
|
1161
|
-
entities), not in an attribute.
|
|
1164
|
+
Unlike parametrize_numerical_attribute_changed_trigger_states,
|
|
1165
|
+
this is for entities where the tracked numerical value is in
|
|
1166
|
+
state.state (e.g. sensor entities), not in an attribute.
|
|
1162
1167
|
"""
|
|
1163
1168
|
from homeassistant.const import ATTR_DEVICE_CLASS # noqa: PLC0415
|
|
1164
1169
|
|
|
@@ -1235,7 +1240,7 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
|
|
|
1235
1240
|
trigger_options: dict[str, Any] | None = None,
|
|
1236
1241
|
unit_attributes: dict | None = None,
|
|
1237
1242
|
) -> list[tuple[str, dict[str, Any], list[TriggerStateDescription]]]:
|
|
1238
|
-
"""Parametrize states
|
|
1243
|
+
"""Parametrize states for numerical state-value crossed threshold triggers.
|
|
1239
1244
|
|
|
1240
1245
|
Unlike parametrize_numerical_attribute_crossed_threshold_trigger_states,
|
|
1241
1246
|
this is for entities where the tracked numerical value is in state.state
|
|
@@ -1261,7 +1266,7 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
|
|
|
1261
1266
|
},
|
|
1262
1267
|
threshold_unit,
|
|
1263
1268
|
),
|
|
1264
|
-
target_states=[("
|
|
1269
|
+
target_states=[("10", unit_attributes), ("90", unit_attributes)],
|
|
1265
1270
|
other_states=[
|
|
1266
1271
|
("none", unit_attributes),
|
|
1267
1272
|
("0", unit_attributes),
|
|
@@ -1286,8 +1291,8 @@ def parametrize_numerical_state_value_crossed_threshold_trigger_states(
|
|
|
1286
1291
|
target_states=[("0", unit_attributes), ("100", unit_attributes)],
|
|
1287
1292
|
other_states=[
|
|
1288
1293
|
("none", unit_attributes),
|
|
1289
|
-
("
|
|
1290
|
-
("
|
|
1294
|
+
("10", unit_attributes),
|
|
1295
|
+
("90", unit_attributes),
|
|
1291
1296
|
],
|
|
1292
1297
|
required_filter_attributes=required_filter_attributes,
|
|
1293
1298
|
trigger_from_none=False,
|
|
@@ -1336,7 +1341,7 @@ async def arm_trigger(
|
|
|
1336
1341
|
trigger_target: dict,
|
|
1337
1342
|
calls: list[str],
|
|
1338
1343
|
) -> None:
|
|
1339
|
-
"""Arm the
|
|
1344
|
+
"""Arm the trigger and record fired entity_ids in calls."""
|
|
1340
1345
|
options = {CONF_OPTIONS: {**trigger_options}} if trigger_options is not None else {}
|
|
1341
1346
|
|
|
1342
1347
|
trigger_config = {
|
|
@@ -1485,7 +1490,7 @@ async def _validate_condition_options(
|
|
|
1485
1490
|
*,
|
|
1486
1491
|
valid: bool,
|
|
1487
1492
|
) -> None:
|
|
1488
|
-
"""Assert that a condition accepts or rejects the given options
|
|
1493
|
+
"""Assert that a condition accepts or rejects the given options."""
|
|
1489
1494
|
config: dict[str, Any] = {
|
|
1490
1495
|
CONF_CONDITION: condition,
|
|
1491
1496
|
CONF_TARGET: {ATTR_LABEL_ID: "test_label"},
|
|
@@ -1631,7 +1636,7 @@ async def assert_trigger_options_supported(
|
|
|
1631
1636
|
return {**(base_options or {}), **extra}
|
|
1632
1637
|
|
|
1633
1638
|
# Behavior
|
|
1634
|
-
for behavior in ("
|
|
1639
|
+
for behavior in ("each", "first", "all"):
|
|
1635
1640
|
await _validate_trigger_options(
|
|
1636
1641
|
hass, trigger, _merge({"behavior": behavior}), valid=supports_behavior
|
|
1637
1642
|
)
|
|
@@ -1749,7 +1754,7 @@ async def assert_condition_behavior_all(
|
|
|
1749
1754
|
assert cond.async_check() == state["condition_true"]
|
|
1750
1755
|
|
|
1751
1756
|
|
|
1752
|
-
async def
|
|
1757
|
+
async def assert_trigger_behavior_each(
|
|
1753
1758
|
hass: HomeAssistant,
|
|
1754
1759
|
*,
|
|
1755
1760
|
target_entities: dict[str, list[str]],
|
|
@@ -1760,7 +1765,7 @@ async def assert_trigger_behavior_any(
|
|
|
1760
1765
|
trigger_options: dict[str, Any],
|
|
1761
1766
|
states: list[TriggerStateDescription],
|
|
1762
1767
|
) -> None:
|
|
1763
|
-
"""Test trigger fires in mode
|
|
1768
|
+
"""Test trigger fires in mode each."""
|
|
1764
1769
|
calls: list[str] = []
|
|
1765
1770
|
other_entity_ids = set(target_entities["included_entities"]) - {entity_id}
|
|
1766
1771
|
excluded_entity_ids = set(target_entities["excluded_entities"]) - {entity_id}
|
|
@@ -1854,7 +1859,7 @@ async def assert_trigger_behavior_first(
|
|
|
1854
1859
|
assert len(calls) == 0
|
|
1855
1860
|
|
|
1856
1861
|
|
|
1857
|
-
async def
|
|
1862
|
+
async def assert_trigger_behavior_all(
|
|
1858
1863
|
hass: HomeAssistant,
|
|
1859
1864
|
*,
|
|
1860
1865
|
target_entities: dict[str, list[str]],
|
|
@@ -1865,7 +1870,7 @@ async def assert_trigger_behavior_last(
|
|
|
1865
1870
|
trigger_options: dict[str, Any],
|
|
1866
1871
|
states: list[TriggerStateDescription],
|
|
1867
1872
|
) -> None:
|
|
1868
|
-
"""Test trigger fires in mode
|
|
1873
|
+
"""Test trigger fires in mode all."""
|
|
1869
1874
|
calls: list[str] = []
|
|
1870
1875
|
other_entity_ids = set(target_entities["included_entities"]) - {entity_id}
|
|
1871
1876
|
excluded_entity_ids = set(target_entities["excluded_entities"]) - {entity_id}
|
|
@@ -1880,7 +1885,7 @@ async def assert_trigger_behavior_last(
|
|
|
1880
1885
|
await arm_trigger(
|
|
1881
1886
|
hass,
|
|
1882
1887
|
trigger,
|
|
1883
|
-
{"behavior": "
|
|
1888
|
+
{"behavior": "all"} | trigger_options,
|
|
1884
1889
|
trigger_target_config,
|
|
1885
1890
|
calls,
|
|
1886
1891
|
)
|
|
@@ -1915,10 +1920,11 @@ def parametrize_numerical_condition_above_below_any(
|
|
|
1915
1920
|
threshold_unit: str | None | UndefinedType = UNDEFINED,
|
|
1916
1921
|
unit_attributes: dict | None = None,
|
|
1917
1922
|
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
1918
|
-
"""Parametrize
|
|
1923
|
+
"""Parametrize threshold cases for state-value numerical conditions.
|
|
1919
1924
|
|
|
1920
|
-
Generates state sequences for a condition
|
|
1921
|
-
directly from `state.state`
|
|
1925
|
+
Uses behavior=any. Generates state sequences for a condition
|
|
1926
|
+
that reads its tracked value directly from `state.state`
|
|
1927
|
+
(e.g. a sensor with a temperature device
|
|
1922
1928
|
class). The condition is exercised across three threshold types in turn
|
|
1923
1929
|
— "above", "below", "between" — and for each, the helper invokes
|
|
1924
1930
|
`parametrize_condition_states_any` with target/other states populated
|
|
@@ -2014,14 +2020,14 @@ def parametrize_numerical_condition_above_below_any(
|
|
|
2014
2020
|
threshold_unit,
|
|
2015
2021
|
),
|
|
2016
2022
|
target_states=[
|
|
2017
|
-
("
|
|
2023
|
+
("20", unit_attributes),
|
|
2018
2024
|
("50", unit_attributes),
|
|
2019
|
-
("
|
|
2025
|
+
("80", unit_attributes),
|
|
2020
2026
|
],
|
|
2021
2027
|
other_states=[
|
|
2022
2028
|
("0", unit_attributes),
|
|
2023
|
-
("
|
|
2024
|
-
("
|
|
2029
|
+
("19", unit_attributes),
|
|
2030
|
+
("81", unit_attributes),
|
|
2025
2031
|
("100", unit_attributes),
|
|
2026
2032
|
],
|
|
2027
2033
|
required_filter_attributes=required_filter_attributes,
|
|
@@ -2037,7 +2043,9 @@ def parametrize_numerical_condition_above_below_all(
|
|
|
2037
2043
|
threshold_unit: str | None | UndefinedType = UNDEFINED,
|
|
2038
2044
|
unit_attributes: dict | None = None,
|
|
2039
2045
|
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
2040
|
-
"""Parametrize
|
|
2046
|
+
"""Parametrize threshold cases for state-value numerical conditions.
|
|
2047
|
+
|
|
2048
|
+
Uses behavior=all.
|
|
2041
2049
|
|
|
2042
2050
|
See `parametrize_numerical_condition_above_below_any` for the structure
|
|
2043
2051
|
of the generated test cases; the only difference is that this helper
|
|
@@ -2130,14 +2138,14 @@ def parametrize_numerical_condition_above_below_all(
|
|
|
2130
2138
|
threshold_unit,
|
|
2131
2139
|
),
|
|
2132
2140
|
target_states=[
|
|
2133
|
-
("
|
|
2141
|
+
("20", unit_attributes),
|
|
2134
2142
|
("50", unit_attributes),
|
|
2135
|
-
("
|
|
2143
|
+
("80", unit_attributes),
|
|
2136
2144
|
],
|
|
2137
2145
|
other_states=[
|
|
2138
2146
|
("0", unit_attributes),
|
|
2139
|
-
("
|
|
2140
|
-
("
|
|
2147
|
+
("19", unit_attributes),
|
|
2148
|
+
("81", unit_attributes),
|
|
2141
2149
|
("100", unit_attributes),
|
|
2142
2150
|
],
|
|
2143
2151
|
required_filter_attributes=required_filter_attributes,
|
|
@@ -2157,10 +2165,11 @@ def parametrize_numerical_attribute_condition_above_below_any(
|
|
|
2157
2165
|
attribute_required: bool = False,
|
|
2158
2166
|
attribute_value_scale: float = 1.0,
|
|
2159
2167
|
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
2160
|
-
"""Parametrize
|
|
2168
|
+
"""Parametrize threshold cases for attribute-based numerical conditions.
|
|
2161
2169
|
|
|
2162
|
-
Generates state sequences for a condition
|
|
2163
|
-
from a state attribute
|
|
2170
|
+
Uses behavior=any. Generates state sequences for a condition
|
|
2171
|
+
that reads its tracked value from a state attribute
|
|
2172
|
+
(e.g. `climate.target_humidity`). The condition
|
|
2164
2173
|
is exercised across three threshold types in turn — "above", "below",
|
|
2165
2174
|
"between" — and for each, the helper invokes
|
|
2166
2175
|
`parametrize_condition_states_any` with target/other states populated
|
|
@@ -2276,14 +2285,14 @@ def parametrize_numerical_attribute_condition_above_below_any(
|
|
|
2276
2285
|
threshold_unit,
|
|
2277
2286
|
),
|
|
2278
2287
|
target_states=[
|
|
2279
|
-
(state, {attribute:
|
|
2288
|
+
(state, {attribute: 20 * s} | unit_attributes),
|
|
2280
2289
|
(state, {attribute: 50 * s} | unit_attributes),
|
|
2281
|
-
(state, {attribute:
|
|
2290
|
+
(state, {attribute: 80 * s} | unit_attributes),
|
|
2282
2291
|
],
|
|
2283
2292
|
other_states=[
|
|
2284
2293
|
(state, {attribute: 0 * s} | unit_attributes),
|
|
2285
|
-
(state, {attribute:
|
|
2286
|
-
(state, {attribute:
|
|
2294
|
+
(state, {attribute: 19 * s} | unit_attributes),
|
|
2295
|
+
(state, {attribute: 81 * s} | unit_attributes),
|
|
2287
2296
|
(state, {attribute: 100 * s} | unit_attributes),
|
|
2288
2297
|
],
|
|
2289
2298
|
extra_excluded_states=extra_excluded_states,
|
|
@@ -2304,7 +2313,9 @@ def parametrize_numerical_attribute_condition_above_below_all(
|
|
|
2304
2313
|
attribute_required: bool = False,
|
|
2305
2314
|
attribute_value_scale: float = 1.0,
|
|
2306
2315
|
) -> list[tuple[str, dict[str, Any], list[ConditionStateDescription]]]:
|
|
2307
|
-
"""Parametrize
|
|
2316
|
+
"""Parametrize threshold cases for attribute-based numerical conditions.
|
|
2317
|
+
|
|
2318
|
+
Uses behavior=all.
|
|
2308
2319
|
|
|
2309
2320
|
See `parametrize_numerical_attribute_condition_above_below_any` for the
|
|
2310
2321
|
structure of the generated test cases; the only difference is that this
|
|
@@ -2421,14 +2432,14 @@ def parametrize_numerical_attribute_condition_above_below_all(
|
|
|
2421
2432
|
threshold_unit,
|
|
2422
2433
|
),
|
|
2423
2434
|
target_states=[
|
|
2424
|
-
(state, {attribute:
|
|
2435
|
+
(state, {attribute: 20 * s} | unit_attributes),
|
|
2425
2436
|
(state, {attribute: 50 * s} | unit_attributes),
|
|
2426
|
-
(state, {attribute:
|
|
2437
|
+
(state, {attribute: 80 * s} | unit_attributes),
|
|
2427
2438
|
],
|
|
2428
2439
|
other_states=[
|
|
2429
2440
|
(state, {attribute: 0 * s} | unit_attributes),
|
|
2430
|
-
(state, {attribute:
|
|
2431
|
-
(state, {attribute:
|
|
2441
|
+
(state, {attribute: 19 * s} | unit_attributes),
|
|
2442
|
+
(state, {attribute: 81 * s} | unit_attributes),
|
|
2432
2443
|
(state, {attribute: 100 * s} | unit_attributes),
|
|
2433
2444
|
],
|
|
2434
2445
|
extra_excluded_states=extra_excluded_states,
|
|
@@ -2595,3 +2606,28 @@ async def assert_numerical_condition_unit_conversion(
|
|
|
2595
2606
|
for state in fail_states:
|
|
2596
2607
|
set_or_remove_state(hass, entity_id, state)
|
|
2597
2608
|
assert cond.async_check() is False
|
|
2609
|
+
|
|
2610
|
+
|
|
2611
|
+
async def assert_availability_follows_source_entity(
|
|
2612
|
+
hass: HomeAssistant,
|
|
2613
|
+
entity_id: str,
|
|
2614
|
+
source_entity_id: str,
|
|
2615
|
+
) -> None:
|
|
2616
|
+
"""Check that entity becomes unavailable when source entity is unavailable."""
|
|
2617
|
+
state = hass.states.get(entity_id)
|
|
2618
|
+
assert state is not None
|
|
2619
|
+
assert state.state != STATE_UNAVAILABLE
|
|
2620
|
+
|
|
2621
|
+
hass.states.async_set(source_entity_id, STATE_UNAVAILABLE)
|
|
2622
|
+
await hass.async_block_till_done()
|
|
2623
|
+
|
|
2624
|
+
state = hass.states.get(entity_id)
|
|
2625
|
+
assert state is not None
|
|
2626
|
+
assert state.state == STATE_UNAVAILABLE
|
|
2627
|
+
|
|
2628
|
+
hass.states.async_set(source_entity_id, STATE_UNKNOWN)
|
|
2629
|
+
await hass.async_block_till_done()
|
|
2630
|
+
|
|
2631
|
+
state = hass.states.get(entity_id)
|
|
2632
|
+
assert state is not None
|
|
2633
|
+
assert state.state != STATE_UNAVAILABLE
|
|
@@ -4,8 +4,6 @@ Common test utils for working with recorder.
|
|
|
4
4
|
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
import asyncio
|
|
10
8
|
from collections.abc import Iterable, Iterator
|
|
11
9
|
from contextlib import contextmanager
|
|
@@ -261,7 +259,7 @@ def assert_events_equal_without_context(event: Event, other: Event) -> None:
|
|
|
261
259
|
"""Assert that two events are equal, ignoring context."""
|
|
262
260
|
assert event.data == other.data
|
|
263
261
|
assert event.event_type == other.event_type
|
|
264
|
-
assert event.origin
|
|
262
|
+
assert event.origin is other.origin
|
|
265
263
|
assert event.time_fired == other.time_fired
|
|
266
264
|
|
|
267
265
|
|
|
@@ -5,8 +5,8 @@ This file is originally from homeassistant/core and modified by pytest-homeassis
|
|
|
5
5
|
"""
|
|
6
6
|
from typing import TYPE_CHECKING, Final
|
|
7
7
|
MAJOR_VERSION: Final = 2026
|
|
8
|
-
MINOR_VERSION: Final =
|
|
9
|
-
PATCH_VERSION: Final = "
|
|
8
|
+
MINOR_VERSION: Final = 6
|
|
9
|
+
PATCH_VERSION: Final = "0b0"
|
|
10
10
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
|
11
11
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
|
12
12
|
CONF_API_VERSION: Final = "api_version"
|
|
@@ -4,8 +4,6 @@ Set up some common test helper things.
|
|
|
4
4
|
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
import asyncio
|
|
10
8
|
from collections.abc import AsyncGenerator, Callable, Coroutine, Generator
|
|
11
9
|
from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
|
|
@@ -66,14 +64,14 @@ from homeassistant.auth.models import Credentials
|
|
|
66
64
|
from homeassistant.auth.providers import homeassistant
|
|
67
65
|
from homeassistant.components.device_tracker.legacy import Device
|
|
68
66
|
|
|
69
|
-
# pylint: disable-next=
|
|
67
|
+
# pylint: disable-next=home-assistant-component-root-import
|
|
70
68
|
from homeassistant.components.websocket_api.auth import (
|
|
71
69
|
TYPE_AUTH,
|
|
72
70
|
TYPE_AUTH_OK,
|
|
73
71
|
TYPE_AUTH_REQUIRED,
|
|
74
72
|
)
|
|
75
73
|
|
|
76
|
-
# pylint: disable-next=
|
|
74
|
+
# pylint: disable-next=home-assistant-component-root-import
|
|
77
75
|
from homeassistant.components.websocket_api.http import URL
|
|
78
76
|
from homeassistant.config import YAML_CONFIG_FILE
|
|
79
77
|
from homeassistant.config_entries import (
|
|
@@ -210,7 +208,9 @@ def pytest_runtest_setup() -> None:
|
|
|
210
208
|
of times it was raised.
|
|
211
209
|
|
|
212
210
|
freezegun:
|
|
213
|
-
- Modified to include
|
|
211
|
+
- Modified to include
|
|
212
|
+
https://github.com/spulec/freezegun/pull/424
|
|
213
|
+
and improve class str.
|
|
214
214
|
"""
|
|
215
215
|
pytest_socket.socket_allow_hosts(["127.0.0.1"])
|
|
216
216
|
pytest_socket.disable_socket(allow_unix_socket=True)
|
|
@@ -235,9 +235,9 @@ def pytest_runtest_setup() -> None:
|
|
|
235
235
|
_validate_host(host)
|
|
236
236
|
return (host, [], [host])
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
socket.getaddrinfo = getaddrinfo_patched
|
|
239
|
+
socket.gethostbyname = gethostbyname_patched
|
|
240
|
+
socket.gethostbyname_ex = gethostbyname_ex_patched
|
|
241
241
|
|
|
242
242
|
pytest_socket.SocketBlockedError = HASocketBlockedError
|
|
243
243
|
|
|
@@ -457,7 +457,8 @@ def verify_cleanup(
|
|
|
457
457
|
try:
|
|
458
458
|
# Verify respx.mock has been cleaned up
|
|
459
459
|
assert not respx.mock.routes, (
|
|
460
|
-
"respx.mock routes not cleaned up, maybe the test
|
|
460
|
+
"respx.mock routes not cleaned up, maybe the test"
|
|
461
|
+
" needs to be decorated with @respx.mock"
|
|
461
462
|
)
|
|
462
463
|
finally:
|
|
463
464
|
# Clear mock routes not break subsequent tests
|
|
@@ -565,7 +566,9 @@ def aiohttp_client_cls() -> type[CoalescingClient]:
|
|
|
565
566
|
|
|
566
567
|
@pytest.fixture
|
|
567
568
|
def aiohttp_client() -> Generator[ClientSessionGenerator]:
|
|
568
|
-
"""Override the default aiohttp_client since 3.x does not support
|
|
569
|
+
"""Override the default aiohttp_client since 3.x does not support it.
|
|
570
|
+
|
|
571
|
+
The aiohttp_client_cls is not supported in 3.x.
|
|
569
572
|
|
|
570
573
|
Remove this when upgrading to 4.x as aiohttp_client_cls
|
|
571
574
|
will do the same thing
|
|
@@ -697,7 +700,8 @@ async def hass(
|
|
|
697
700
|
|
|
698
701
|
yield hass
|
|
699
702
|
|
|
700
|
-
# Config entries are not normally unloaded on HA shutdown.
|
|
703
|
+
# Config entries are not normally unloaded on HA shutdown.
|
|
704
|
+
# They are unloaded here
|
|
701
705
|
# to ensure that they could, and to help track lingering tasks and timers.
|
|
702
706
|
loaded_entries = [
|
|
703
707
|
entry
|
|
@@ -1017,7 +1021,11 @@ def hass_ws_client(
|
|
|
1017
1021
|
def fail_on_log_exception(
|
|
1018
1022
|
request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch
|
|
1019
1023
|
) -> None:
|
|
1020
|
-
"""Fixture to fail if a callback
|
|
1024
|
+
"""Fixture to fail if a callback or coroutine throws.
|
|
1025
|
+
|
|
1026
|
+
Catches callbacks wrapped by catch_log_exception or coroutines
|
|
1027
|
+
wrapped by async_create_catching_coro.
|
|
1028
|
+
"""
|
|
1021
1029
|
if "no_fail_on_log_exception" in request.keywords:
|
|
1022
1030
|
return
|
|
1023
1031
|
|
|
@@ -1063,23 +1071,23 @@ def mqtt_client_mock(hass: HomeAssistant) -> Generator[MqttMockPahoClient]:
|
|
|
1063
1071
|
self.mid = mid
|
|
1064
1072
|
self.rc = 0
|
|
1065
1073
|
|
|
1066
|
-
with patch(
|
|
1067
|
-
"homeassistant.components.mqtt.async_client.AsyncMQTTClient"
|
|
1068
|
-
) as mock_client:
|
|
1074
|
+
with patch("homeassistant.components.mqtt.client.AsyncMQTTClient") as mock_client:
|
|
1069
1075
|
# The below use a call_soon for the on_publish/on_subscribe/on_unsubscribe
|
|
1070
1076
|
# callbacks to simulate the behavior of the real MQTT client which will
|
|
1071
1077
|
# not be synchronous.
|
|
1072
1078
|
|
|
1073
1079
|
@ha.callback
|
|
1074
|
-
def _async_fire_mqtt_message(topic, payload, qos, retain):
|
|
1075
|
-
async_fire_mqtt_message(
|
|
1080
|
+
def _async_fire_mqtt_message(topic, payload, qos, retain, properties=None):
|
|
1081
|
+
async_fire_mqtt_message(
|
|
1082
|
+
hass, topic, payload or b"", qos, retain, properties=properties
|
|
1083
|
+
)
|
|
1076
1084
|
mid = get_mid()
|
|
1077
1085
|
hass.loop.call_soon(
|
|
1078
1086
|
mock_client.on_publish, Mock(), 0, mid, MockMqttReasonCode(), None
|
|
1079
1087
|
)
|
|
1080
1088
|
return FakeInfo(mid)
|
|
1081
1089
|
|
|
1082
|
-
def _subscribe(
|
|
1090
|
+
def _subscribe(topic_or_list, qos=0, **kwargs):
|
|
1083
1091
|
mid = get_mid()
|
|
1084
1092
|
hass.loop.call_soon(
|
|
1085
1093
|
mock_client.on_subscribe, Mock(), 0, mid, [MockMqttReasonCode()], None
|
|
@@ -1146,7 +1154,10 @@ async def _mqtt_mock_entry(
|
|
|
1146
1154
|
from homeassistant.components import mqtt # noqa: PLC0415
|
|
1147
1155
|
|
|
1148
1156
|
if mqtt_config_entry_data is None:
|
|
1149
|
-
mqtt_config_entry_data = {
|
|
1157
|
+
mqtt_config_entry_data = {
|
|
1158
|
+
mqtt.CONF_BROKER: "mock-broker",
|
|
1159
|
+
mqtt.CONF_PROTOCOL: "5",
|
|
1160
|
+
}
|
|
1150
1161
|
if mqtt_config_entry_options is None:
|
|
1151
1162
|
mqtt_config_entry_options = {mqtt.CONF_BIRTH_MESSAGE: {}}
|
|
1152
1163
|
|
|
@@ -1636,7 +1647,8 @@ def recorder_db_url(
|
|
|
1636
1647
|
# to ensure that InnoDB does not deadlock.
|
|
1637
1648
|
with engine.begin() as connection:
|
|
1638
1649
|
query = sa.text(
|
|
1639
|
-
"select id FROM information_schema.processlist
|
|
1650
|
+
"select id FROM information_schema.processlist"
|
|
1651
|
+
" WHERE db=:db and id != CONNECTION_ID()"
|
|
1640
1652
|
)
|
|
1641
1653
|
rows = connection.execute(query, parameters={"db": db}).fetchall()
|
|
1642
1654
|
if rows:
|
|
@@ -1966,6 +1978,15 @@ async def mock_enable_bluetooth(
|
|
|
1966
1978
|
def mock_bluetooth_adapters() -> Generator[None]:
|
|
1967
1979
|
"""Fixture to mock bluetooth adapters."""
|
|
1968
1980
|
with (
|
|
1981
|
+
# Simulate the Bluetooth management API being unavailable, as it is on
|
|
1982
|
+
# CI and most dev machines. Letting the real setup() run would attempt
|
|
1983
|
+
# real socket I/O on Linux hosts that do have BlueZ available, and
|
|
1984
|
+
# mocking it as successful would enable the advertising side channel,
|
|
1985
|
+
# changing the scanner code path the existing tests were written for.
|
|
1986
|
+
patch(
|
|
1987
|
+
"habluetooth.channels.bluez.MGMTBluetoothCtl.setup",
|
|
1988
|
+
AsyncMock(side_effect=OSError),
|
|
1989
|
+
),
|
|
1969
1990
|
patch("habluetooth.util.recover_adapter"),
|
|
1970
1991
|
patch("bluetooth_auto_recovery.recover_adapter"),
|
|
1971
1992
|
patch("bluetooth_adapters.systems.platform.system", return_value="Linux"),
|
|
@@ -2228,7 +2249,8 @@ _real_dhcp_service_info_init = DhcpServiceInfo.__init__
|
|
|
2228
2249
|
def _dhcp_service_info_init(self: DhcpServiceInfo, *args: Any, **kwargs: Any) -> None:
|
|
2229
2250
|
"""Override __init__ for DhcpServiceInfo.
|
|
2230
2251
|
|
|
2231
|
-
Ensure that the macaddress is always in lowercase and
|
|
2252
|
+
Ensure that the macaddress is always in lowercase and
|
|
2253
|
+
without colons to match DHCP service.
|
|
2232
2254
|
"""
|
|
2233
2255
|
_real_dhcp_service_info_init(self, *args, **kwargs)
|
|
2234
2256
|
if self.macaddress != self.macaddress.lower().replace(":", ""):
|
|
@@ -4,8 +4,6 @@ Home Assistant extension for Syrupy.
|
|
|
4
4
|
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
from contextlib import suppress
|
|
10
8
|
import dataclasses
|
|
11
9
|
from enum import IntFlag
|
|
@@ -248,8 +246,10 @@ class _IntFlagWrapper:
|
|
|
248
246
|
self._flag = flag
|
|
249
247
|
|
|
250
248
|
def __repr__(self) -> str:
|
|
251
|
-
# 3.10:
|
|
252
|
-
#
|
|
249
|
+
# 3.10:
|
|
250
|
+
# <ClimateEntityFeature.SWING_MODE|PRESET_MODE|FAN_MODE|TARGET_TEMPERATURE: 57>
|
|
251
|
+
# 3.11:
|
|
252
|
+
# <ClimateEntityFeature.TARGET_TEMPERATURE|FAN_MODE|PRESET_MODE|SWING_MODE: 57>
|
|
253
253
|
# Syrupy: <ClimateEntityFeature: 57>
|
|
254
254
|
return f"<{self._flag.__class__.__name__}: {self._flag.value}>"
|
|
255
255
|
|
|
@@ -370,7 +370,7 @@ def _merge_serialized_report(report: SnapshotReport, json_data: dict[str, Any])
|
|
|
370
370
|
for key, selected_item in json_data["_selected_items"].items():
|
|
371
371
|
if key in report.selected_items:
|
|
372
372
|
status = ItemStatus(selected_item)
|
|
373
|
-
if status
|
|
373
|
+
if status is not ItemStatus.NOT_RUN:
|
|
374
374
|
report.selected_items[key] = status
|
|
375
375
|
else:
|
|
376
376
|
report.selected_items[key] = ItemStatus(selected_item)
|
|
@@ -4,8 +4,6 @@ Typing helpers for Home Assistant tests.
|
|
|
4
4
|
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
7
|
from collections.abc import Callable, Coroutine
|
|
10
8
|
from contextlib import AbstractAsyncContextManager
|
|
11
9
|
from typing import TYPE_CHECKING, Any
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-homeassistant-custom-component
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.334
|
|
4
4
|
Summary: Experimental package to automatically extract test plugins for Home Assistant custom components
|
|
5
5
|
Home-page: https://github.com/MatthewFlamm/pytest-homeassistant-custom-component
|
|
6
6
|
Author: Matthew Flamm
|
|
@@ -18,12 +18,12 @@ Description-Content-Type: text/markdown
|
|
|
18
18
|
License-File: LICENSE
|
|
19
19
|
License-File: LICENSE_HA_CORE.md
|
|
20
20
|
Requires-Dist: sqlalchemy
|
|
21
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: ast-serialize==0.3.0
|
|
22
|
+
Requires-Dist: coverage==7.14.0
|
|
22
23
|
Requires-Dist: freezegun==1.5.5
|
|
23
24
|
Requires-Dist: license-expression==30.4.3
|
|
24
25
|
Requires-Dist: mock-open==1.4.0
|
|
25
|
-
Requires-Dist: pydantic==2.13.
|
|
26
|
-
Requires-Dist: pylint-per-file-ignores==3.2.1
|
|
26
|
+
Requires-Dist: pydantic==2.13.4
|
|
27
27
|
Requires-Dist: pipdeptree==2.26.1
|
|
28
28
|
Requires-Dist: pytest-asyncio==1.3.0
|
|
29
29
|
Requires-Dist: pytest-aiohttp==1.1.0
|
|
@@ -36,11 +36,13 @@ Requires-Dist: pytest-unordered==0.7.0
|
|
|
36
36
|
Requires-Dist: pytest-picked==0.5.1
|
|
37
37
|
Requires-Dist: pytest-xdist==3.8.0
|
|
38
38
|
Requires-Dist: pytest==9.0.3
|
|
39
|
+
Requires-Dist: requests==2.34.2
|
|
39
40
|
Requires-Dist: requests-mock==1.12.1
|
|
40
41
|
Requires-Dist: respx==0.23.1
|
|
41
|
-
Requires-Dist: syrupy==5.
|
|
42
|
+
Requires-Dist: syrupy==5.2.0
|
|
42
43
|
Requires-Dist: tqdm==4.67.1
|
|
43
|
-
Requires-Dist:
|
|
44
|
+
Requires-Dist: unidiff==0.7.5
|
|
45
|
+
Requires-Dist: homeassistant==2026.6.0b0
|
|
44
46
|
Requires-Dist: aiohasupervisor==0.4.3
|
|
45
47
|
Requires-Dist: SQLAlchemy==2.0.49
|
|
46
48
|
Requires-Dist: paho-mqtt==2.1.0
|
|
@@ -59,7 +61,7 @@ Dynamic: summary
|
|
|
59
61
|
|
|
60
62
|
# pytest-homeassistant-custom-component
|
|
61
63
|
|
|
62
|
-

|
|
63
65
|
|
|
64
66
|
Package to automatically extract testing plugins from Home Assistant for custom component testing.
|
|
65
67
|
The goal is to provide the same functionality as the tests in home-assistant/core.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
sqlalchemy
|
|
2
|
-
|
|
2
|
+
ast-serialize==0.3.0
|
|
3
|
+
coverage==7.14.0
|
|
3
4
|
freezegun==1.5.5
|
|
4
5
|
license-expression==30.4.3
|
|
5
6
|
mock-open==1.4.0
|
|
6
|
-
pydantic==2.13.
|
|
7
|
-
pylint-per-file-ignores==3.2.1
|
|
7
|
+
pydantic==2.13.4
|
|
8
8
|
pipdeptree==2.26.1
|
|
9
9
|
pytest-asyncio==1.3.0
|
|
10
10
|
pytest-aiohttp==1.1.0
|
|
@@ -17,11 +17,13 @@ pytest-unordered==0.7.0
|
|
|
17
17
|
pytest-picked==0.5.1
|
|
18
18
|
pytest-xdist==3.8.0
|
|
19
19
|
pytest==9.0.3
|
|
20
|
+
requests==2.34.2
|
|
20
21
|
requests-mock==1.12.1
|
|
21
22
|
respx==0.23.1
|
|
22
|
-
syrupy==5.
|
|
23
|
+
syrupy==5.2.0
|
|
23
24
|
tqdm==4.67.1
|
|
24
|
-
|
|
25
|
+
unidiff==0.7.5
|
|
26
|
+
homeassistant==2026.6.0b0
|
|
25
27
|
aiohasupervisor==0.4.3
|
|
26
28
|
SQLAlchemy==2.0.49
|
|
27
29
|
paho-mqtt==2.1.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|