aiohomematic-test-support 2025.10.14__tar.gz → 2025.10.15__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.15}/PKG-INFO +1 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/__init__.py +1 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15/aiohomematic_test_support.egg-info}/PKG-INFO +1 -1
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/const.py +49 -7
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/support.py +177 -228
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/MANIFEST.in +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/README.md +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/SOURCES.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/dependency_links.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/top_level.txt +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/client_local.py +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/data/full_session_randomized_ccu.zip +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/data/full_session_randomized_pydevccu.zip +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/py.typed +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/pyproject.toml +0 -0
- {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/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.15
|
|
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
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "2025.10.
|
|
1
|
+
__version__ = "2025.10.15"
|
|
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.15
|
|
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
|
|
@@ -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,58 @@ 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) -> tuple[CentralUnit, Client | Mock]:
|
|
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
165
|
await central.start()
|
|
270
|
-
|
|
166
|
+
await central._init_hub()
|
|
271
167
|
client = central.primary_client
|
|
272
168
|
assert central
|
|
273
169
|
assert client
|
|
274
170
|
return central, client
|
|
275
171
|
|
|
276
172
|
|
|
277
|
-
def _get_not_mockable_method_names(instance: Any) -> set[str]:
|
|
173
|
+
def _get_not_mockable_method_names(instance: Any, exclude_methods: set[str]) -> set[str]:
|
|
278
174
|
"""Return all relevant method names for mocking."""
|
|
279
175
|
methods: set[str] = set(_get_properties(data_object=instance, decorator=property))
|
|
280
176
|
|
|
281
177
|
for method in dir(instance):
|
|
282
|
-
if method in
|
|
178
|
+
if method in exclude_methods:
|
|
283
179
|
methods.add(method)
|
|
284
180
|
return methods
|
|
285
181
|
|
|
@@ -306,26 +202,26 @@ def _load_json_file(anchor: str, resource: str, file_name: str) -> Any | None:
|
|
|
306
202
|
return orjson.loads(fptr.read())
|
|
307
203
|
|
|
308
204
|
|
|
309
|
-
def _get_client_session(
|
|
205
|
+
def _get_client_session( # noqa: C901
|
|
310
206
|
*,
|
|
311
|
-
|
|
312
|
-
address_device_translation:
|
|
207
|
+
player: SessionPlayer,
|
|
208
|
+
address_device_translation: set[str] | None = None,
|
|
313
209
|
ignore_devices_on_create: list[str] | None = None,
|
|
314
210
|
) -> ClientSession:
|
|
315
211
|
"""
|
|
316
|
-
Provide a ClientSession-like fixture that answers via
|
|
212
|
+
Provide a ClientSession-like fixture that answers via SessionPlayer(JSON-RPC).
|
|
317
213
|
|
|
318
214
|
Any POST request will be answered by looking up the latest recorded
|
|
319
|
-
JSON-RPC response in the session
|
|
215
|
+
JSON-RPC response in the session player using the provided method and params.
|
|
320
216
|
"""
|
|
321
217
|
|
|
322
218
|
class _MockResponse:
|
|
323
219
|
def __init__(self, json_data: dict | None) -> None:
|
|
324
220
|
# If no match is found, emulate backend error payload
|
|
325
221
|
self._json = json_data or {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
222
|
+
_JsonKey.RESULT: None,
|
|
223
|
+
_JsonKey.ERROR: {"name": "-1", "code": -1, "message": "Not found in session player"},
|
|
224
|
+
_JsonKey.ID: 0,
|
|
329
225
|
}
|
|
330
226
|
self.status = 200
|
|
331
227
|
|
|
@@ -365,6 +261,32 @@ def _get_client_session(
|
|
|
365
261
|
params = payload.get("params")
|
|
366
262
|
|
|
367
263
|
if self._central:
|
|
264
|
+
if method in (
|
|
265
|
+
_JsonRpcMethod.PROGRAM_EXECUTE,
|
|
266
|
+
_JsonRpcMethod.SYSVAR_SET_BOOL,
|
|
267
|
+
_JsonRpcMethod.SYSVAR_SET_FLOAT,
|
|
268
|
+
_JsonRpcMethod.SESSION_LOGOUT,
|
|
269
|
+
):
|
|
270
|
+
return _MockResponse({_JsonKey.ID: 0, _JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
271
|
+
if method == _JsonRpcMethod.SYSVAR_GET_ALL:
|
|
272
|
+
return _MockResponse(
|
|
273
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.SYSVAR_DATA_JSON, _JsonKey.ERROR: None}
|
|
274
|
+
)
|
|
275
|
+
if method == _JsonRpcMethod.PROGRAM_GET_ALL:
|
|
276
|
+
return _MockResponse(
|
|
277
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.PROGRAM_DATA_JSON, _JsonKey.ERROR: None}
|
|
278
|
+
)
|
|
279
|
+
if method == _JsonRpcMethod.REGA_RUN_SCRIPT:
|
|
280
|
+
if "get_program_descriptions" in params[_JsonKey.SCRIPT]:
|
|
281
|
+
return _MockResponse(
|
|
282
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.PROGRAM_DATA_JSON_DESCRIPTION, _JsonKey.ERROR: None}
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if "get_system_variable_descriptions" in params[_JsonKey.SCRIPT]:
|
|
286
|
+
return _MockResponse(
|
|
287
|
+
{_JsonKey.ID: 0, _JsonKey.RESULT: const.SYSVAR_DATA_JSON_DESCRIPTION, _JsonKey.ERROR: None}
|
|
288
|
+
)
|
|
289
|
+
|
|
368
290
|
if method == _JsonRpcMethod.INTERFACE_SET_VALUE:
|
|
369
291
|
await self._central.data_point_event(
|
|
370
292
|
interface_id=params[_JsonKey.INTERFACE],
|
|
@@ -372,7 +294,7 @@ def _get_client_session(
|
|
|
372
294
|
parameter=params[_JsonKey.VALUE_KEY],
|
|
373
295
|
value=params[_JsonKey.VALUE],
|
|
374
296
|
)
|
|
375
|
-
return _MockResponse({
|
|
297
|
+
return _MockResponse({_JsonKey.ID: 0, _JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
376
298
|
if method == _JsonRpcMethod.INTERFACE_PUT_PARAMSET:
|
|
377
299
|
if params[_JsonKey.PARAMSET_KEY] == ParamsetKey.VALUES:
|
|
378
300
|
interface_id = params[_JsonKey.INTERFACE]
|
|
@@ -385,19 +307,29 @@ def _get_client_session(
|
|
|
385
307
|
parameter=param,
|
|
386
308
|
value=value,
|
|
387
309
|
)
|
|
388
|
-
return _MockResponse({
|
|
310
|
+
return _MockResponse({_JsonKey.RESULT: "200", _JsonKey.ERROR: None})
|
|
389
311
|
|
|
390
|
-
json_data =
|
|
312
|
+
json_data = player.get_latest_response_by_params(
|
|
391
313
|
rpc_type=RPCType.JSON_RPC,
|
|
392
314
|
method=str(method) if method is not None else "",
|
|
393
315
|
params=params,
|
|
394
316
|
)
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
317
|
+
if method == _JsonRpcMethod.INTERFACE_LIST_DEVICES and (
|
|
318
|
+
ignore_devices_on_create is not None or address_device_translation is not None
|
|
319
|
+
):
|
|
320
|
+
new_devices = []
|
|
321
|
+
for dd in json_data[_JsonKey.RESULT]:
|
|
322
|
+
if ignore_devices_on_create is not None and (
|
|
323
|
+
dd["address"] in ignore_devices_on_create or dd["parent"] in ignore_devices_on_create
|
|
324
|
+
):
|
|
325
|
+
continue
|
|
326
|
+
if address_device_translation is not None:
|
|
327
|
+
if dd["address"] in address_device_translation or dd["parent"] in address_device_translation:
|
|
328
|
+
new_devices.append(dd)
|
|
329
|
+
else:
|
|
330
|
+
new_devices.append(dd)
|
|
331
|
+
|
|
332
|
+
json_data[_JsonKey.RESULT] = new_devices
|
|
401
333
|
return _MockResponse(json_data)
|
|
402
334
|
|
|
403
335
|
async def close(self) -> None: # compatibility
|
|
@@ -408,16 +340,16 @@ def _get_client_session(
|
|
|
408
340
|
|
|
409
341
|
def _get_xml_rpc_proxy( # noqa: C901
|
|
410
342
|
*,
|
|
411
|
-
|
|
412
|
-
address_device_translation:
|
|
343
|
+
player: SessionPlayer,
|
|
344
|
+
address_device_translation: set[str] | None = None,
|
|
413
345
|
ignore_devices_on_create: list[str] | None = None,
|
|
414
346
|
) -> BaseRpcProxy:
|
|
415
347
|
"""
|
|
416
|
-
Provide an BaseRpcProxy-like fixture that answers via
|
|
348
|
+
Provide an BaseRpcProxy-like fixture that answers via SessionPlayer (XML-RPC).
|
|
417
349
|
|
|
418
350
|
Any method call like: await proxy.system.listMethods(...)
|
|
419
351
|
will be answered by looking up the latest recorded XML-RPC response
|
|
420
|
-
in the session
|
|
352
|
+
in the session player using the provided method and positional params.
|
|
421
353
|
"""
|
|
422
354
|
|
|
423
355
|
class _Method:
|
|
@@ -433,9 +365,9 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
433
365
|
# Forward to caller with collected method name and positional params
|
|
434
366
|
return await self._caller(self._name, *args)
|
|
435
367
|
|
|
436
|
-
class
|
|
368
|
+
class _AioXmlRpcProxyFromSession:
|
|
437
369
|
def __init__(self) -> None:
|
|
438
|
-
self.
|
|
370
|
+
self._player = player
|
|
439
371
|
self._supported_methods: tuple[str, ...] = ()
|
|
440
372
|
self._central: CentralUnit | None = None
|
|
441
373
|
|
|
@@ -448,6 +380,20 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
448
380
|
"""Return the supported methods."""
|
|
449
381
|
return self._supported_methods
|
|
450
382
|
|
|
383
|
+
async def getAllSystemVariables(self) -> dict[str, Any]:
|
|
384
|
+
"""Return all system variables."""
|
|
385
|
+
return const.SYSVAR_DATA_XML
|
|
386
|
+
|
|
387
|
+
async def getParamset(self, channel_address: str, paramset: str) -> Any:
|
|
388
|
+
"""Set a value."""
|
|
389
|
+
if self._central:
|
|
390
|
+
result = self._player.get_latest_response_by_params(
|
|
391
|
+
rpc_type=RPCType.XML_RPC,
|
|
392
|
+
method="getParamset",
|
|
393
|
+
params=(channel_address, paramset),
|
|
394
|
+
)
|
|
395
|
+
return result if result else {}
|
|
396
|
+
|
|
451
397
|
async def setValue(self, channel_address: str, parameter: str, value: Any, rx_mode: Any | None = None) -> None:
|
|
452
398
|
"""Set a value."""
|
|
453
399
|
if self._central:
|
|
@@ -485,7 +431,7 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
485
431
|
|
|
486
432
|
async def listDevices(self) -> list[Any]:
|
|
487
433
|
"""Return a list of devices."""
|
|
488
|
-
devices = self.
|
|
434
|
+
devices = self._player.get_latest_response_by_params(
|
|
489
435
|
rpc_type=RPCType.XML_RPC,
|
|
490
436
|
method="listDevices",
|
|
491
437
|
params="()",
|
|
@@ -495,19 +441,16 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
495
441
|
if ignore_devices_on_create is None and address_device_translation is None:
|
|
496
442
|
return cast(list[Any], devices)
|
|
497
443
|
|
|
498
|
-
for
|
|
444
|
+
for dd in devices:
|
|
499
445
|
if ignore_devices_on_create is not None and (
|
|
500
|
-
|
|
446
|
+
dd["ADDRESS"] in ignore_devices_on_create or dd["PARENT"] in ignore_devices_on_create
|
|
501
447
|
):
|
|
502
448
|
continue
|
|
503
449
|
if address_device_translation is not None:
|
|
504
|
-
if
|
|
505
|
-
|
|
506
|
-
or device["PARENT"] in address_device_translation
|
|
507
|
-
):
|
|
508
|
-
new_devices.append(device)
|
|
450
|
+
if dd["ADDRESS"] in address_device_translation or dd["PARENT"] in address_device_translation:
|
|
451
|
+
new_devices.append(dd)
|
|
509
452
|
else:
|
|
510
|
-
new_devices.append(
|
|
453
|
+
new_devices.append(dd)
|
|
511
454
|
|
|
512
455
|
return new_devices
|
|
513
456
|
|
|
@@ -517,7 +460,7 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
517
460
|
|
|
518
461
|
async def _invoke(self, method: str, *args: Any) -> Any:
|
|
519
462
|
params = tuple(args)
|
|
520
|
-
return self.
|
|
463
|
+
return self._player.get_latest_response_by_params(
|
|
521
464
|
rpc_type=RPCType.XML_RPC,
|
|
522
465
|
method=method,
|
|
523
466
|
params=params,
|
|
@@ -533,12 +476,12 @@ def _get_xml_rpc_proxy( # noqa: C901
|
|
|
533
476
|
supported_methods.append(_RpcMethod.PING)
|
|
534
477
|
self._supported_methods = tuple(supported_methods)
|
|
535
478
|
|
|
536
|
-
return cast(BaseRpcProxy,
|
|
479
|
+
return cast(BaseRpcProxy, _AioXmlRpcProxyFromSession())
|
|
537
480
|
|
|
538
481
|
|
|
539
482
|
async def get_central_client_factory(
|
|
540
|
-
|
|
541
|
-
address_device_translation:
|
|
483
|
+
player: SessionPlayer,
|
|
484
|
+
address_device_translation: set[str],
|
|
542
485
|
do_mock_client: bool,
|
|
543
486
|
ignore_devices_on_create: list[str] | None,
|
|
544
487
|
ignore_custom_device_definition_models: list[str] | None,
|
|
@@ -546,15 +489,14 @@ async def get_central_client_factory(
|
|
|
546
489
|
) -> AsyncGenerator[tuple[CentralUnit, Client | Mock, FactoryWithClient]]:
|
|
547
490
|
"""Return central factory."""
|
|
548
491
|
factory = FactoryWithClient(
|
|
549
|
-
|
|
492
|
+
player=player,
|
|
550
493
|
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
494
|
do_mock_client=do_mock_client,
|
|
555
495
|
ignore_custom_device_definition_models=ignore_custom_device_definition_models,
|
|
496
|
+
ignore_devices_on_create=ignore_devices_on_create,
|
|
556
497
|
un_ignore_list=un_ignore_list,
|
|
557
498
|
)
|
|
499
|
+
central_client = await factory.get_default_central()
|
|
558
500
|
central, client = central_client
|
|
559
501
|
try:
|
|
560
502
|
yield central, client, factory
|
|
@@ -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.15}/client_local.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|