pytest-homeassistant-custom-component 0.13.296__tar.gz → 0.13.298__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.296/src/pytest_homeassistant_custom_component.egg-info → pytest_homeassistant_custom_component-0.13.298}/PKG-INFO +6 -6
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/README.md +1 -1
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/common.py +9 -5
- pytest_homeassistant_custom_component-0.13.298/src/pytest_homeassistant_custom_component/components/__init__.py +356 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/const.py +2 -2
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/plugins.py +11 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/test_util/aiohttp.py +2 -2
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298/src/pytest_homeassistant_custom_component.egg-info}/PKG-INFO +6 -6
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component.egg-info/requires.txt +4 -4
- pytest_homeassistant_custom_component-0.13.296/src/pytest_homeassistant_custom_component/components/__init__.py +0 -5
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/LICENSE +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/LICENSE_HA_CORE.md +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/setup.cfg +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/setup.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/asyncio_legacy.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/components/diagnostics/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/components/recorder/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/components/recorder/common.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/components/recorder/db_schema_0.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/ignore_uncaught_exceptions.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/patch_json.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/patch_recorder.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/patch_time.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/syrupy.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/test_util/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/testing_config/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/testing_config/custom_components/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/testing_config/custom_components/test_constant_deprecation/__init__.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component/typing.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component.egg-info/SOURCES.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component.egg-info/dependency_links.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component.egg-info/entry_points.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/src/pytest_homeassistant_custom_component.egg-info/top_level.txt +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/tests/test_common.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/tests/test_config_flow.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/tests/test_diagnostics.py +0 -0
- {pytest_homeassistant_custom_component-0.13.296 → pytest_homeassistant_custom_component-0.13.298}/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.298
|
|
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
|
|
@@ -20,14 +20,14 @@ License-File: LICENSE_HA_CORE.md
|
|
|
20
20
|
Requires-Dist: sqlalchemy
|
|
21
21
|
Requires-Dist: coverage==7.10.6
|
|
22
22
|
Requires-Dist: freezegun==1.5.2
|
|
23
|
-
Requires-Dist: go2rtc-client==0.
|
|
23
|
+
Requires-Dist: go2rtc-client==0.3.0
|
|
24
24
|
Requires-Dist: librt==0.2.1
|
|
25
25
|
Requires-Dist: license-expression==30.4.3
|
|
26
26
|
Requires-Dist: mock-open==1.4.0
|
|
27
27
|
Requires-Dist: pydantic==2.12.2
|
|
28
28
|
Requires-Dist: pylint-per-file-ignores==1.4.0
|
|
29
29
|
Requires-Dist: pipdeptree==2.26.1
|
|
30
|
-
Requires-Dist: pytest-asyncio==1.
|
|
30
|
+
Requires-Dist: pytest-asyncio==1.3.0
|
|
31
31
|
Requires-Dist: pytest-aiohttp==1.1.0
|
|
32
32
|
Requires-Dist: pytest-cov==7.0.0
|
|
33
33
|
Requires-Dist: pytest-freezer==0.4.9
|
|
@@ -38,12 +38,12 @@ Requires-Dist: pytest-timeout==2.4.0
|
|
|
38
38
|
Requires-Dist: pytest-unordered==0.7.0
|
|
39
39
|
Requires-Dist: pytest-picked==0.5.1
|
|
40
40
|
Requires-Dist: pytest-xdist==3.8.0
|
|
41
|
-
Requires-Dist: pytest==
|
|
41
|
+
Requires-Dist: pytest==9.0.0
|
|
42
42
|
Requires-Dist: requests-mock==1.12.1
|
|
43
43
|
Requires-Dist: respx==0.22.0
|
|
44
44
|
Requires-Dist: syrupy==5.0.0
|
|
45
45
|
Requires-Dist: tqdm==4.67.1
|
|
46
|
-
Requires-Dist: homeassistant==2025.
|
|
46
|
+
Requires-Dist: homeassistant==2025.12.0
|
|
47
47
|
Requires-Dist: SQLAlchemy==2.0.41
|
|
48
48
|
Requires-Dist: paho-mqtt==2.1.0
|
|
49
49
|
Requires-Dist: numpy==2.3.2
|
|
@@ -61,7 +61,7 @@ Dynamic: summary
|
|
|
61
61
|
|
|
62
62
|
# pytest-homeassistant-custom-component
|
|
63
63
|
|
|
64
|
-

|
|
65
65
|
|
|
66
66
|
[](https://gitpod.io/#https://github.com/MatthewFlamm/pytest-homeassistant-custom-component)
|
|
67
67
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pytest-homeassistant-custom-component
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
[](https://gitpod.io/#https://github.com/MatthewFlamm/pytest-homeassistant-custom-component)
|
|
6
6
|
|
|
@@ -1539,7 +1539,7 @@ def mock_storage(data: dict[str, Any] | None = None) -> Generator[dict[str, Any]
|
|
|
1539
1539
|
return loaded
|
|
1540
1540
|
|
|
1541
1541
|
async def mock_write_data(
|
|
1542
|
-
store: storage.Store,
|
|
1542
|
+
store: storage.Store, data_to_write: dict[str, Any]
|
|
1543
1543
|
) -> None:
|
|
1544
1544
|
"""Mock version of write data."""
|
|
1545
1545
|
# To ensure that the data can be serialized
|
|
@@ -1616,12 +1616,16 @@ def mock_integration(
|
|
|
1616
1616
|
top_level_files: set[str] | None = None,
|
|
1617
1617
|
) -> loader.Integration:
|
|
1618
1618
|
"""Mock an integration."""
|
|
1619
|
-
|
|
1620
|
-
hass,
|
|
1619
|
+
path = (
|
|
1621
1620
|
f"{loader.PACKAGE_BUILTIN}.{module.DOMAIN}"
|
|
1622
1621
|
if built_in
|
|
1623
|
-
else f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{module.DOMAIN}"
|
|
1624
|
-
|
|
1622
|
+
else f"{loader.PACKAGE_CUSTOM_COMPONENTS}.{module.DOMAIN}"
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
integration = loader.Integration(
|
|
1626
|
+
hass,
|
|
1627
|
+
path,
|
|
1628
|
+
pathlib.Path(path.replace(".", "/")),
|
|
1625
1629
|
module.mock_manifest(),
|
|
1626
1630
|
top_level_files,
|
|
1627
1631
|
)
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The tests for components.
|
|
3
|
+
|
|
4
|
+
This file is originally from homeassistant/core and modified by pytest-homeassistant-custom-component.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import StrEnum
|
|
8
|
+
import itertools
|
|
9
|
+
from typing import TypedDict
|
|
10
|
+
|
|
11
|
+
from homeassistant.const import (
|
|
12
|
+
ATTR_AREA_ID,
|
|
13
|
+
ATTR_DEVICE_ID,
|
|
14
|
+
ATTR_FLOOR_ID,
|
|
15
|
+
ATTR_LABEL_ID,
|
|
16
|
+
CONF_ENTITY_ID,
|
|
17
|
+
CONF_OPTIONS,
|
|
18
|
+
CONF_PLATFORM,
|
|
19
|
+
CONF_TARGET,
|
|
20
|
+
STATE_UNAVAILABLE,
|
|
21
|
+
STATE_UNKNOWN,
|
|
22
|
+
)
|
|
23
|
+
from homeassistant.core import HomeAssistant
|
|
24
|
+
from homeassistant.helpers import (
|
|
25
|
+
area_registry as ar,
|
|
26
|
+
device_registry as dr,
|
|
27
|
+
entity_registry as er,
|
|
28
|
+
floor_registry as fr,
|
|
29
|
+
label_registry as lr,
|
|
30
|
+
)
|
|
31
|
+
from homeassistant.setup import async_setup_component
|
|
32
|
+
|
|
33
|
+
from ..common import MockConfigEntry, mock_device_registry
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def target_entities(
|
|
37
|
+
hass: HomeAssistant, domain: str
|
|
38
|
+
) -> tuple[list[str], list[str]]:
|
|
39
|
+
"""Create multiple entities associated with different targets.
|
|
40
|
+
|
|
41
|
+
Returns a dict with the following keys:
|
|
42
|
+
- included: List of entity_ids meant to be targeted.
|
|
43
|
+
- excluded: List of entity_ids not meant to be targeted.
|
|
44
|
+
"""
|
|
45
|
+
await async_setup_component(hass, domain, {})
|
|
46
|
+
|
|
47
|
+
config_entry = MockConfigEntry(domain="test")
|
|
48
|
+
config_entry.add_to_hass(hass)
|
|
49
|
+
|
|
50
|
+
floor_reg = fr.async_get(hass)
|
|
51
|
+
floor = floor_reg.async_get_floor_by_name("Test Floor") or floor_reg.async_create(
|
|
52
|
+
"Test Floor"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
area_reg = ar.async_get(hass)
|
|
56
|
+
area = area_reg.async_get_area_by_name("Test Area") or area_reg.async_create(
|
|
57
|
+
"Test Area", floor_id=floor.floor_id
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
label_reg = lr.async_get(hass)
|
|
61
|
+
label = label_reg.async_get_label_by_name("Test Label") or label_reg.async_create(
|
|
62
|
+
"Test Label"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
device = dr.DeviceEntry(id="test_device", area_id=area.id, labels={label.label_id})
|
|
66
|
+
mock_device_registry(hass, {device.id: device})
|
|
67
|
+
|
|
68
|
+
entity_reg = er.async_get(hass)
|
|
69
|
+
# Entities associated with area
|
|
70
|
+
entity_area = entity_reg.async_get_or_create(
|
|
71
|
+
domain=domain,
|
|
72
|
+
platform="test",
|
|
73
|
+
unique_id=f"{domain}_area",
|
|
74
|
+
suggested_object_id=f"area_{domain}",
|
|
75
|
+
)
|
|
76
|
+
entity_reg.async_update_entity(entity_area.entity_id, area_id=area.id)
|
|
77
|
+
entity_area_excluded = entity_reg.async_get_or_create(
|
|
78
|
+
domain=domain,
|
|
79
|
+
platform="test",
|
|
80
|
+
unique_id=f"{domain}_area_excluded",
|
|
81
|
+
suggested_object_id=f"area_{domain}_excluded",
|
|
82
|
+
)
|
|
83
|
+
entity_reg.async_update_entity(entity_area_excluded.entity_id, area_id=area.id)
|
|
84
|
+
|
|
85
|
+
# Entities associated with device
|
|
86
|
+
entity_reg.async_get_or_create(
|
|
87
|
+
domain=domain,
|
|
88
|
+
platform="test",
|
|
89
|
+
unique_id=f"{domain}_device",
|
|
90
|
+
suggested_object_id=f"device_{domain}",
|
|
91
|
+
device_id=device.id,
|
|
92
|
+
)
|
|
93
|
+
entity_reg.async_get_or_create(
|
|
94
|
+
domain=domain,
|
|
95
|
+
platform="test",
|
|
96
|
+
unique_id=f"{domain}_device_excluded",
|
|
97
|
+
suggested_object_id=f"device_{domain}_excluded",
|
|
98
|
+
device_id=device.id,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Entities associated with label
|
|
102
|
+
entity_label = entity_reg.async_get_or_create(
|
|
103
|
+
domain=domain,
|
|
104
|
+
platform="test",
|
|
105
|
+
unique_id=f"{domain}_label",
|
|
106
|
+
suggested_object_id=f"label_{domain}",
|
|
107
|
+
)
|
|
108
|
+
entity_reg.async_update_entity(entity_label.entity_id, labels={label.label_id})
|
|
109
|
+
entity_label_excluded = entity_reg.async_get_or_create(
|
|
110
|
+
domain=domain,
|
|
111
|
+
platform="test",
|
|
112
|
+
unique_id=f"{domain}_label_excluded",
|
|
113
|
+
suggested_object_id=f"label_{domain}_excluded",
|
|
114
|
+
)
|
|
115
|
+
entity_reg.async_update_entity(
|
|
116
|
+
entity_label_excluded.entity_id, labels={label.label_id}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Return all available entities
|
|
120
|
+
return {
|
|
121
|
+
"included": [
|
|
122
|
+
f"{domain}.standalone_{domain}",
|
|
123
|
+
f"{domain}.label_{domain}",
|
|
124
|
+
f"{domain}.area_{domain}",
|
|
125
|
+
f"{domain}.device_{domain}",
|
|
126
|
+
],
|
|
127
|
+
"excluded": [
|
|
128
|
+
f"{domain}.standalone_{domain}_excluded",
|
|
129
|
+
f"{domain}.label_{domain}_excluded",
|
|
130
|
+
f"{domain}.area_{domain}_excluded",
|
|
131
|
+
f"{domain}.device_{domain}_excluded",
|
|
132
|
+
],
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parametrize_target_entities(domain: str) -> list[tuple[dict, str, int]]:
|
|
137
|
+
"""Parametrize target entities for different target types.
|
|
138
|
+
|
|
139
|
+
Meant to be used with target_entities.
|
|
140
|
+
"""
|
|
141
|
+
return [
|
|
142
|
+
(
|
|
143
|
+
{CONF_ENTITY_ID: f"{domain}.standalone_{domain}"},
|
|
144
|
+
f"{domain}.standalone_{domain}",
|
|
145
|
+
1,
|
|
146
|
+
),
|
|
147
|
+
({ATTR_LABEL_ID: "test_label"}, f"{domain}.label_{domain}", 2),
|
|
148
|
+
({ATTR_AREA_ID: "test_area"}, f"{domain}.area_{domain}", 2),
|
|
149
|
+
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.area_{domain}", 2),
|
|
150
|
+
({ATTR_LABEL_ID: "test_label"}, f"{domain}.device_{domain}", 2),
|
|
151
|
+
({ATTR_AREA_ID: "test_area"}, f"{domain}.device_{domain}", 2),
|
|
152
|
+
({ATTR_FLOOR_ID: "test_floor"}, f"{domain}.device_{domain}", 2),
|
|
153
|
+
({ATTR_DEVICE_ID: "test_device"}, f"{domain}.device_{domain}", 1),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class _StateDescription(TypedDict):
|
|
158
|
+
"""Test state and expected service call count."""
|
|
159
|
+
|
|
160
|
+
state: str | None
|
|
161
|
+
attributes: dict
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class StateDescription(TypedDict):
|
|
165
|
+
"""Test state and expected service call count."""
|
|
166
|
+
|
|
167
|
+
included: _StateDescription
|
|
168
|
+
excluded: _StateDescription
|
|
169
|
+
count: int
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def parametrize_trigger_states(
|
|
173
|
+
*,
|
|
174
|
+
trigger: str,
|
|
175
|
+
target_states: list[str | None | tuple[str | None, dict]],
|
|
176
|
+
other_states: list[str | None | tuple[str | None, dict]],
|
|
177
|
+
additional_attributes: dict | None = None,
|
|
178
|
+
trigger_from_none: bool = True,
|
|
179
|
+
) -> list[tuple[str, list[StateDescription]]]:
|
|
180
|
+
"""Parametrize states and expected service call counts.
|
|
181
|
+
|
|
182
|
+
The target_states and other_states iterables are either iterables of
|
|
183
|
+
states or iterables of (state, attributes) tuples.
|
|
184
|
+
|
|
185
|
+
Set `trigger_from_none` to False if the trigger is not expected to fire
|
|
186
|
+
when the initial state is None.
|
|
187
|
+
|
|
188
|
+
Returns a list of tuples with (trigger, list of states),
|
|
189
|
+
where states is a list of StateDescription dicts.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
additional_attributes = additional_attributes or {}
|
|
193
|
+
|
|
194
|
+
def state_with_attributes(
|
|
195
|
+
state: str | None | tuple[str | None, dict], count: int
|
|
196
|
+
) -> dict:
|
|
197
|
+
"""Return (state, attributes) dict."""
|
|
198
|
+
if isinstance(state, str) or state is None:
|
|
199
|
+
return {
|
|
200
|
+
"included": {
|
|
201
|
+
"state": state,
|
|
202
|
+
"attributes": additional_attributes,
|
|
203
|
+
},
|
|
204
|
+
"excluded": {
|
|
205
|
+
"state": state,
|
|
206
|
+
"attributes": {},
|
|
207
|
+
},
|
|
208
|
+
"count": count,
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
"included": {
|
|
212
|
+
"state": state[0],
|
|
213
|
+
"attributes": state[1] | additional_attributes,
|
|
214
|
+
},
|
|
215
|
+
"excluded": {
|
|
216
|
+
"state": state[0],
|
|
217
|
+
"attributes": state[1],
|
|
218
|
+
},
|
|
219
|
+
"count": count,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
# Initial state None
|
|
224
|
+
(
|
|
225
|
+
trigger,
|
|
226
|
+
list(
|
|
227
|
+
itertools.chain.from_iterable(
|
|
228
|
+
(
|
|
229
|
+
state_with_attributes(None, 0),
|
|
230
|
+
state_with_attributes(target_state, 0),
|
|
231
|
+
state_with_attributes(other_state, 0),
|
|
232
|
+
state_with_attributes(
|
|
233
|
+
target_state, 1 if trigger_from_none else 0
|
|
234
|
+
),
|
|
235
|
+
)
|
|
236
|
+
for target_state in target_states
|
|
237
|
+
for other_state in other_states
|
|
238
|
+
)
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
# Initial state different from target state
|
|
242
|
+
(
|
|
243
|
+
trigger,
|
|
244
|
+
# other_state,
|
|
245
|
+
list(
|
|
246
|
+
itertools.chain.from_iterable(
|
|
247
|
+
(
|
|
248
|
+
state_with_attributes(other_state, 0),
|
|
249
|
+
state_with_attributes(target_state, 1),
|
|
250
|
+
state_with_attributes(other_state, 0),
|
|
251
|
+
state_with_attributes(target_state, 1),
|
|
252
|
+
)
|
|
253
|
+
for target_state in target_states
|
|
254
|
+
for other_state in other_states
|
|
255
|
+
)
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
# Initial state same as target state
|
|
259
|
+
(
|
|
260
|
+
trigger,
|
|
261
|
+
list(
|
|
262
|
+
itertools.chain.from_iterable(
|
|
263
|
+
(
|
|
264
|
+
state_with_attributes(target_state, 0),
|
|
265
|
+
state_with_attributes(target_state, 0),
|
|
266
|
+
state_with_attributes(other_state, 0),
|
|
267
|
+
state_with_attributes(target_state, 1),
|
|
268
|
+
)
|
|
269
|
+
for target_state in target_states
|
|
270
|
+
for other_state in other_states
|
|
271
|
+
)
|
|
272
|
+
),
|
|
273
|
+
),
|
|
274
|
+
# Initial state unavailable / unknown
|
|
275
|
+
(
|
|
276
|
+
trigger,
|
|
277
|
+
list(
|
|
278
|
+
itertools.chain.from_iterable(
|
|
279
|
+
(
|
|
280
|
+
state_with_attributes(STATE_UNAVAILABLE, 0),
|
|
281
|
+
state_with_attributes(target_state, 0),
|
|
282
|
+
state_with_attributes(other_state, 0),
|
|
283
|
+
state_with_attributes(target_state, 1),
|
|
284
|
+
)
|
|
285
|
+
for target_state in target_states
|
|
286
|
+
for other_state in other_states
|
|
287
|
+
)
|
|
288
|
+
),
|
|
289
|
+
),
|
|
290
|
+
(
|
|
291
|
+
trigger,
|
|
292
|
+
list(
|
|
293
|
+
itertools.chain.from_iterable(
|
|
294
|
+
(
|
|
295
|
+
state_with_attributes(STATE_UNKNOWN, 0),
|
|
296
|
+
state_with_attributes(target_state, 0),
|
|
297
|
+
state_with_attributes(other_state, 0),
|
|
298
|
+
state_with_attributes(target_state, 1),
|
|
299
|
+
)
|
|
300
|
+
for target_state in target_states
|
|
301
|
+
for other_state in other_states
|
|
302
|
+
)
|
|
303
|
+
),
|
|
304
|
+
),
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async def arm_trigger(
|
|
309
|
+
hass: HomeAssistant,
|
|
310
|
+
trigger: str,
|
|
311
|
+
trigger_options: dict | None,
|
|
312
|
+
trigger_target: dict,
|
|
313
|
+
) -> None:
|
|
314
|
+
"""Arm the specified trigger, call service test.automation when it triggers."""
|
|
315
|
+
|
|
316
|
+
# Local include to avoid importing the automation component unnecessarily
|
|
317
|
+
from homeassistant.components import automation # noqa: PLC0415
|
|
318
|
+
|
|
319
|
+
options = {CONF_OPTIONS: {**trigger_options}} if trigger_options is not None else {}
|
|
320
|
+
|
|
321
|
+
await async_setup_component(
|
|
322
|
+
hass,
|
|
323
|
+
automation.DOMAIN,
|
|
324
|
+
{
|
|
325
|
+
automation.DOMAIN: {
|
|
326
|
+
"trigger": {
|
|
327
|
+
CONF_PLATFORM: trigger,
|
|
328
|
+
CONF_TARGET: {**trigger_target},
|
|
329
|
+
}
|
|
330
|
+
| options,
|
|
331
|
+
"action": {
|
|
332
|
+
"service": "test.automation",
|
|
333
|
+
"data_template": {CONF_ENTITY_ID: "{{ trigger.entity_id }}"},
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def set_or_remove_state(
|
|
341
|
+
hass: HomeAssistant,
|
|
342
|
+
entity_id: str,
|
|
343
|
+
state: StateDescription,
|
|
344
|
+
) -> None:
|
|
345
|
+
"""Set or remove the state of an entity."""
|
|
346
|
+
if state["state"] is None:
|
|
347
|
+
hass.states.async_remove(entity_id)
|
|
348
|
+
else:
|
|
349
|
+
hass.states.async_set(
|
|
350
|
+
entity_id, state["state"], state["attributes"], force_update=True
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def other_states(state: StrEnum) -> list[str]:
|
|
355
|
+
"""Return a sorted list with all states except the specified one."""
|
|
356
|
+
return sorted({s.value for s in state.__class__} - {state.value})
|
|
@@ -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 = 2025
|
|
8
|
-
MINOR_VERSION: Final =
|
|
9
|
-
PATCH_VERSION: Final = "
|
|
8
|
+
MINOR_VERSION: Final = 12
|
|
9
|
+
PATCH_VERSION: Final = "0"
|
|
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"
|
|
@@ -2133,3 +2133,14 @@ def _dhcp_service_info_init(self: DhcpServiceInfo, *args: Any, **kwargs: Any) ->
|
|
|
2133
2133
|
|
|
2134
2134
|
|
|
2135
2135
|
DhcpServiceInfo.__init__ = _dhcp_service_info_init
|
|
2136
|
+
|
|
2137
|
+
|
|
2138
|
+
@pytest.fixture(autouse=True)
|
|
2139
|
+
def disable_http_server() -> Generator[None]:
|
|
2140
|
+
"""Disable automatic start of HTTP server during .
|
|
2141
|
+
|
|
2142
|
+
This prevents the HTTP server from starting in tests that setup
|
|
2143
|
+
integrations which depend on the HTTP component.
|
|
2144
|
+
"""
|
|
2145
|
+
with patch("homeassistant.components.http.start_http_server_and_save_config"):
|
|
2146
|
+
yield
|
|
@@ -225,8 +225,8 @@ class AiohttpClientMockResponse:
|
|
|
225
225
|
|
|
226
226
|
if (
|
|
227
227
|
self._url.scheme != url.scheme
|
|
228
|
-
or self._url.
|
|
229
|
-
or self._url.
|
|
228
|
+
or self._url.raw_host != url.raw_host
|
|
229
|
+
or self._url.raw_path != url.raw_path
|
|
230
230
|
):
|
|
231
231
|
return False
|
|
232
232
|
|
|
@@ -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.298
|
|
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
|
|
@@ -20,14 +20,14 @@ License-File: LICENSE_HA_CORE.md
|
|
|
20
20
|
Requires-Dist: sqlalchemy
|
|
21
21
|
Requires-Dist: coverage==7.10.6
|
|
22
22
|
Requires-Dist: freezegun==1.5.2
|
|
23
|
-
Requires-Dist: go2rtc-client==0.
|
|
23
|
+
Requires-Dist: go2rtc-client==0.3.0
|
|
24
24
|
Requires-Dist: librt==0.2.1
|
|
25
25
|
Requires-Dist: license-expression==30.4.3
|
|
26
26
|
Requires-Dist: mock-open==1.4.0
|
|
27
27
|
Requires-Dist: pydantic==2.12.2
|
|
28
28
|
Requires-Dist: pylint-per-file-ignores==1.4.0
|
|
29
29
|
Requires-Dist: pipdeptree==2.26.1
|
|
30
|
-
Requires-Dist: pytest-asyncio==1.
|
|
30
|
+
Requires-Dist: pytest-asyncio==1.3.0
|
|
31
31
|
Requires-Dist: pytest-aiohttp==1.1.0
|
|
32
32
|
Requires-Dist: pytest-cov==7.0.0
|
|
33
33
|
Requires-Dist: pytest-freezer==0.4.9
|
|
@@ -38,12 +38,12 @@ Requires-Dist: pytest-timeout==2.4.0
|
|
|
38
38
|
Requires-Dist: pytest-unordered==0.7.0
|
|
39
39
|
Requires-Dist: pytest-picked==0.5.1
|
|
40
40
|
Requires-Dist: pytest-xdist==3.8.0
|
|
41
|
-
Requires-Dist: pytest==
|
|
41
|
+
Requires-Dist: pytest==9.0.0
|
|
42
42
|
Requires-Dist: requests-mock==1.12.1
|
|
43
43
|
Requires-Dist: respx==0.22.0
|
|
44
44
|
Requires-Dist: syrupy==5.0.0
|
|
45
45
|
Requires-Dist: tqdm==4.67.1
|
|
46
|
-
Requires-Dist: homeassistant==2025.
|
|
46
|
+
Requires-Dist: homeassistant==2025.12.0
|
|
47
47
|
Requires-Dist: SQLAlchemy==2.0.41
|
|
48
48
|
Requires-Dist: paho-mqtt==2.1.0
|
|
49
49
|
Requires-Dist: numpy==2.3.2
|
|
@@ -61,7 +61,7 @@ Dynamic: summary
|
|
|
61
61
|
|
|
62
62
|
# pytest-homeassistant-custom-component
|
|
63
63
|
|
|
64
|
-

|
|
65
65
|
|
|
66
66
|
[](https://gitpod.io/#https://github.com/MatthewFlamm/pytest-homeassistant-custom-component)
|
|
67
67
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
sqlalchemy
|
|
2
2
|
coverage==7.10.6
|
|
3
3
|
freezegun==1.5.2
|
|
4
|
-
go2rtc-client==0.
|
|
4
|
+
go2rtc-client==0.3.0
|
|
5
5
|
librt==0.2.1
|
|
6
6
|
license-expression==30.4.3
|
|
7
7
|
mock-open==1.4.0
|
|
8
8
|
pydantic==2.12.2
|
|
9
9
|
pylint-per-file-ignores==1.4.0
|
|
10
10
|
pipdeptree==2.26.1
|
|
11
|
-
pytest-asyncio==1.
|
|
11
|
+
pytest-asyncio==1.3.0
|
|
12
12
|
pytest-aiohttp==1.1.0
|
|
13
13
|
pytest-cov==7.0.0
|
|
14
14
|
pytest-freezer==0.4.9
|
|
@@ -19,12 +19,12 @@ pytest-timeout==2.4.0
|
|
|
19
19
|
pytest-unordered==0.7.0
|
|
20
20
|
pytest-picked==0.5.1
|
|
21
21
|
pytest-xdist==3.8.0
|
|
22
|
-
pytest==
|
|
22
|
+
pytest==9.0.0
|
|
23
23
|
requests-mock==1.12.1
|
|
24
24
|
respx==0.22.0
|
|
25
25
|
syrupy==5.0.0
|
|
26
26
|
tqdm==4.67.1
|
|
27
|
-
homeassistant==2025.
|
|
27
|
+
homeassistant==2025.12.0
|
|
28
28
|
SQLAlchemy==2.0.41
|
|
29
29
|
paho-mqtt==2.1.0
|
|
30
30
|
numpy==2.3.2
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|