aiohomematic-test-support 2025.12.51__tar.gz → 2025.12.54__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.
Files changed (20) hide show
  1. {aiohomematic_test_support-2025.12.51/aiohomematic_test_support.egg-info → aiohomematic_test_support-2025.12.54}/PKG-INFO +1 -1
  2. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/__init__.py +30 -1
  3. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54/aiohomematic_test_support.egg-info}/PKG-INFO +1 -1
  4. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/aiohomematic_test_support.egg-info/SOURCES.txt +4 -0
  5. aiohomematic_test_support-2025.12.54/event_capture.py +256 -0
  6. aiohomematic_test_support-2025.12.54/event_mock.py +300 -0
  7. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/factory.py +3 -3
  8. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/MANIFEST.in +0 -0
  9. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/README.md +0 -0
  10. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/aiohomematic_test_support.egg-info/dependency_links.txt +0 -0
  11. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/aiohomematic_test_support.egg-info/top_level.txt +0 -0
  12. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/const.py +0 -0
  13. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/data/device_translation.json +0 -0
  14. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/data/full_session_randomized_ccu.zip +0 -0
  15. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/data/full_session_randomized_pydevccu.zip +0 -0
  16. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/helper.py +0 -0
  17. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/mock.py +0 -0
  18. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/py.typed +0 -0
  19. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/pyproject.toml +0 -0
  20. {aiohomematic_test_support-2025.12.51 → aiohomematic_test_support-2025.12.54}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic-test-support
3
- Version: 2025.12.51
3
+ Version: 2025.12.54
4
4
  Summary: Support-only package for AioHomematic (tests/dev). Not part of production builds.
5
5
  Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/SukramJ/aiohomematic
@@ -18,6 +18,8 @@ Key Components
18
18
  playback support for deterministic testing.
19
19
  - **helper**: Test helper utilities for common testing operations.
20
20
  - **const**: Test-specific constants and configuration values.
21
+ - **event_capture**: Event capture and assertion utilities for behavior testing.
22
+ - **event_mock**: Event-driven mock server for test triggers.
21
23
 
22
24
  Usage Example
23
25
  -------------
@@ -35,6 +37,33 @@ Using the factory to create a test central with session playback:
35
37
  device = central.device_coordinator.get_device_by_address("VCU0000001")
36
38
  await central.stop()
37
39
 
40
+ Using EventCapture for behavior-focused testing:
41
+
42
+ from aiohomematic_test_support.event_capture import EventCapture
43
+ from aiohomematic.central.event_bus import CircuitBreakerTrippedEvent
44
+
45
+ capture = EventCapture()
46
+ capture.subscribe_to(central.event_bus, CircuitBreakerTrippedEvent)
47
+
48
+ # ... trigger failures ...
49
+
50
+ capture.assert_event_emitted(CircuitBreakerTrippedEvent, failure_count=5)
51
+ capture.cleanup()
52
+
53
+ Using EventDrivenMockServer for event-triggered test behavior:
54
+
55
+ from aiohomematic_test_support.event_mock import EventDrivenMockServer
56
+ from aiohomematic.central.event_bus import DataRefreshTriggeredEvent
57
+
58
+ mock_server = EventDrivenMockServer(event_bus=central.event_bus)
59
+ mock_server.when(DataRefreshTriggeredEvent).then_call(
60
+ lambda event: inject_mock_data()
61
+ )
62
+
63
+ # ... trigger refresh ...
64
+
65
+ mock_server.cleanup()
66
+
38
67
  The session player replays pre-recorded backend responses, enabling fast and
39
68
  reproducible tests without backend dependencies.
40
69
 
@@ -47,4 +76,4 @@ test dependencies to access test support functionality.
47
76
 
48
77
  from __future__ import annotations
49
78
 
50
- __version__ = "2025.12.51"
79
+ __version__ = "2025.12.54"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiohomematic-test-support
3
- Version: 2025.12.51
3
+ Version: 2025.12.54
4
4
  Summary: Support-only package for AioHomematic (tests/dev). Not part of production builds.
5
5
  Author-email: SukramJ <sukramj@icloud.com>, Daniel Perna <danielperna84@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/SukramJ/aiohomematic
@@ -2,6 +2,8 @@ MANIFEST.in
2
2
  README.md
3
3
  __init__.py
4
4
  const.py
5
+ event_capture.py
6
+ event_mock.py
5
7
  factory.py
6
8
  helper.py
7
9
  mock.py
@@ -9,6 +11,8 @@ py.typed
9
11
  pyproject.toml
10
12
  ./__init__.py
11
13
  ./const.py
14
+ ./event_capture.py
15
+ ./event_mock.py
12
16
  ./factory.py
13
17
  ./helper.py
14
18
  ./mock.py
@@ -0,0 +1,256 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """
4
+ Event capture utilities for test verification.
5
+
6
+ This module provides tools for capturing and asserting events in tests,
7
+ enabling behavior-focused testing through event verification.
8
+
9
+ Public API
10
+ ----------
11
+ - EventCapture: Capture events for test verification
12
+ - EventSequenceAssertion: Verify event ordering
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Callable
18
+ from dataclasses import dataclass, field
19
+ from typing import TYPE_CHECKING, Any, TypeVar
20
+
21
+ if TYPE_CHECKING:
22
+ from aiohomematic.central.event_bus import Event, EventBus
23
+
24
+ T = TypeVar("T", bound="Event")
25
+
26
+
27
+ @dataclass
28
+ class EventCapture:
29
+ """
30
+ Capture events from EventBus for test verification.
31
+
32
+ Provides methods to subscribe to events, capture them, and make assertions
33
+ about what events were emitted during test execution.
34
+
35
+ Example:
36
+ -------
37
+ capture = EventCapture()
38
+ capture.subscribe_to(event_bus, CircuitBreakerTrippedEvent)
39
+
40
+ # ... perform test actions ...
41
+
42
+ capture.assert_event_emitted(
43
+ CircuitBreakerTrippedEvent,
44
+ interface_id="test",
45
+ failure_count=5,
46
+ )
47
+ capture.cleanup()
48
+
49
+ """
50
+
51
+ captured_events: list[Event] = field(default_factory=list)
52
+ """List of all captured events."""
53
+
54
+ _unsubscribers: list[Callable[[], None]] = field(default_factory=list)
55
+ """Unsubscribe callbacks for cleanup."""
56
+
57
+ def assert_event_emitted(
58
+ self,
59
+ *,
60
+ event_type: type[Event],
61
+ count: int | None = None,
62
+ **expected_attrs: Any,
63
+ ) -> None:
64
+ """
65
+ Assert that an event with specific attributes was emitted.
66
+
67
+ Args:
68
+ event_type: The expected event type
69
+ count: If provided, assert exactly this many events were emitted
70
+ **expected_attrs: Expected attribute values on the event
71
+
72
+ Raises:
73
+ AssertionError: If no matching event found or count mismatch
74
+
75
+ """
76
+ events = self.get_events_of_type(event_type=event_type)
77
+
78
+ if count is not None and len(events) != count:
79
+ raise AssertionError(f"Expected {count} {event_type.__name__} events, got {len(events)}")
80
+
81
+ if not expected_attrs:
82
+ if not events:
83
+ raise AssertionError(f"No {event_type.__name__} events captured")
84
+ return
85
+
86
+ for event in events:
87
+ if all(getattr(event, k, None) == v for k, v in expected_attrs.items()):
88
+ return
89
+
90
+ raise AssertionError(
91
+ f"No {event_type.__name__} found with attributes {expected_attrs}. Captured {len(events)} events: {events}"
92
+ )
93
+
94
+ def assert_no_event(self, *, event_type: type[Event]) -> None:
95
+ """
96
+ Assert that no events of a specific type were emitted.
97
+
98
+ Args:
99
+ event_type: The event type that should not exist
100
+
101
+ Raises:
102
+ AssertionError: If any events of that type were captured
103
+
104
+ """
105
+ if events := self.get_events_of_type(event_type=event_type):
106
+ raise AssertionError(f"Expected no {event_type.__name__} events, but found {len(events)}: {events}")
107
+
108
+ def cleanup(self) -> None:
109
+ """Unsubscribe from all events and clear captured events."""
110
+ for unsub in self._unsubscribers:
111
+ unsub()
112
+ self._unsubscribers.clear()
113
+ self.captured_events.clear()
114
+
115
+ def clear(self) -> None:
116
+ """Clear all captured events."""
117
+ self.captured_events.clear()
118
+
119
+ def get_event_count(self, *, event_type: type[Event]) -> int:
120
+ """
121
+ Return count of captured events of a specific type.
122
+
123
+ Args:
124
+ event_type: The event type to count
125
+
126
+ Returns:
127
+ Number of events of that type
128
+
129
+ """
130
+ return len(self.get_events_of_type(event_type=event_type))
131
+
132
+ def get_events_of_type(self, *, event_type: type[T]) -> list[T]:
133
+ """
134
+ Return all captured events of a specific type.
135
+
136
+ Args:
137
+ event_type: The event type to filter by
138
+
139
+ Returns:
140
+ List of events matching the type
141
+
142
+ """
143
+ return [e for e in self.captured_events if isinstance(e, event_type)]
144
+
145
+ def subscribe_to(self, event_bus: EventBus, *event_types: type[Event]) -> None:
146
+ """
147
+ Subscribe to specific event types on the EventBus.
148
+
149
+ Args:
150
+ event_bus: The EventBus to subscribe to
151
+ *event_types: Event types to capture
152
+
153
+ """
154
+ for event_type in event_types:
155
+ unsubscribe = event_bus.subscribe(
156
+ event_type=event_type,
157
+ event_key=None,
158
+ handler=self._capture_handler,
159
+ )
160
+ self._unsubscribers.append(unsubscribe)
161
+
162
+ def _capture_handler(self, *, event: Event) -> None:
163
+ """Handle event by capturing it."""
164
+ self.captured_events.append(event)
165
+
166
+
167
+ @dataclass
168
+ class EventSequenceAssertion:
169
+ """
170
+ Assert events occur in expected sequence.
171
+
172
+ Useful for verifying state machine transitions and multi-step processes.
173
+
174
+ Example:
175
+ -------
176
+ sequence = EventSequenceAssertion(expected_sequence=[
177
+ ConnectionStageChangedEvent,
178
+ ClientStateChangedEvent,
179
+ CentralStateChangedEvent,
180
+ ])
181
+ event_bus.subscribe(Event, sequence.on_event)
182
+
183
+ # ... perform actions ...
184
+
185
+ sequence.verify()
186
+
187
+ """
188
+
189
+ expected_sequence: list[type[Event]]
190
+ """The expected sequence of event types."""
191
+
192
+ captured_types: list[type[Event]] = field(default_factory=list)
193
+ """Captured event types in order."""
194
+
195
+ def on_event(self, *, event: Event) -> None:
196
+ """
197
+ Handle event by capturing its type.
198
+
199
+ Args:
200
+ event: The event that was published
201
+
202
+ """
203
+ self.captured_types.append(type(event))
204
+
205
+ def reset(self) -> None:
206
+ """Reset captured types for reuse."""
207
+ self.captured_types.clear()
208
+
209
+ def verify(self, *, strict: bool = False) -> None:
210
+ """
211
+ Verify the captured sequence matches expected.
212
+
213
+ Args:
214
+ strict: If True, require exact sequence match.
215
+ If False, verify expected events appear in order
216
+ (other events may be interspersed).
217
+
218
+ Raises:
219
+ AssertionError: If sequence doesn't match
220
+
221
+ """
222
+ if strict:
223
+ self._verify_strict()
224
+ else:
225
+ self._verify_subsequence()
226
+
227
+ def _verify_strict(self) -> None:
228
+ """Verify exact sequence match."""
229
+ if len(self.captured_types) != len(self.expected_sequence):
230
+ raise AssertionError(
231
+ f"Expected {len(self.expected_sequence)} events, "
232
+ f"got {len(self.captured_types)}.\n"
233
+ f"Expected: {[e.__name__ for e in self.expected_sequence]}\n"
234
+ f"Got: {[e.__name__ for e in self.captured_types]}"
235
+ )
236
+
237
+ for i, (expected, actual) in enumerate(zip(self.expected_sequence, self.captured_types, strict=True)):
238
+ if expected != actual:
239
+ raise AssertionError(f"Event {i}: expected {expected.__name__}, got {actual.__name__}")
240
+
241
+ def _verify_subsequence(self) -> None:
242
+ """Verify expected events appear in order (non-strict)."""
243
+ expected_idx = 0
244
+ for actual_type in self.captured_types:
245
+ if expected_idx >= len(self.expected_sequence):
246
+ break
247
+ if actual_type == self.expected_sequence[expected_idx]:
248
+ expected_idx += 1
249
+
250
+ if expected_idx < len(self.expected_sequence):
251
+ missing = self.expected_sequence[expected_idx:]
252
+ raise AssertionError(
253
+ f"Missing expected events in sequence: {[e.__name__ for e in missing]}\n"
254
+ f"Expected: {[e.__name__ for e in self.expected_sequence]}\n"
255
+ f"Got: {[e.__name__ for e in self.captured_types]}"
256
+ )
@@ -0,0 +1,300 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2021-2025
3
+ """
4
+ Event-driven mock server for test triggers.
5
+
6
+ Overview
7
+ --------
8
+ This module provides a mock server that responds to events by injecting
9
+ data or triggering actions. This enables event-based test orchestration
10
+ where test behavior is triggered by events rather than explicit calls.
11
+
12
+ The EventDrivenMockServer allows tests to:
13
+ - Configure responses to specific event types
14
+ - Inject mock data when certain events are published
15
+ - Build complex test scenarios with event-driven behavior
16
+
17
+ Public API
18
+ ----------
19
+ - EventDrivenMockServer: Main mock server class
20
+ - ResponseBuilder: Fluent builder for configuring responses
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from collections.abc import Callable
26
+ from dataclasses import dataclass, field
27
+ from typing import TYPE_CHECKING, Any
28
+
29
+ if TYPE_CHECKING:
30
+ from aiohomematic.central.event_bus import Event, EventBus
31
+
32
+
33
+ @dataclass
34
+ class MockAction:
35
+ """
36
+ An action to perform when an event is received.
37
+
38
+ Attributes:
39
+ ----------
40
+ handler: The callback to invoke when the event is received
41
+ filter_fn: Optional filter function to match specific events
42
+ one_shot: If True, the action is removed after first invocation
43
+
44
+ """
45
+
46
+ handler: Callable[[Any], Any]
47
+ filter_fn: Callable[[Any], bool] | None = None
48
+ one_shot: bool = False
49
+
50
+
51
+ @dataclass
52
+ class EventDrivenMockServer:
53
+ """
54
+ Mock server that responds to events.
55
+
56
+ Provides a fluent API for configuring responses to specific event types.
57
+ When an event is published that matches a configured response, the
58
+ corresponding action is executed.
59
+
60
+ Example Usage
61
+ -------------
62
+ mock_server = EventDrivenMockServer(event_bus=central.event_bus)
63
+
64
+ # Configure response to inject data when refresh is triggered
65
+ mock_server.when(DataRefreshTriggeredEvent).then_call(
66
+ lambda event: inject_mock_data()
67
+ )
68
+
69
+ # Configure one-shot response
70
+ mock_server.when(CircuitBreakerTrippedEvent).once().then_call(
71
+ lambda event: trigger_recovery()
72
+ )
73
+
74
+ # Later, cleanup:
75
+ mock_server.cleanup()
76
+
77
+ """
78
+
79
+ _event_bus: EventBus = field(repr=False)
80
+ """The EventBus to subscribe to."""
81
+
82
+ _responses: dict[type[Event], list[MockAction]] = field(default_factory=dict)
83
+ """Mapping of event types to their configured actions."""
84
+
85
+ _unsubscribers: list[Callable[[], None]] = field(default_factory=list)
86
+ """Unsubscribe callbacks for cleanup."""
87
+
88
+ _invocation_count: dict[type[Event], int] = field(default_factory=dict)
89
+ """Count of invocations per event type."""
90
+
91
+ def cleanup(self) -> None:
92
+ """Unsubscribe from all events and clear responses."""
93
+ for unsub in self._unsubscribers:
94
+ unsub()
95
+ self._unsubscribers.clear()
96
+ self._responses.clear()
97
+ self._invocation_count.clear()
98
+
99
+ def get_invocation_count(self, *, event_type: type[Event]) -> int:
100
+ """
101
+ Return the number of times handlers were invoked for an event type.
102
+
103
+ Args:
104
+ ----
105
+ event_type: The event type to query
106
+
107
+ Returns:
108
+ -------
109
+ Number of handler invocations
110
+
111
+ """
112
+ return self._invocation_count.get(event_type, 0)
113
+
114
+ def when[T: Event](self, *, event_type: type[T]) -> ResponseBuilder[T]:
115
+ """
116
+ Start configuring a response for an event type.
117
+
118
+ Args:
119
+ ----
120
+ event_type: The event type to respond to
121
+
122
+ Returns:
123
+ -------
124
+ A ResponseBuilder for configuring the response
125
+
126
+ """
127
+ return ResponseBuilder(mock_server=self, event_type=event_type)
128
+
129
+ def _add_response(
130
+ self,
131
+ *,
132
+ event_type: type[Event],
133
+ action: MockAction,
134
+ ) -> None:
135
+ """
136
+ Add a response for an event type.
137
+
138
+ If this is the first response for this event type, subscribe to it.
139
+
140
+ Args:
141
+ ----
142
+ event_type: The event type to respond to
143
+ action: The action to perform
144
+
145
+ """
146
+ if event_type not in self._responses:
147
+ self._responses[event_type] = []
148
+ # Subscribe to this event type
149
+ unsub = self._event_bus.subscribe(
150
+ event_type=event_type,
151
+ event_key=None,
152
+ handler=lambda event, et=event_type: self._on_event(event=event, event_type=et),
153
+ )
154
+ self._unsubscribers.append(unsub)
155
+
156
+ self._responses[event_type].append(action)
157
+
158
+ def _on_event(self, *, event: Event, event_type: type[Event]) -> None:
159
+ """
160
+ Handle an event by invoking matching responses.
161
+
162
+ Args:
163
+ ----
164
+ event: The event that was published
165
+ event_type: The event type (for lookup)
166
+
167
+ """
168
+ if event_type not in self._responses:
169
+ return
170
+
171
+ # Track invocations
172
+ self._invocation_count[event_type] = self._invocation_count.get(event_type, 0) + 1
173
+
174
+ # Process actions (copy list to allow modification during iteration)
175
+ actions_to_remove: list[MockAction] = []
176
+ for action in list(self._responses[event_type]):
177
+ # Check filter if present
178
+ if action.filter_fn is not None and not action.filter_fn(event):
179
+ continue
180
+
181
+ # Invoke handler
182
+ action.handler(event)
183
+
184
+ # Mark one-shot actions for removal
185
+ if action.one_shot:
186
+ actions_to_remove.append(action)
187
+
188
+ # Remove one-shot actions
189
+ for action in actions_to_remove:
190
+ self._responses[event_type].remove(action)
191
+
192
+
193
+ @dataclass
194
+ class ResponseBuilder[T: "Event"]:
195
+ """
196
+ Fluent builder for configuring mock responses.
197
+
198
+ Provides a chainable API for configuring how the mock server should
199
+ respond to specific events.
200
+
201
+ Example:
202
+ -------
203
+ mock_server.when(SomeEvent)
204
+ .matching(lambda e: e.interface_id == "test")
205
+ .once()
206
+ .then_call(handler_fn)
207
+
208
+ """
209
+
210
+ mock_server: EventDrivenMockServer
211
+ """The mock server to configure."""
212
+
213
+ event_type: type[T]
214
+ """The event type being configured."""
215
+
216
+ _filter_fn: Callable[[T], bool] | None = None
217
+ """Optional filter function."""
218
+
219
+ _one_shot: bool = False
220
+ """Whether this is a one-shot response."""
221
+
222
+ def matching(self, *, filter_fn: Callable[[T], bool]) -> ResponseBuilder[T]:
223
+ """
224
+ Add a filter to match specific events.
225
+
226
+ Args:
227
+ ----
228
+ filter_fn: Function that returns True for matching events
229
+
230
+ Returns:
231
+ -------
232
+ Self for chaining
233
+
234
+ """
235
+ self._filter_fn = filter_fn
236
+ return self
237
+
238
+ def once(self) -> ResponseBuilder[T]:
239
+ """
240
+ Make this a one-shot response (removed after first invocation).
241
+
242
+ Returns
243
+ -------
244
+ Self for chaining
245
+
246
+ """
247
+ self._one_shot = True
248
+ return self
249
+
250
+ def then_call(self, *, handler: Callable[[T], Any]) -> None:
251
+ """
252
+ Configure the handler to call when the event is received.
253
+
254
+ Args:
255
+ ----
256
+ handler: Function to call with the event
257
+
258
+ """
259
+ self.mock_server._add_response( # noqa: SLF001 # pylint: disable=protected-access
260
+ event_type=self.event_type,
261
+ action=MockAction(
262
+ handler=handler,
263
+ filter_fn=self._filter_fn,
264
+ one_shot=self._one_shot,
265
+ ),
266
+ )
267
+
268
+ def then_publish(self, *, event_factory: Callable[[T], Event]) -> None:
269
+ """
270
+ Configure the mock to publish another event when this event is received.
271
+
272
+ Args:
273
+ ----
274
+ event_factory: Function that creates the event to publish
275
+
276
+ """
277
+
278
+ def publish_handler(event: T) -> None:
279
+ new_event = event_factory(event)
280
+ self.mock_server._event_bus.publish_sync(event=new_event) # noqa: SLF001 # pylint: disable=protected-access
281
+
282
+ self.then_call(handler=publish_handler)
283
+
284
+
285
+ def create_event_mock_server(*, event_bus: EventBus) -> EventDrivenMockServer:
286
+ """
287
+ Create an EventDrivenMockServer instance.
288
+
289
+ Factory function for creating an event-driven mock server.
290
+
291
+ Args:
292
+ ----
293
+ event_bus: The EventBus to subscribe to
294
+
295
+ Returns:
296
+ -------
297
+ Configured EventDrivenMockServer instance
298
+
299
+ """
300
+ return EventDrivenMockServer(_event_bus=event_bus)
@@ -48,7 +48,7 @@ from aiohomematic.central.integration_events import (
48
48
  DeviceLifecycleEvent,
49
49
  DeviceLifecycleEventType,
50
50
  DeviceTriggerEvent,
51
- SystemStatusEvent,
51
+ SystemStatusChangedEvent,
52
52
  )
53
53
  from aiohomematic.client import ClientConfig, InterfaceConfig
54
54
  from aiohomematic.const import LOCAL_HOST, Interface, OptionalSettings
@@ -171,7 +171,7 @@ class FactoryWithClient:
171
171
  """Handle device trigger events."""
172
172
  self.ha_event_mock(event)
173
173
 
174
- def _system_status_event_handler(event: SystemStatusEvent) -> None:
174
+ def _system_status_event_handler(event: SystemStatusChangedEvent) -> None:
175
175
  """Handle system status events (issues, state changes)."""
176
176
  self.ha_event_mock(event)
177
177
 
@@ -192,7 +192,7 @@ class FactoryWithClient:
192
192
  )
193
193
  self._event_bus_unsubscribe_callbacks.append(
194
194
  central.event_bus.subscribe(
195
- event_type=SystemStatusEvent, event_key=None, handler=_system_status_event_handler
195
+ event_type=SystemStatusChangedEvent, event_key=None, handler=_system_status_event_handler
196
196
  )
197
197
  )
198
198