pyg90alarm 2.5.0__tar.gz → 2.5.1__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.
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/PKG-INFO +1 -1
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/__init__.py +3 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/base_list.py +6 -1
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/exceptions.py +1 -1
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/PKG-INFO +1 -1
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/SOURCES.txt +1 -0
- pyg90alarm-2.5.1/tests/unit/entities/test_base_list.py +306 -0
- pyg90alarm-2.5.1/tests/unit/test_exceptions.py +16 -0
- pyg90alarm-2.5.0/tests/unit/entities/test_base_list.py +0 -192
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.github/CODEOWNERS +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.github/dependabot.yml +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.github/workflows/main.yml +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.gitignore +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.pylintrc +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/.readthedocs.yaml +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/LICENSE +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/MANIFEST.in +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/README.rst +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/.DS_Store +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/.gitignore +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/api-docs.rst +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/cloud-protocol.rst +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/conf.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/index.rst +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/local-protocol.rst +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/docs/requirements.txt +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/pyproject.toml +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/setup.cfg +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/setup.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/sonar-project.properties +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/alarm.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/callback.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/const.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/messages.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/notifications.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/cloud/protocol.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/const.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/dataclass/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/dataclass/load_save.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/dataclass/validation.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/base.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/devices.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/definitions/sensors.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/base_entity.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/device.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/device_list.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/sensor.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/entities/sensor_list.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/event_mapping.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/alarm_phones.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/alert_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/base_cmd.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/discovery.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/history.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_info.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/host_status.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/net_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/notifications.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/paginated_cmd.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/paginated_result.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/targeted_discovery.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/local/user_data_crc.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/notifications/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/notifications/base.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/notifications/protocol.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm/py.typed +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/dependency_links.txt +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/requires.txt +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/src/pyg90alarm.egg-info/top_level.txt +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/__init__.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/conftest.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/device_mock.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_alarm.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_alarm_phones.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_base_commands.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_cloud_notifications.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_devices.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_discovery.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_history.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_host_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_local_notifications.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_net_config.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_paginated_commands.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/test_sensor.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_dataclass_load_save.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_dataclass_load_save_descriptor.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_dataclass_load_save_serialize.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_validation.py +0 -0
- {pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tox.ini +0 -0
|
@@ -53,6 +53,7 @@ from .local.alarm_phones import G90AlarmPhones
|
|
|
53
53
|
from .local.net_config import G90NetConfig, G90APNAuth
|
|
54
54
|
from .local.history import G90History
|
|
55
55
|
|
|
56
|
+
from .dataclass.load_save import DataclassLoadSave
|
|
56
57
|
from .dataclass.validation import (
|
|
57
58
|
get_field_validation_constraints,
|
|
58
59
|
)
|
|
@@ -101,4 +102,6 @@ __all__ = [
|
|
|
101
102
|
'G90History',
|
|
102
103
|
# Dataclass validation
|
|
103
104
|
'get_field_validation_constraints',
|
|
105
|
+
# Dataclass load/save
|
|
106
|
+
'DataclassLoadSave',
|
|
104
107
|
]
|
|
@@ -68,6 +68,7 @@ class G90BaseList(Generic[T], ABC):
|
|
|
68
68
|
|
|
69
69
|
:return: Async generator of entities
|
|
70
70
|
"""
|
|
71
|
+
# Placeholder to satisfy the abstractmethod
|
|
71
72
|
yield cast(T, None) # pragma: no cover
|
|
72
73
|
|
|
73
74
|
@property
|
|
@@ -118,7 +119,11 @@ class G90BaseList(Generic[T], ABC):
|
|
|
118
119
|
)
|
|
119
120
|
|
|
120
121
|
existing_entity.update(entity)
|
|
121
|
-
|
|
122
|
+
# The entity might have already been removed if there
|
|
123
|
+
# are duplicate entities from the `_fetch` method,
|
|
124
|
+
# protect against that
|
|
125
|
+
if existing_entity in non_existing_entities:
|
|
126
|
+
non_existing_entities.remove(existing_entity)
|
|
122
127
|
|
|
123
128
|
# Invoke the list change callback for the existing
|
|
124
129
|
# entity to notify about the update
|
|
@@ -32,7 +32,7 @@ class G90Error(Exception):
|
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
class G90TimeoutError(asyncio.TimeoutError):
|
|
35
|
+
class G90TimeoutError(asyncio.TimeoutError, G90Error):
|
|
36
36
|
"""
|
|
37
37
|
Raised when particular package class to report an operation (typically
|
|
38
38
|
device command) has timed out.
|
|
@@ -86,6 +86,7 @@ tests/test_local_notifications.py
|
|
|
86
86
|
tests/test_net_config.py
|
|
87
87
|
tests/test_paginated_commands.py
|
|
88
88
|
tests/test_sensor.py
|
|
89
|
+
tests/unit/test_exceptions.py
|
|
89
90
|
tests/unit/dataclass/test_dataclass_load_save.py
|
|
90
91
|
tests/unit/dataclass/test_dataclass_load_save_descriptor.py
|
|
91
92
|
tests/unit/dataclass/test_dataclass_load_save_serialize.py
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for G90BaseList class
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
from typing import AsyncGenerator
|
|
6
|
+
from unittest.mock import MagicMock
|
|
7
|
+
|
|
8
|
+
from pyg90alarm.entities.base_list import G90BaseList
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def test_find_free_idx_empty_list() -> None:
|
|
12
|
+
"""
|
|
13
|
+
Tests find_free_idx with empty entities list.
|
|
14
|
+
"""
|
|
15
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
16
|
+
"""
|
|
17
|
+
Mock subclass for testing G90BaseList.
|
|
18
|
+
"""
|
|
19
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
20
|
+
"""
|
|
21
|
+
Mock no entities.
|
|
22
|
+
"""
|
|
23
|
+
x: MagicMock
|
|
24
|
+
for x in []:
|
|
25
|
+
yield x
|
|
26
|
+
|
|
27
|
+
base_list = TestClass(parent=MagicMock())
|
|
28
|
+
result = await base_list.find_free_idx()
|
|
29
|
+
|
|
30
|
+
assert result == 0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def test_find_free_idx_one_entity_at_zero() -> None:
|
|
34
|
+
"""
|
|
35
|
+
Tests find_free_idx with one entity at index 0.
|
|
36
|
+
"""
|
|
37
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
38
|
+
"""
|
|
39
|
+
Mock subclass for testing G90BaseList.
|
|
40
|
+
"""
|
|
41
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
42
|
+
"""
|
|
43
|
+
Mock one entity at index 0.
|
|
44
|
+
"""
|
|
45
|
+
for x in [MagicMock(index=0)]:
|
|
46
|
+
yield x
|
|
47
|
+
|
|
48
|
+
base_list = TestClass(parent=MagicMock())
|
|
49
|
+
result = await base_list.find_free_idx()
|
|
50
|
+
|
|
51
|
+
assert result == 1
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def test_find_free_idx_returns_lowest_available_index() -> None:
|
|
55
|
+
"""
|
|
56
|
+
Tests find_free_idx with two entities at indexes 10 and 11.
|
|
57
|
+
"""
|
|
58
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
59
|
+
"""
|
|
60
|
+
Mock subclass for testing G90BaseList.
|
|
61
|
+
"""
|
|
62
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
63
|
+
"""
|
|
64
|
+
Mock entities at indexes 10 and 11.
|
|
65
|
+
"""
|
|
66
|
+
for x in [MagicMock(index=10), MagicMock(index=11)]:
|
|
67
|
+
yield x
|
|
68
|
+
|
|
69
|
+
base_list = TestClass(parent=MagicMock())
|
|
70
|
+
result = await base_list.find_free_idx()
|
|
71
|
+
|
|
72
|
+
assert result == 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def test_find_free_idx_returns_lowest_available_index_over_gap() -> None:
|
|
76
|
+
"""
|
|
77
|
+
Tests find_free_idx with two entities at indexes 10 and 11.
|
|
78
|
+
"""
|
|
79
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
80
|
+
"""
|
|
81
|
+
Mock subclass for testing G90BaseList.
|
|
82
|
+
"""
|
|
83
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
84
|
+
"""
|
|
85
|
+
Mock entities at indexes 0, 10 and 11.
|
|
86
|
+
"""
|
|
87
|
+
for x in [
|
|
88
|
+
MagicMock(index=0), MagicMock(index=10), MagicMock(index=11)
|
|
89
|
+
]:
|
|
90
|
+
yield x
|
|
91
|
+
|
|
92
|
+
base_list = TestClass(parent=MagicMock())
|
|
93
|
+
result = await base_list.find_free_idx()
|
|
94
|
+
|
|
95
|
+
assert result == 1
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def test_find_entity_by_idx_and_name() -> None:
|
|
99
|
+
"""
|
|
100
|
+
Tests find with matching index, subindex, and name.
|
|
101
|
+
"""
|
|
102
|
+
entity = MagicMock()
|
|
103
|
+
entity.index = 0
|
|
104
|
+
entity.subindex = 0
|
|
105
|
+
entity.name = "Test Entity"
|
|
106
|
+
entity.is_unavailable = False
|
|
107
|
+
|
|
108
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
109
|
+
"""
|
|
110
|
+
Mock subclass for testing G90BaseList.
|
|
111
|
+
"""
|
|
112
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
113
|
+
"""
|
|
114
|
+
Mock one entity.
|
|
115
|
+
"""
|
|
116
|
+
yield entity
|
|
117
|
+
|
|
118
|
+
base_list = TestClass(parent=MagicMock())
|
|
119
|
+
result = await base_list.find(
|
|
120
|
+
idx=0, name="Test Entity", exclude_unavailable=False
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert result == entity
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def test_find_entity_name_mismatch() -> None:
|
|
127
|
+
"""
|
|
128
|
+
Tests find with matching index but mismatched name.
|
|
129
|
+
"""
|
|
130
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
131
|
+
"""
|
|
132
|
+
Mock subclass for testing G90BaseList.
|
|
133
|
+
"""
|
|
134
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
135
|
+
"""
|
|
136
|
+
Mock one entity.
|
|
137
|
+
"""
|
|
138
|
+
entity = MagicMock()
|
|
139
|
+
entity.index = 0
|
|
140
|
+
entity.subindex = 0
|
|
141
|
+
entity.name = "Test Entity"
|
|
142
|
+
entity.is_unavailable = False
|
|
143
|
+
yield entity
|
|
144
|
+
|
|
145
|
+
base_list = TestClass(parent=MagicMock())
|
|
146
|
+
result = await base_list.find(
|
|
147
|
+
idx=0, name="Wrong Name", exclude_unavailable=False
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
assert result is None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def test_find_entity_not_found() -> None:
|
|
154
|
+
"""
|
|
155
|
+
Tests find with non-existing index.
|
|
156
|
+
"""
|
|
157
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
158
|
+
"""
|
|
159
|
+
Mock subclass for testing G90BaseList.
|
|
160
|
+
"""
|
|
161
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
162
|
+
"""
|
|
163
|
+
Mock no entities.
|
|
164
|
+
"""
|
|
165
|
+
x: MagicMock
|
|
166
|
+
for x in []:
|
|
167
|
+
yield x
|
|
168
|
+
|
|
169
|
+
base_list = TestClass(parent=MagicMock())
|
|
170
|
+
result = await base_list.find(
|
|
171
|
+
idx=0, name="Test Entity", exclude_unavailable=False
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert result is None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def test_find_entity_unavailable_excluded() -> None:
|
|
178
|
+
"""
|
|
179
|
+
Tests find with unavailable entity and exclude_unavailable=True.
|
|
180
|
+
"""
|
|
181
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
182
|
+
"""
|
|
183
|
+
Mock subclass for testing G90BaseList.
|
|
184
|
+
"""
|
|
185
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
186
|
+
"""
|
|
187
|
+
Mock one entity.
|
|
188
|
+
"""
|
|
189
|
+
entity = MagicMock()
|
|
190
|
+
entity.index = 0
|
|
191
|
+
entity.subindex = 0
|
|
192
|
+
entity.name = "Test Entity"
|
|
193
|
+
entity.is_unavailable = True
|
|
194
|
+
yield entity
|
|
195
|
+
|
|
196
|
+
base_list = TestClass(parent=MagicMock())
|
|
197
|
+
result = await base_list.find(
|
|
198
|
+
idx=0, name="Test Entity", exclude_unavailable=True
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
assert result is None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def test_find_entity_unavailable_not_excluded() -> None:
|
|
205
|
+
"""
|
|
206
|
+
Tests find with unavailable entity and exclude_unavailable=False.
|
|
207
|
+
"""
|
|
208
|
+
entity = MagicMock()
|
|
209
|
+
entity.index = 0
|
|
210
|
+
entity.subindex = 0
|
|
211
|
+
entity.name = "Test Entity"
|
|
212
|
+
entity.is_unavailable = True
|
|
213
|
+
|
|
214
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
215
|
+
"""
|
|
216
|
+
Mock subclass for testing G90BaseList.
|
|
217
|
+
"""
|
|
218
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
219
|
+
"""
|
|
220
|
+
Mock one entity.
|
|
221
|
+
"""
|
|
222
|
+
yield entity
|
|
223
|
+
|
|
224
|
+
base_list = TestClass(parent=MagicMock())
|
|
225
|
+
result = await base_list.find(
|
|
226
|
+
idx=0, name="Test Entity", exclude_unavailable=False
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
assert result == entity
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
async def test_find_entity_with_subindex() -> None:
|
|
233
|
+
"""
|
|
234
|
+
Tests find with specific subindex.
|
|
235
|
+
"""
|
|
236
|
+
entity = MagicMock()
|
|
237
|
+
entity.index = 0
|
|
238
|
+
entity.subindex = 1
|
|
239
|
+
entity.name = "Test Entity"
|
|
240
|
+
entity.is_unavailable = False
|
|
241
|
+
|
|
242
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
243
|
+
"""
|
|
244
|
+
Mock subclass for testing G90BaseList.
|
|
245
|
+
"""
|
|
246
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
247
|
+
"""
|
|
248
|
+
Mock one entity.
|
|
249
|
+
"""
|
|
250
|
+
yield entity
|
|
251
|
+
|
|
252
|
+
base_list = TestClass(parent=MagicMock())
|
|
253
|
+
result = await base_list.find(
|
|
254
|
+
idx=0, name="Test Entity", exclude_unavailable=False, subindex=1
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
assert result == entity
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
async def test_duplicate_entities() -> None:
|
|
261
|
+
"""
|
|
262
|
+
Tests that duplicate entities are handled correctly during update.
|
|
263
|
+
"""
|
|
264
|
+
existing_entity = MagicMock()
|
|
265
|
+
existing_entity.index = 1
|
|
266
|
+
existing_entity.subindex = 0
|
|
267
|
+
existing_entity.name = "Existing entity"
|
|
268
|
+
existing_entity.is_unavailable = False
|
|
269
|
+
|
|
270
|
+
new_entity = MagicMock()
|
|
271
|
+
new_entity.index = 1
|
|
272
|
+
new_entity.subindex = 0
|
|
273
|
+
new_entity.name = "Test entity"
|
|
274
|
+
new_entity.is_unavailable = False
|
|
275
|
+
|
|
276
|
+
mock_entities = iter([
|
|
277
|
+
# Initial fetch returns one existing entity
|
|
278
|
+
[existing_entity],
|
|
279
|
+
# Second fetch returns duplicate new entities
|
|
280
|
+
[new_entity, new_entity],
|
|
281
|
+
])
|
|
282
|
+
|
|
283
|
+
class TestClass(G90BaseList[MagicMock]):
|
|
284
|
+
"""
|
|
285
|
+
Mock subclass for testing G90BaseList.
|
|
286
|
+
"""
|
|
287
|
+
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
288
|
+
"""
|
|
289
|
+
Mock test list entities.
|
|
290
|
+
"""
|
|
291
|
+
# Yield entities under test
|
|
292
|
+
for x in next(mock_entities):
|
|
293
|
+
yield x
|
|
294
|
+
|
|
295
|
+
base_list = TestClass(parent=MagicMock())
|
|
296
|
+
# Initial update to simulate the existing entity
|
|
297
|
+
await base_list.update()
|
|
298
|
+
# Second update to test handling of duplicates, should not raise
|
|
299
|
+
result = await base_list.update()
|
|
300
|
+
|
|
301
|
+
assert len(result) == 2
|
|
302
|
+
# Initial entities not present in second update result should be marked
|
|
303
|
+
# unavailable
|
|
304
|
+
assert result[0] == existing_entity
|
|
305
|
+
assert result[0].is_unavailable is True
|
|
306
|
+
assert result[1] == new_entity
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for package-specific exceptions.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from pyg90alarm.exceptions import G90TimeoutError, G90Error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_g90_timeout_error_inheritance() -> None:
|
|
11
|
+
"""
|
|
12
|
+
Test that catching G90Error also catches G90TimeoutError.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
with pytest.raises(G90Error):
|
|
16
|
+
raise G90TimeoutError("Timeout occurred")
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tests for G90BaseList class
|
|
3
|
-
"""
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
from typing import AsyncGenerator
|
|
6
|
-
from unittest.mock import MagicMock
|
|
7
|
-
|
|
8
|
-
from pyg90alarm.entities.base_list import G90BaseList
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestBaseList(G90BaseList[MagicMock]):
|
|
12
|
-
"""
|
|
13
|
-
Mock subclass for testing G90BaseList.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
# Prevent pytest from collecting this class as a test case
|
|
17
|
-
__test__ = False
|
|
18
|
-
|
|
19
|
-
async def _fetch(self) -> AsyncGenerator[MagicMock, None]:
|
|
20
|
-
"""Mock _fetch method."""
|
|
21
|
-
if False: # pragma: no cover
|
|
22
|
-
yield MagicMock()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
async def test_find_free_idx_empty_list() -> None:
|
|
26
|
-
"""
|
|
27
|
-
Tests find_free_idx with empty entities list.
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
parent = MagicMock()
|
|
31
|
-
base_list = TestBaseList(parent)
|
|
32
|
-
base_list._entities = []
|
|
33
|
-
|
|
34
|
-
result = await base_list.find_free_idx()
|
|
35
|
-
|
|
36
|
-
assert result == 0
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async def test_find_free_idx_one_entity_at_zero() -> None:
|
|
40
|
-
"""
|
|
41
|
-
Tests find_free_idx with one entity at index 0.
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
45
|
-
base_list._entities = [MagicMock(index=0)]
|
|
46
|
-
|
|
47
|
-
result = await base_list.find_free_idx()
|
|
48
|
-
|
|
49
|
-
assert result == 1
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
async def test_find_free_idx_returns_lowest_available_index() -> None:
|
|
53
|
-
"""
|
|
54
|
-
Tests find_free_idx with two entities at indexes 10 and 11.
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
58
|
-
base_list._entities = [MagicMock(index=10), MagicMock(index=11)]
|
|
59
|
-
|
|
60
|
-
result = await base_list.find_free_idx()
|
|
61
|
-
|
|
62
|
-
assert result == 0
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
async def test_find_free_idx_returns_lowest_available_index_over_gap() -> None:
|
|
66
|
-
"""
|
|
67
|
-
Tests find_free_idx with two entities at indexes 10 and 11.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
71
|
-
base_list._entities = [
|
|
72
|
-
MagicMock(index=0), MagicMock(index=10), MagicMock(index=11)
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
result = await base_list.find_free_idx()
|
|
76
|
-
|
|
77
|
-
assert result == 1
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
async def test_find_entity_by_idx_and_name() -> None:
|
|
81
|
-
"""
|
|
82
|
-
Tests find with matching index, subindex, and name.
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
86
|
-
entity = MagicMock()
|
|
87
|
-
entity.index = 0
|
|
88
|
-
entity.subindex = 0
|
|
89
|
-
entity.name = "Test Entity"
|
|
90
|
-
entity.is_unavailable = False
|
|
91
|
-
base_list._entities = [entity]
|
|
92
|
-
|
|
93
|
-
result = await base_list.find(
|
|
94
|
-
idx=0, name="Test Entity", exclude_unavailable=False
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
assert result == entity
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
async def test_find_entity_name_mismatch() -> None:
|
|
101
|
-
"""
|
|
102
|
-
Tests find with matching index but mismatched name.
|
|
103
|
-
"""
|
|
104
|
-
|
|
105
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
106
|
-
entity = MagicMock()
|
|
107
|
-
entity.index = 0
|
|
108
|
-
entity.subindex = 0
|
|
109
|
-
entity.name = "Test Entity"
|
|
110
|
-
entity.is_unavailable = False
|
|
111
|
-
base_list._entities = [entity]
|
|
112
|
-
|
|
113
|
-
result = await base_list.find(
|
|
114
|
-
idx=0, name="Wrong Name", exclude_unavailable=False
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
assert result is None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
async def test_find_entity_not_found() -> None:
|
|
121
|
-
"""
|
|
122
|
-
Tests find with non-existing index.
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
126
|
-
base_list._entities = []
|
|
127
|
-
|
|
128
|
-
result = await base_list.find(
|
|
129
|
-
idx=0, name="Test Entity", exclude_unavailable=False
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
assert result is None
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async def test_find_entity_unavailable_excluded() -> None:
|
|
136
|
-
"""
|
|
137
|
-
Tests find with unavailable entity and exclude_unavailable=True.
|
|
138
|
-
"""
|
|
139
|
-
|
|
140
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
141
|
-
entity = MagicMock()
|
|
142
|
-
entity.index = 0
|
|
143
|
-
entity.subindex = 0
|
|
144
|
-
entity.name = "Test Entity"
|
|
145
|
-
entity.is_unavailable = True
|
|
146
|
-
base_list._entities = [entity]
|
|
147
|
-
|
|
148
|
-
result = await base_list.find(
|
|
149
|
-
idx=0, name="Test Entity", exclude_unavailable=True
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
assert result is None
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
async def test_find_entity_unavailable_not_excluded() -> None:
|
|
156
|
-
"""
|
|
157
|
-
Tests find with unavailable entity and exclude_unavailable=False.
|
|
158
|
-
"""
|
|
159
|
-
|
|
160
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
161
|
-
entity = MagicMock()
|
|
162
|
-
entity.index = 0
|
|
163
|
-
entity.subindex = 0
|
|
164
|
-
entity.name = "Test Entity"
|
|
165
|
-
entity.is_unavailable = True
|
|
166
|
-
base_list._entities = [entity]
|
|
167
|
-
|
|
168
|
-
result = await base_list.find(
|
|
169
|
-
idx=0, name="Test Entity", exclude_unavailable=False
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
assert result == entity
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
async def test_find_entity_with_subindex() -> None:
|
|
176
|
-
"""
|
|
177
|
-
Tests find with specific subindex.
|
|
178
|
-
"""
|
|
179
|
-
|
|
180
|
-
base_list = TestBaseList(parent=MagicMock())
|
|
181
|
-
entity = MagicMock()
|
|
182
|
-
entity.index = 0
|
|
183
|
-
entity.subindex = 1
|
|
184
|
-
entity.name = "Test Entity"
|
|
185
|
-
entity.is_unavailable = False
|
|
186
|
-
base_list._entities = [entity]
|
|
187
|
-
|
|
188
|
-
result = await base_list.find(
|
|
189
|
-
idx=0, name="Test Entity", exclude_unavailable=False, subindex=1
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
assert result == entity
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_dataclass_load_save_descriptor.py
RENAMED
|
File without changes
|
{pyg90alarm-2.5.0 → pyg90alarm-2.5.1}/tests/unit/dataclass/test_dataclass_load_save_serialize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|