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.

Files changed (16) hide show
  1. {aiohomematic_test_support-2025.10.14/aiohomematic_test_support.egg-info → aiohomematic_test_support-2025.10.15}/PKG-INFO +1 -1
  2. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/__init__.py +1 -1
  3. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15/aiohomematic_test_support.egg-info}/PKG-INFO +1 -1
  4. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/const.py +49 -7
  5. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/support.py +177 -228
  6. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/MANIFEST.in +0 -0
  7. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/README.md +0 -0
  8. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/SOURCES.txt +0 -0
  9. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/dependency_links.txt +0 -0
  10. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/aiohomematic_test_support.egg-info/top_level.txt +0 -0
  11. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/client_local.py +0 -0
  12. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/data/full_session_randomized_ccu.zip +0 -0
  13. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/data/full_session_randomized_pydevccu.zip +0 -0
  14. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/py.typed +0 -0
  15. {aiohomematic_test_support-2025.10.14 → aiohomematic_test_support-2025.10.15}/pyproject.toml +0 -0
  16. {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.14
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.14"
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.14
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, Final, cast
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 FactoryWithLocalClient:
42
+ class FactoryWithClient:
48
43
  """Factory for a central with one local client."""
49
44
 
50
- def __init__(self, *, client_session: ClientSession | None = None):
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
- port: int,
87
- address_device_translation: dict[str, str],
48
+ player: SessionPlayer,
49
+ address_device_translation: set[str] | None = None,
88
50
  do_mock_client: bool = True,
89
- ignore_devices_on_create: list[str] | None = None,
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
- ignore_custom_device_definition_models: list[str] | None = None,
134
- ) -> tuple[CentralUnit, Client | Mock]:
135
- """Return a central based on give address_device_translation."""
136
- central, client = await self.get_unpatched_default_central(
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=True,
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
- patch("aiohomematic.central.CentralUnit._get_primary_client", return_value=client).start()
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
- recorder: SessionPlayer,
174
- address_device_translation: dict[str, str] | None = None,
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
- recorder=recorder,
180
- address_device_translation=address_device_translation,
181
- ignore_devices_on_create=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
- recorder=recorder,
185
- address_device_translation=address_device_translation,
186
- ignore_devices_on_create=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.system_event_mock = MagicMock()
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 = {interface_config} if interface_config else set()
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=frozenset(un_ignore_list or []),
209
- ignore_custom_device_definition_models=frozenset(ignore_custom_device_definition_models or []),
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.get_unpatched_default_central(
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 do_mock_client:
149
+ if self._do_mock_client:
261
150
  _orig_create_client = ClientConfig.create_client
262
151
 
263
- async def _mocked_create_client(self: ClientConfig) -> Client | Mock:
264
- real_client = await _orig_create_client(self)
265
- return cast(Mock, get_mock(real_client))
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 EXCLUDE_METHODS_FROM_MOCKS:
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
- recorder: SessionPlayer,
312
- address_device_translation: dict[str, str] | None = None,
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 SimpleSessionRecorder (JSON-RPC).
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 recorder using the provided method and params.
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
- "result": None,
327
- "error": {"name": "-1", "code": -1, "message": "Not found in session recorder"},
328
- "id": 0,
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({"result": "200"})
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({"result": "200"})
310
+ return _MockResponse({_JsonKey.RESULT: "200", _JsonKey.ERROR: None})
389
311
 
390
- json_data = recorder.get_latest_response_by_params(
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
- # if method == _JsonRpcMethod.INTERFACE_LIST_DEVICES:
396
- # if address_device_translation:
397
- # devices: dict[str, Any] = {}
398
- # for address, device in json_data["result"].items():
399
- # if address in address_device_translation:
400
- # device["ADDRESS"] = address_device_translation[address]
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
- recorder: SessionPlayer,
412
- address_device_translation: dict[str, str] | None = None,
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 SimpleSessionRecorder (XML-RPC).
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 recorder using the provided method and positional params.
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 _AioXmlRpcProxyFromRecorder:
368
+ class _AioXmlRpcProxyFromSession:
437
369
  def __init__(self) -> None:
438
- self._recorder = recorder
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._recorder.get_latest_response_by_params(
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 device in devices:
444
+ for dd in devices:
499
445
  if ignore_devices_on_create is not None and (
500
- device["ADDRESS"] in ignore_devices_on_create or device["PARENT"] in ignore_devices_on_create
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
- device["ADDRESS"] in address_device_translation
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(device)
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._recorder.get_latest_response_by_params(
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, _AioXmlRpcProxyFromRecorder())
479
+ return cast(BaseRpcProxy, _AioXmlRpcProxyFromSession())
537
480
 
538
481
 
539
482
  async def get_central_client_factory(
540
- recorder: SessionPlayer,
541
- address_device_translation: dict[str, str],
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
- recorder=recorder,
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(instance: Any, **kwargs: Any) -> Any:
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 INCLUDE_PROPERTIES_IN_MOCKS and prop not in kwargs
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 recorder."""
607
+ """Initialize the session player."""
659
608
  self._file_id = file_id
660
609
 
661
610
  async def load(self, *, file_path: str) -> DataOperationResult: