aiohomematic-test-support 2025.10.14__tar.gz → 2025.10.16__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.
Potentially problematic release.
This version of aiohomematic-test-support might be problematic. Click here for more details.
- {aiohomematic_test_support-2025.10.14/aiohomematic_test_support.egg-info → aiohomematic_test_support-2025.10.16}/PKG-INFO +2 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/__init__.py +1 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16/aiohomematic_test_support.egg-info}/PKG-INFO +2 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/const.py +49 -7
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/pyproject.toml +1 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/support.py +182 -233
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/MANIFEST.in +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/README.md +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/aiohomematic_test_support.egg-info/SOURCES.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/aiohomematic_test_support.egg-info/dependency_links.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/aiohomematic_test_support.egg-info/top_level.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/client_local.py +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/data/full_session_randomized_ccu.zip +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/data/full_session_randomized_pydevccu.zip +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/py.typed +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic-test-support
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.16
|
|
4
4
|
Summary: Support-only package for AioHomematic (tests/dev). Not part of production builds.
|
|
5
5
|
Author-email: SukramJ <sukramj@icloud.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/SukramJ/aiohomematic
|
|
@@ -8,4 +8,5 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
12
|
Requires-Python: >=3.13
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "2025.10.
|
|
1
|
+
__version__ = "2025.10.16"
|
|
2
2
|
"""Module to support aiohomematic testing with a local client."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aiohomematic-test-support
|
|
3
|
-
Version: 2025.10.
|
|
3
|
+
Version: 2025.10.16
|
|
4
4
|
Summary: Support-only package for AioHomematic (tests/dev). Not part of production builds.
|
|
5
5
|
Author-email: SukramJ <sukramj@icloud.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/SukramJ/aiohomematic
|
|
@@ -8,4 +8,5 @@ Classifier: Development Status :: 3 - Alpha
|
|
|
8
8
|
Classifier: Intended Audience :: Developers
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
12
|
Requires-Python: >=3.13
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from aiohomematic.client.json_rpc import _JsonKey
|
|
5
6
|
from aiohomematic.const import LOCAL_HOST, Interface, ProgramData, SystemVariableData, SysvarType
|
|
6
7
|
|
|
7
8
|
CENTRAL_NAME = "CentralTest"
|
|
@@ -32,7 +33,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
32
33
|
SystemVariableData(
|
|
33
34
|
vid="2",
|
|
34
35
|
legacy_name="alarm_ext",
|
|
35
|
-
description="",
|
|
36
|
+
description="HAHM",
|
|
36
37
|
data_type=SysvarType.ALARM,
|
|
37
38
|
unit=None,
|
|
38
39
|
value=False,
|
|
@@ -56,7 +57,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
56
57
|
SystemVariableData(
|
|
57
58
|
vid="4",
|
|
58
59
|
legacy_name="logic_ext",
|
|
59
|
-
description="",
|
|
60
|
+
description="HAHM",
|
|
60
61
|
data_type=SysvarType.LOGIC,
|
|
61
62
|
unit=None,
|
|
62
63
|
value=False,
|
|
@@ -80,7 +81,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
80
81
|
SystemVariableData(
|
|
81
82
|
vid="6",
|
|
82
83
|
legacy_name="list_ext",
|
|
83
|
-
description="",
|
|
84
|
+
description="HAHM",
|
|
84
85
|
data_type=SysvarType.LIST,
|
|
85
86
|
unit=None,
|
|
86
87
|
value=0,
|
|
@@ -104,7 +105,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
104
105
|
SystemVariableData(
|
|
105
106
|
vid="8",
|
|
106
107
|
legacy_name="string_ext",
|
|
107
|
-
description="",
|
|
108
|
+
description="HAHM",
|
|
108
109
|
data_type=SysvarType.STRING,
|
|
109
110
|
unit=None,
|
|
110
111
|
value="test1",
|
|
@@ -128,7 +129,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
128
129
|
SystemVariableData(
|
|
129
130
|
vid="10",
|
|
130
131
|
legacy_name="float_ext",
|
|
131
|
-
description="",
|
|
132
|
+
description="HAHM",
|
|
132
133
|
data_type=SysvarType.FLOAT,
|
|
133
134
|
unit="°C",
|
|
134
135
|
value=23.2,
|
|
@@ -152,7 +153,7 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
152
153
|
SystemVariableData(
|
|
153
154
|
vid="12",
|
|
154
155
|
legacy_name="integer_ext",
|
|
155
|
-
description="",
|
|
156
|
+
description="HAHM",
|
|
156
157
|
data_type=SysvarType.INTEGER,
|
|
157
158
|
unit=None,
|
|
158
159
|
value=17,
|
|
@@ -163,6 +164,31 @@ SYSVAR_DATA: list[SystemVariableData] = [
|
|
|
163
164
|
),
|
|
164
165
|
]
|
|
165
166
|
|
|
167
|
+
|
|
168
|
+
SYSVAR_DATA_JSON = [
|
|
169
|
+
{
|
|
170
|
+
_JsonKey.ID: sv.vid,
|
|
171
|
+
_JsonKey.IS_INTERNAL: False,
|
|
172
|
+
_JsonKey.MAX_VALUE: sv.max_value,
|
|
173
|
+
_JsonKey.MIN_VALUE: sv.min_value,
|
|
174
|
+
_JsonKey.NAME: sv.legacy_name,
|
|
175
|
+
_JsonKey.TYPE: sv.data_type,
|
|
176
|
+
_JsonKey.UNIT: sv.unit,
|
|
177
|
+
_JsonKey.VALUE: sv.value,
|
|
178
|
+
_JsonKey.VALUE_LIST: ";".join(sv.values) if sv.values else None,
|
|
179
|
+
}
|
|
180
|
+
for sv in SYSVAR_DATA
|
|
181
|
+
]
|
|
182
|
+
SYSVAR_DATA_JSON_DESCRIPTION = [
|
|
183
|
+
{
|
|
184
|
+
_JsonKey.ID: sv.vid,
|
|
185
|
+
_JsonKey.DESCRIPTION: sv.description,
|
|
186
|
+
}
|
|
187
|
+
for sv in SYSVAR_DATA
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
SYSVAR_DATA_XML = {sv.legacy_name: sv.value for sv in SYSVAR_DATA}
|
|
191
|
+
|
|
166
192
|
PROGRAM_DATA: list[ProgramData] = [
|
|
167
193
|
ProgramData(
|
|
168
194
|
legacy_name="p1",
|
|
@@ -181,7 +207,23 @@ PROGRAM_DATA: list[ProgramData] = [
|
|
|
181
207
|
last_execute_time="",
|
|
182
208
|
),
|
|
183
209
|
]
|
|
184
|
-
|
|
210
|
+
PROGRAM_DATA_JSON = [
|
|
211
|
+
{
|
|
212
|
+
_JsonKey.ID: p.pid,
|
|
213
|
+
_JsonKey.IS_ACTIVE: p.is_active,
|
|
214
|
+
_JsonKey.IS_INTERNAL: p.is_internal,
|
|
215
|
+
_JsonKey.LAST_EXECUTE_TIME: p.last_execute_time,
|
|
216
|
+
_JsonKey.NAME: p.legacy_name,
|
|
217
|
+
}
|
|
218
|
+
for p in PROGRAM_DATA
|
|
219
|
+
]
|
|
220
|
+
PROGRAM_DATA_JSON_DESCRIPTION = [
|
|
221
|
+
{
|
|
222
|
+
_JsonKey.ID: p.pid,
|
|
223
|
+
_JsonKey.DESCRIPTION: p.description,
|
|
224
|
+
}
|
|
225
|
+
for p in PROGRAM_DATA
|
|
226
|
+
]
|
|
185
227
|
|
|
186
228
|
ADDRESS_DEVICE_TRANSLATION = {
|
|
187
229
|
"VCU3432945": "HmIP-STV.json",
|
|
@@ -10,7 +10,7 @@ import importlib.resources
|
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
12
|
import os
|
|
13
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, Self, cast
|
|
14
14
|
from unittest.mock import MagicMock, Mock, patch
|
|
15
15
|
import zipfile
|
|
16
16
|
|
|
@@ -30,173 +30,92 @@ from aiohomematic.const import (
|
|
|
30
30
|
Parameter,
|
|
31
31
|
ParamsetKey,
|
|
32
32
|
RPCType,
|
|
33
|
-
SourceOfDeviceCreation,
|
|
34
33
|
)
|
|
35
34
|
from aiohomematic.model.custom import CustomDataPoint
|
|
36
35
|
from aiohomematic.store.persistent import _freeze_params, _unfreeze_params
|
|
37
36
|
from aiohomematic_test_support import const
|
|
38
|
-
from aiohomematic_test_support.client_local import ClientLocal, LocalRessources
|
|
39
37
|
|
|
40
38
|
_LOGGER = logging.getLogger(__name__)
|
|
41
39
|
|
|
42
|
-
EXCLUDE_METHODS_FROM_MOCKS: Final[list[str]] = []
|
|
43
|
-
INCLUDE_PROPERTIES_IN_MOCKS: Final[list[str]] = []
|
|
44
|
-
|
|
45
40
|
|
|
46
41
|
# pylint: disable=protected-access
|
|
47
|
-
class
|
|
42
|
+
class FactoryWithClient:
|
|
48
43
|
"""Factory for a central with one local client."""
|
|
49
44
|
|
|
50
|
-
def __init__(
|
|
51
|
-
"""Init the central factory."""
|
|
52
|
-
self._client_session = client_session
|
|
53
|
-
self.system_event_mock = MagicMock()
|
|
54
|
-
self.ha_event_mock = MagicMock()
|
|
55
|
-
|
|
56
|
-
async def get_raw_central(
|
|
57
|
-
self,
|
|
58
|
-
*,
|
|
59
|
-
interface_config: InterfaceConfig | None,
|
|
60
|
-
un_ignore_list: list[str] | None = None,
|
|
61
|
-
ignore_custom_device_definition_models: list[str] | None = None,
|
|
62
|
-
) -> CentralUnit:
|
|
63
|
-
"""Return a central based on give address_device_translation."""
|
|
64
|
-
interface_configs = {interface_config} if interface_config else set()
|
|
65
|
-
central = CentralConfig(
|
|
66
|
-
name=const.CENTRAL_NAME,
|
|
67
|
-
host=const.CCU_HOST,
|
|
68
|
-
username=const.CCU_USERNAME,
|
|
69
|
-
password=const.CCU_PASSWORD,
|
|
70
|
-
central_id="test1234",
|
|
71
|
-
interface_configs=interface_configs,
|
|
72
|
-
client_session=self._client_session,
|
|
73
|
-
un_ignore_list=frozenset(un_ignore_list or []),
|
|
74
|
-
ignore_custom_device_definition_models=frozenset(ignore_custom_device_definition_models or []),
|
|
75
|
-
start_direct=True,
|
|
76
|
-
).create_central()
|
|
77
|
-
|
|
78
|
-
central.register_backend_system_callback(cb=self.system_event_mock)
|
|
79
|
-
central.register_homematic_callback(cb=self.ha_event_mock)
|
|
80
|
-
|
|
81
|
-
return central
|
|
82
|
-
|
|
83
|
-
async def get_unpatched_default_central(
|
|
45
|
+
def __init__(
|
|
84
46
|
self,
|
|
85
47
|
*,
|
|
86
|
-
|
|
87
|
-
address_device_translation:
|
|
48
|
+
player: SessionPlayer,
|
|
49
|
+
address_device_translation: set[str] | None = None,
|
|
88
50
|
do_mock_client: bool = True,
|
|
89
|
-
|
|
90
|
-
un_ignore_list: list[str] | None = None,
|
|
51
|
+
exclude_methods_from_mocks: set[str] | None = None,
|
|
91
52
|
ignore_custom_device_definition_models: list[str] | None = None,
|
|
92
|
-
) -> tuple[CentralUnit, Client | Mock]:
|
|
93
|
-
"""Return a central based on give address_device_translation."""
|
|
94
|
-
interface_config = InterfaceConfig(
|
|
95
|
-
central_name=const.CENTRAL_NAME,
|
|
96
|
-
interface=Interface.BIDCOS_RF,
|
|
97
|
-
port=port,
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
central = await self.get_raw_central(
|
|
101
|
-
interface_config=interface_config,
|
|
102
|
-
un_ignore_list=un_ignore_list,
|
|
103
|
-
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
_client = ClientLocal(
|
|
107
|
-
client_config=ClientConfig(
|
|
108
|
-
central=central,
|
|
109
|
-
interface_config=interface_config,
|
|
110
|
-
),
|
|
111
|
-
local_resources=LocalRessources(
|
|
112
|
-
address_device_translation=address_device_translation,
|
|
113
|
-
ignore_devices_on_create=ignore_devices_on_create if ignore_devices_on_create else [],
|
|
114
|
-
),
|
|
115
|
-
)
|
|
116
|
-
await _client.init_client()
|
|
117
|
-
client = get_mock(_client) if do_mock_client else _client
|
|
118
|
-
|
|
119
|
-
assert central
|
|
120
|
-
assert client
|
|
121
|
-
return central, client
|
|
122
|
-
|
|
123
|
-
async def get_default_central(
|
|
124
|
-
self,
|
|
125
|
-
*,
|
|
126
|
-
port: int = const.CCU_MINI_PORT,
|
|
127
|
-
address_device_translation: dict[str, str],
|
|
128
|
-
do_mock_client: bool = True,
|
|
129
|
-
add_sysvars: bool = False,
|
|
130
|
-
add_programs: bool = False,
|
|
131
53
|
ignore_devices_on_create: list[str] | None = None,
|
|
54
|
+
include_properties_in_mocks: set[str] | None = None,
|
|
55
|
+
interface_configs: set[InterfaceConfig] | None = None,
|
|
132
56
|
un_ignore_list: list[str] | None = None,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
port=port,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Init the central factory."""
|
|
59
|
+
self._player = player
|
|
60
|
+
self.init(
|
|
138
61
|
address_device_translation=address_device_translation,
|
|
139
|
-
do_mock_client=
|
|
62
|
+
do_mock_client=do_mock_client,
|
|
63
|
+
exclude_methods_from_mocks=exclude_methods_from_mocks,
|
|
64
|
+
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
140
65
|
ignore_devices_on_create=ignore_devices_on_create,
|
|
66
|
+
include_properties_in_mocks=include_properties_in_mocks,
|
|
67
|
+
interface_configs=interface_configs,
|
|
141
68
|
un_ignore_list=un_ignore_list,
|
|
142
|
-
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
143
69
|
)
|
|
70
|
+
self.system_event_mock = MagicMock()
|
|
71
|
+
self.ha_event_mock = MagicMock()
|
|
144
72
|
|
|
145
|
-
|
|
146
|
-
patch("aiohomematic.client.ClientConfig.create_client", return_value=client).start()
|
|
147
|
-
patch(
|
|
148
|
-
"aiohomematic_test_support.client_local.ClientLocal.get_all_system_variables",
|
|
149
|
-
return_value=const.SYSVAR_DATA if add_sysvars else [],
|
|
150
|
-
).start()
|
|
151
|
-
patch(
|
|
152
|
-
"aiohomematic_test_support.client_local.ClientLocal.get_all_programs",
|
|
153
|
-
return_value=const.PROGRAM_DATA if add_programs else [],
|
|
154
|
-
).start()
|
|
155
|
-
patch("aiohomematic.central.CentralUnit._identify_ip_addr", return_value=LOCAL_HOST).start()
|
|
156
|
-
|
|
157
|
-
await central.start()
|
|
158
|
-
if new_device_addresses := central._check_for_new_device_addresses():
|
|
159
|
-
await central._create_devices(new_device_addresses=new_device_addresses, source=SourceOfDeviceCreation.INIT)
|
|
160
|
-
await central._init_hub()
|
|
161
|
-
|
|
162
|
-
assert central
|
|
163
|
-
assert client
|
|
164
|
-
return central, client
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
class FactoryWithClient:
|
|
168
|
-
"""Factory for a central with one local client."""
|
|
169
|
-
|
|
170
|
-
def __init__(
|
|
73
|
+
def init(
|
|
171
74
|
self,
|
|
172
75
|
*,
|
|
173
|
-
|
|
174
|
-
|
|
76
|
+
address_device_translation: set[str] | None = None,
|
|
77
|
+
do_mock_client: bool = True,
|
|
78
|
+
exclude_methods_from_mocks: set[str] | None = None,
|
|
79
|
+
ignore_custom_device_definition_models: list[str] | None = None,
|
|
175
80
|
ignore_devices_on_create: list[str] | None = None,
|
|
176
|
-
|
|
81
|
+
include_properties_in_mocks: set[str] | None = None,
|
|
82
|
+
interface_configs: set[InterfaceConfig] | None = None,
|
|
83
|
+
un_ignore_list: list[str] | None = None,
|
|
84
|
+
) -> Self:
|
|
177
85
|
"""Init the central factory."""
|
|
86
|
+
self._address_device_translation = address_device_translation
|
|
87
|
+
self._do_mock_client = do_mock_client
|
|
88
|
+
self._exclude_methods_from_mocks = exclude_methods_from_mocks
|
|
89
|
+
self._ignore_custom_device_definition_models = ignore_custom_device_definition_models
|
|
90
|
+
self._ignore_devices_on_create = ignore_devices_on_create
|
|
91
|
+
self._include_properties_in_mocks = include_properties_in_mocks
|
|
92
|
+
self._interface_configs = (
|
|
93
|
+
interface_configs
|
|
94
|
+
if interface_configs is not None
|
|
95
|
+
else {
|
|
96
|
+
InterfaceConfig(
|
|
97
|
+
central_name=const.CENTRAL_NAME,
|
|
98
|
+
interface=Interface.BIDCOS_RF,
|
|
99
|
+
port=2001,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
self._un_ignore_list = frozenset(un_ignore_list or [])
|
|
178
104
|
self._client_session = _get_client_session(
|
|
179
|
-
|
|
180
|
-
address_device_translation=
|
|
181
|
-
ignore_devices_on_create=
|
|
105
|
+
player=self._player,
|
|
106
|
+
address_device_translation=self._address_device_translation,
|
|
107
|
+
ignore_devices_on_create=self._ignore_devices_on_create,
|
|
182
108
|
)
|
|
183
109
|
self._xml_proxy = _get_xml_rpc_proxy(
|
|
184
|
-
|
|
185
|
-
address_device_translation=
|
|
186
|
-
ignore_devices_on_create=
|
|
110
|
+
player=self._player,
|
|
111
|
+
address_device_translation=self._address_device_translation,
|
|
112
|
+
ignore_devices_on_create=self._ignore_devices_on_create,
|
|
187
113
|
)
|
|
188
|
-
self
|
|
189
|
-
self.ha_event_mock = MagicMock()
|
|
114
|
+
return self
|
|
190
115
|
|
|
191
|
-
async def get_raw_central(
|
|
192
|
-
self,
|
|
193
|
-
*,
|
|
194
|
-
interface_config: InterfaceConfig | None,
|
|
195
|
-
un_ignore_list: list[str] | None = None,
|
|
196
|
-
ignore_custom_device_definition_models: list[str] | None = None,
|
|
197
|
-
) -> CentralUnit:
|
|
116
|
+
async def get_raw_central(self) -> CentralUnit:
|
|
198
117
|
"""Return a central based on give address_device_translation."""
|
|
199
|
-
interface_configs =
|
|
118
|
+
interface_configs = self._interface_configs if self._interface_configs else set()
|
|
200
119
|
central = CentralConfig(
|
|
201
120
|
name=const.CENTRAL_NAME,
|
|
202
121
|
host=const.CCU_HOST,
|
|
@@ -205,81 +124,57 @@ class FactoryWithClient:
|
|
|
205
124
|
central_id="test1234",
|
|
206
125
|
interface_configs=interface_configs,
|
|
207
126
|
client_session=self._client_session,
|
|
208
|
-
un_ignore_list=
|
|
209
|
-
ignore_custom_device_definition_models=frozenset(
|
|
127
|
+
un_ignore_list=self._un_ignore_list,
|
|
128
|
+
ignore_custom_device_definition_models=frozenset(self._ignore_custom_device_definition_models or []),
|
|
210
129
|
start_direct=True,
|
|
211
130
|
).create_central()
|
|
212
131
|
|
|
213
132
|
central.register_backend_system_callback(cb=self.system_event_mock)
|
|
214
133
|
central.register_homematic_callback(cb=self.ha_event_mock)
|
|
215
134
|
|
|
216
|
-
return central
|
|
217
|
-
|
|
218
|
-
async def get_unpatched_default_central(
|
|
219
|
-
self,
|
|
220
|
-
*,
|
|
221
|
-
un_ignore_list: list[str] | None = None,
|
|
222
|
-
ignore_custom_device_definition_models: list[str] | None = None,
|
|
223
|
-
) -> CentralUnit:
|
|
224
|
-
"""Return a central based on give address_device_translation."""
|
|
225
|
-
interface_config = InterfaceConfig(
|
|
226
|
-
central_name=const.CENTRAL_NAME,
|
|
227
|
-
interface=Interface.BIDCOS_RF,
|
|
228
|
-
port=2001,
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
central = await self.get_raw_central(
|
|
232
|
-
interface_config=interface_config,
|
|
233
|
-
un_ignore_list=un_ignore_list,
|
|
234
|
-
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
135
|
assert central
|
|
238
136
|
self._client_session.set_central(central=central) # type: ignore[attr-defined]
|
|
239
137
|
self._xml_proxy.set_central(central=central)
|
|
240
138
|
return central
|
|
241
139
|
|
|
242
|
-
async def get_default_central(
|
|
243
|
-
self,
|
|
244
|
-
*,
|
|
245
|
-
do_mock_client: bool = True,
|
|
246
|
-
un_ignore_list: list[str] | None = None,
|
|
247
|
-
ignore_custom_device_definition_models: list[str] | None = None,
|
|
248
|
-
) -> tuple[CentralUnit, Client | Mock]:
|
|
140
|
+
async def get_default_central(self, *, start: bool = True) -> CentralUnit:
|
|
249
141
|
"""Return a central based on give address_device_translation."""
|
|
250
|
-
central = await self.
|
|
251
|
-
un_ignore_list=un_ignore_list,
|
|
252
|
-
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
253
|
-
)
|
|
142
|
+
central = await self.get_raw_central()
|
|
254
143
|
|
|
255
144
|
await self._xml_proxy.do_init()
|
|
256
145
|
patch("aiohomematic.client.ClientConfig._create_xml_rpc_proxy", return_value=self._xml_proxy).start()
|
|
257
146
|
patch("aiohomematic.central.CentralUnit._identify_ip_addr", return_value=LOCAL_HOST).start()
|
|
258
147
|
|
|
259
148
|
# Optionally patch client creation to return a mocked client
|
|
260
|
-
if
|
|
149
|
+
if self._do_mock_client:
|
|
261
150
|
_orig_create_client = ClientConfig.create_client
|
|
262
151
|
|
|
263
|
-
async def _mocked_create_client(
|
|
264
|
-
real_client = await _orig_create_client(
|
|
265
|
-
return cast(
|
|
152
|
+
async def _mocked_create_client(config: ClientConfig) -> Client | Mock:
|
|
153
|
+
real_client = await _orig_create_client(config)
|
|
154
|
+
return cast(
|
|
155
|
+
Mock,
|
|
156
|
+
get_mock(
|
|
157
|
+
instance=real_client,
|
|
158
|
+
exclude_methods=self._exclude_methods_from_mocks,
|
|
159
|
+
include_properties=self._include_properties_in_mocks,
|
|
160
|
+
),
|
|
161
|
+
)
|
|
266
162
|
|
|
267
163
|
patch("aiohomematic.client.ClientConfig.create_client", _mocked_create_client).start()
|
|
268
164
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
165
|
+
if start:
|
|
166
|
+
await central.start()
|
|
167
|
+
await central._init_hub()
|
|
272
168
|
assert central
|
|
273
|
-
|
|
274
|
-
return central, client
|
|
169
|
+
return central
|
|
275
170
|
|
|
276
171
|
|
|
277
|
-
def _get_not_mockable_method_names(instance: Any) -> set[str]:
|
|
172
|
+
def _get_not_mockable_method_names(instance: Any, exclude_methods: set[str]) -> set[str]:
|
|
278
173
|
"""Return all relevant method names for mocking."""
|
|
279
174
|
methods: set[str] = set(_get_properties(data_object=instance, decorator=property))
|
|
280
175
|
|
|
281
176
|
for method in dir(instance):
|
|
282
|
-
if method in
|
|
177
|
+
if method in exclude_methods:
|
|
283
178
|
methods.add(method)
|
|
284
179
|
return methods
|
|
285
180
|
|
|
@@ -306,26 +201,26 @@ def _load_json_file(anchor: str, resource: str, file_name: str) -> Any | None:
|
|
|
306
201
|
return orjson.loads(fptr.read())
|
|
307
202
|
|
|
308
203
|
|
|
309
|
-
def _get_client_session(
|
|
204
|
+
def _get_client_session( # noqa: C901
|
|
310
205
|
*,
|
|
311
|
-
|
|
312
|
-
address_device_translation:
|
|
206
|
+
player: SessionPlayer,
|
|
207
|
+
address_device_translation: set[str] | None = None,
|
|
313
208
|
ignore_devices_on_create: list[str] | None = None,
|
|
314
209
|
) -> ClientSession:
|
|
315
210
|
"""
|
|
316
|
-
Provide a ClientSession-like fixture that answers via
|
|
211
|
+
Provide a ClientSession-like fixture that answers via SessionPlayer(JSON-RPC).
|
|
317
212
|
|
|
318
213
|
Any POST request will be answered by looking up the latest recorded
|
|
319
|
-
JSON-RPC response in the session
|
|
214
|
+
JSON-RPC response in the session player using the provided method and params.
|
|
320
215
|
"""
|
|
321
216
|
|
|
322
217
|
class _MockResponse:
|
|
323
218
|
def __init__(self, json_data: dict | None) -> None:
|
|
324
219
|
# If no match is found, emulate backend error payload
|
|
325
220
|
self._json = json_data or {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
221
|
+
_JsonKey.RESULT: None,
|
|
222
|
+
_JsonKey.ERROR: {"name": "-1", "code": -1, "message": "Not found in session player"},
|
|
223
|
+
_JsonKey.ID: 0,
|
|
329
224
|
}
|
|
330
225
|
self.status = 200
|
|
331
226
|
|
|
@@ -365,6 +260,32 @@ def _get_client_session(
|
|
|
365
260
|
params = payload.get("params")
|
|
366
261
|
|
|
367
262
|
if self._central:
|
|
263
|
+
if method in (
|
|
264
|
+
_JsonRpcMethod.PROGRAM_EXECUTE,
|
|
265
|
+
_JsonRpcMethod.SYSVAR_SET_BOOL,
|
|
266
|
+
_JsonRpcMethod.SYSVAR_SET_FLOAT,
|
|
267
|
+
_JsonRpcMethod.SESSION_LOGOUT,
|
|
268
|
+
):
|
|
269
|
+
return _MockResponse({_JsonKey.ID: 0, _JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
270
|
+
if method == _JsonRpcMethod.SYSVAR_GET_ALL:
|
|
271
|
+
return _MockResponse(
|
|
272
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.SYSVAR_DATA_JSON, _JsonKey.ERROR: None}
|
|
273
|
+
)
|
|
274
|
+
if method == _JsonRpcMethod.PROGRAM_GET_ALL:
|
|
275
|
+
return _MockResponse(
|
|
276
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.PROGRAM_DATA_JSON, _JsonKey.ERROR: None}
|
|
277
|
+
)
|
|
278
|
+
if method == _JsonRpcMethod.REGA_RUN_SCRIPT:
|
|
279
|
+
if "get_program_descriptions" in params[_JsonKey.SCRIPT]:
|
|
280
|
+
return _MockResponse(
|
|
281
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.PROGRAM_DATA_JSON_DESCRIPTION, _JsonKey.ERROR: None}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
if "get_system_variable_descriptions" in params[_JsonKey.SCRIPT]:
|
|
285
|
+
return _MockResponse(
|
|
286
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.SYSVAR_DATA_JSON_DESCRIPTION, _JsonKey.ERROR: None}
|
|
287
|
+
)
|
|
288
|
+
|
|
368
289
|
if method == _JsonRpcMethod.INTERFACE_SET_VALUE:
|
|
369
290
|
await self._central.data_point_event(
|
|
370
291
|
interface_id=params[_JsonKey.INTERFACE],
|
|
@@ -372,7 +293,7 @@ def _get_client_session(
|
|
|
372
293
|
parameter=params[_JsonKey.VALUE_KEY],
|
|
373
294
|
value=params[_JsonKey.VALUE],
|
|
374
295
|
)
|
|
375
|
-
return _MockResponse({
|
|
296
|
+
return _MockResponse({_JsonKey.ID: 0, _JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
376
297
|
if method == _JsonRpcMethod.INTERFACE_PUT_PARAMSET:
|
|
377
298
|
if params[_JsonKey.PARAMSET_KEY] == ParamsetKey.VALUES:
|
|
378
299
|
interface_id = params[_JsonKey.INTERFACE]
|
|
@@ -385,19 +306,29 @@ def _get_client_session(
|
|
|
385
306
|
parameter=param,
|
|
386
307
|
value=value,
|
|
387
308
|
)
|
|
388
|
-
return _MockResponse({
|
|
309
|
+
return _MockResponse({_JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
389
310
|
|
|
390
|
-
json_data =
|
|
311
|
+
json_data = player.get_latest_response_by_params(
|
|
391
312
|
rpc_type=RPCType.JSON_RPC,
|
|
392
313
|
method=str(method) if method is not None else "",
|
|
393
314
|
params=params,
|
|
394
315
|
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
316
|
+
if method == _JsonRpcMethod.INTERFACE_LIST_DEVICES and (
|
|
317
|
+
ignore_devices_on_create is not None or address_device_translation is not None
|
|
318
|
+
):
|
|
319
|
+
new_devices = []
|
|
320
|
+
for dd in json_data[_JsonKey.RESULT]:
|
|
321
|
+
if ignore_devices_on_create is not None and (
|
|
322
|
+
dd["address"] in ignore_devices_on_create or dd["parent"] in ignore_devices_on_create
|
|
323
|
+
):
|
|
324
|
+
continue
|
|
325
|
+
if address_device_translation is not None:
|
|
326
|
+
if dd["address"] in address_device_translation or dd["parent"] in address_device_translation:
|
|
327
|
+
new_devices.append(dd)
|
|
328
|
+
else:
|
|
329
|
+
new_devices.append(dd)
|
|
330
|
+
|
|
331
|
+
json_data[_JsonKey.RESULT] = new_devices
|
|
401
332
|
return _MockResponse(json_data)
|
|
402
333
|
|
|
403
334
|
async def close(self) -> None: # compatibility
|
|
@@ -408,16 +339,16 @@ def _get_client_session(
|
|
|
408
339
|
|
|
409
340
|
def _get_xml_rpc_proxy( # noqa: C901
|
|
410
341
|
*,
|
|
411
|
-
|
|
412
|
-
address_device_translation:
|
|
342
|
+
player: SessionPlayer,
|
|
343
|
+
address_device_translation: set[str] | None = None,
|
|
413
344
|
ignore_devices_on_create: list[str] | None = None,
|
|
414
345
|
) -> BaseRpcProxy:
|
|
415
346
|
"""
|
|
416
|
-
Provide an BaseRpcProxy-like fixture that answers via
|
|
347
|
+
Provide an BaseRpcProxy-like fixture that answers via SessionPlayer (XML-RPC).
|
|
417
348
|
|
|
418
349
|
Any method call like: await proxy.system.listMethods(...)
|
|
419
350
|
will be answered by looking up the latest recorded XML-RPC response
|
|
420
|
-
in the session
|
|
351
|
+
in the session player using the provided method and positional params.
|
|
421
352
|
"""
|
|
422
353
|
|
|
423
354
|
class _Method:
|
|
@@ -433,9 +364,9 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
433
364
|
# Forward to caller with collected method name and positional params
|
|
434
365
|
return await self._caller(self._name, *args)
|
|
435
366
|
|
|
436
|
-
class
|
|
367
|
+
class _AioXmlRpcProxyFromSession:
|
|
437
368
|
def __init__(self) -> None:
|
|
438
|
-
self.
|
|
369
|
+
self._player = player
|
|
439
370
|
self._supported_methods: tuple[str, ...] = ()
|
|
440
371
|
self._central: CentralUnit | None = None
|
|
441
372
|
|
|
@@ -448,6 +379,20 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
448
379
|
"""Return the supported methods."""
|
|
449
380
|
return self._supported_methods
|
|
450
381
|
|
|
382
|
+
async def getAllSystemVariables(self) -> dict[str, Any]:
|
|
383
|
+
"""Return all system variables."""
|
|
384
|
+
return const.SYSVAR_DATA_XML
|
|
385
|
+
|
|
386
|
+
async def getParamset(self, channel_address: str, paramset: str) -> Any:
|
|
387
|
+
"""Set a value."""
|
|
388
|
+
if self._central:
|
|
389
|
+
result = self._player.get_latest_response_by_params(
|
|
390
|
+
rpc_type=RPCType.XML_RPC,
|
|
391
|
+
method="getParamset",
|
|
392
|
+
params=(channel_address, paramset),
|
|
393
|
+
)
|
|
394
|
+
return result if result else {}
|
|
395
|
+
|
|
451
396
|
async def setValue(self, channel_address: str, parameter: str, value: Any, rx_mode: Any | None = None) -> None:
|
|
452
397
|
"""Set a value."""
|
|
453
398
|
if self._central:
|
|
@@ -485,7 +430,7 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
485
430
|
|
|
486
431
|
async def listDevices(self) -> list[Any]:
|
|
487
432
|
"""Return a list of devices."""
|
|
488
|
-
devices = self.
|
|
433
|
+
devices = self._player.get_latest_response_by_params(
|
|
489
434
|
rpc_type=RPCType.XML_RPC,
|
|
490
435
|
method="listDevices",
|
|
491
436
|
params="()",
|
|
@@ -495,19 +440,16 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
495
440
|
if ignore_devices_on_create is None and address_device_translation is None:
|
|
496
441
|
return cast(list[Any], devices)
|
|
497
442
|
|
|
498
|
-
for
|
|
443
|
+
for dd in devices:
|
|
499
444
|
if ignore_devices_on_create is not None and (
|
|
500
|
-
|
|
445
|
+
dd["ADDRESS"] in ignore_devices_on_create or dd["PARENT"] in ignore_devices_on_create
|
|
501
446
|
):
|
|
502
447
|
continue
|
|
503
448
|
if address_device_translation is not None:
|
|
504
|
-
if
|
|
505
|
-
|
|
506
|
-
or device["PARENT"] in address_device_translation
|
|
507
|
-
):
|
|
508
|
-
new_devices.append(device)
|
|
449
|
+
if dd["ADDRESS"] in address_device_translation or dd["PARENT"] in address_device_translation:
|
|
450
|
+
new_devices.append(dd)
|
|
509
451
|
else:
|
|
510
|
-
new_devices.append(
|
|
452
|
+
new_devices.append(dd)
|
|
511
453
|
|
|
512
454
|
return new_devices
|
|
513
455
|
|
|
@@ -517,7 +459,7 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
517
459
|
|
|
518
460
|
async def _invoke(self, method: str, *args: Any) -> Any:
|
|
519
461
|
params = tuple(args)
|
|
520
|
-
return self.
|
|
462
|
+
return self._player.get_latest_response_by_params(
|
|
521
463
|
rpc_type=RPCType.XML_RPC,
|
|
522
464
|
method=method,
|
|
523
465
|
params=params,
|
|
@@ -533,12 +475,12 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
533
475
|
supported_methods.append(_RpcMethod.PING)
|
|
534
476
|
self._supported_methods = tuple(supported_methods)
|
|
535
477
|
|
|
536
|
-
return cast(BaseRpcProxy,
|
|
478
|
+
return cast(BaseRpcProxy, _AioXmlRpcProxyFromSession())
|
|
537
479
|
|
|
538
480
|
|
|
539
481
|
async def get_central_client_factory(
|
|
540
|
-
|
|
541
|
-
address_device_translation:
|
|
482
|
+
player: SessionPlayer,
|
|
483
|
+
address_device_translation: set[str],
|
|
542
484
|
do_mock_client: bool,
|
|
543
485
|
ignore_devices_on_create: list[str] | None,
|
|
544
486
|
ignore_custom_device_definition_models: list[str] | None,
|
|
@@ -546,16 +488,16 @@ async def get_central_client_factory(
|
|
|
546
488
|
) -> AsyncGenerator[tuple[CentralUnit, Client | Mock, FactoryWithClient]]:
|
|
547
489
|
"""Return central factory."""
|
|
548
490
|
factory = FactoryWithClient(
|
|
549
|
-
|
|
491
|
+
player=player,
|
|
550
492
|
address_device_translation=address_device_translation,
|
|
551
|
-
ignore_devices_on_create=ignore_devices_on_create,
|
|
552
|
-
)
|
|
553
|
-
central_client = await factory.get_default_central(
|
|
554
493
|
do_mock_client=do_mock_client,
|
|
555
494
|
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
495
|
+
ignore_devices_on_create=ignore_devices_on_create,
|
|
556
496
|
un_ignore_list=un_ignore_list,
|
|
557
497
|
)
|
|
558
|
-
central
|
|
498
|
+
central = await factory.get_default_central()
|
|
499
|
+
client = central.primary_client
|
|
500
|
+
assert client
|
|
559
501
|
try:
|
|
560
502
|
yield central, client, factory
|
|
561
503
|
finally:
|
|
@@ -563,8 +505,15 @@ async def get_central_client_factory(
|
|
|
563
505
|
await central.clear_files()
|
|
564
506
|
|
|
565
507
|
|
|
566
|
-
def get_mock(
|
|
508
|
+
def get_mock(
|
|
509
|
+
instance: Any, exclude_methods: set[str] | None = None, include_properties: set[str] | None = None, **kwargs: Any
|
|
510
|
+
) -> Any:
|
|
567
511
|
"""Create a mock and copy instance attributes over mock."""
|
|
512
|
+
if exclude_methods is None:
|
|
513
|
+
exclude_methods = set()
|
|
514
|
+
if include_properties is None:
|
|
515
|
+
include_properties = set()
|
|
516
|
+
|
|
568
517
|
if isinstance(instance, Mock):
|
|
569
518
|
instance.__dict__.update(instance._mock_wraps.__dict__)
|
|
570
519
|
return instance
|
|
@@ -573,8 +522,8 @@ def get_mock(instance: Any, **kwargs: Any) -> Any:
|
|
|
573
522
|
try:
|
|
574
523
|
for method_name in [
|
|
575
524
|
prop
|
|
576
|
-
for prop in _get_not_mockable_method_names(instance)
|
|
577
|
-
if prop not in
|
|
525
|
+
for prop in _get_not_mockable_method_names(instance=instance, exclude_methods=exclude_methods)
|
|
526
|
+
if prop not in include_properties and prop not in kwargs
|
|
578
527
|
]:
|
|
579
528
|
setattr(mock, method_name, getattr(instance, method_name))
|
|
580
529
|
except Exception:
|
|
@@ -632,6 +581,13 @@ async def get_pydev_ccu_central_unit_full(
|
|
|
632
581
|
return central
|
|
633
582
|
|
|
634
583
|
|
|
584
|
+
def load_device_description(file_name: str) -> Any:
|
|
585
|
+
"""Load device description."""
|
|
586
|
+
dev_desc = _load_json_file(anchor="pydevccu", resource="device_descriptions", file_name=file_name)
|
|
587
|
+
assert dev_desc
|
|
588
|
+
return dev_desc
|
|
589
|
+
|
|
590
|
+
|
|
635
591
|
async def get_session_player(*, file_name: str) -> SessionPlayer:
|
|
636
592
|
"""Provide a SessionPlayer preloaded from the randomized full session JSON file."""
|
|
637
593
|
player = SessionPlayer(file_id=file_name)
|
|
@@ -640,13 +596,6 @@ async def get_session_player(*, file_name: str) -> SessionPlayer:
|
|
|
640
596
|
return player
|
|
641
597
|
|
|
642
598
|
|
|
643
|
-
def load_device_description(file_name: str) -> Any:
|
|
644
|
-
"""Load device description."""
|
|
645
|
-
dev_desc = _load_json_file(anchor="pydevccu", resource="device_descriptions", file_name=file_name)
|
|
646
|
-
assert dev_desc
|
|
647
|
-
return dev_desc
|
|
648
|
-
|
|
649
|
-
|
|
650
599
|
class SessionPlayer:
|
|
651
600
|
"""Player for sessions."""
|
|
652
601
|
|
|
@@ -655,7 +604,7 @@ class SessionPlayer:
|
|
|
655
604
|
)
|
|
656
605
|
|
|
657
606
|
def __init__(self, *, file_id: str) -> None:
|
|
658
|
-
"""Initialize the session
|
|
607
|
+
"""Initialize the session player."""
|
|
659
608
|
self._file_id = file_id
|
|
660
609
|
|
|
661
610
|
async def load(self, *, file_path: str) -> DataOperationResult:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.16}/client_local.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|