wyzeapy 0.5.30__py3-none-any.whl → 0.5.31rc1__py3-none-any.whl
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.
- wyzeapy/__init__.py +1 -1
- wyzeapy/payload_factory.py +23 -19
- wyzeapy/services/base_service.py +73 -35
- wyzeapy/services/camera_service.py +11 -3
- wyzeapy/services/irrigation_service.py +104 -47
- wyzeapy/tests/test_irrigation_service.py +373 -177
- {wyzeapy-0.5.30.dist-info → wyzeapy-0.5.31rc1.dist-info}/METADATA +3 -3
- {wyzeapy-0.5.30.dist-info → wyzeapy-0.5.31rc1.dist-info}/RECORD +9 -9
- {wyzeapy-0.5.30.dist-info → wyzeapy-0.5.31rc1.dist-info}/WHEEL +1 -1
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from unittest.mock import AsyncMock, MagicMock
|
|
3
|
-
from wyzeapy.services.irrigation_service import
|
|
4
|
-
IrrigationService, Irrigation, Zone
|
|
5
|
-
)
|
|
3
|
+
from wyzeapy.services.irrigation_service import IrrigationService, Irrigation, Zone
|
|
6
4
|
from wyzeapy.types import DeviceTypes, Device
|
|
7
5
|
from wyzeapy.wyze_auth_lib import WyzeAuthLib
|
|
8
6
|
|
|
9
7
|
# todo: add tests for irrigation service
|
|
10
8
|
|
|
9
|
+
|
|
11
10
|
class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
12
11
|
async def asyncSetUp(self):
|
|
13
12
|
self.mock_auth_lib = MagicMock(spec=WyzeAuthLib)
|
|
@@ -20,47 +19,49 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
20
19
|
self.irrigation_service._irrigation_device_info = AsyncMock()
|
|
21
20
|
|
|
22
21
|
# Create test irrigation
|
|
23
|
-
self.test_irrigation = Irrigation(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
self.test_irrigation = Irrigation(
|
|
23
|
+
{
|
|
24
|
+
"device_type": DeviceTypes.IRRIGATION.value,
|
|
25
|
+
"product_model": "BS_WK1",
|
|
26
|
+
"mac": "IRRIG123",
|
|
27
|
+
"nickname": "Test Irrigation",
|
|
28
|
+
"device_params": {"ip": "192.168.1.100"},
|
|
29
|
+
"raw_dict": {},
|
|
30
|
+
}
|
|
31
|
+
)
|
|
31
32
|
|
|
32
33
|
async def test_update_irrigation(self):
|
|
33
34
|
# Mock IoT properties response
|
|
34
35
|
self.irrigation_service.get_iot_prop.return_value = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
"data": {
|
|
37
|
+
"props": {
|
|
38
|
+
"RSSI": "-65",
|
|
39
|
+
"IP": "192.168.1.100",
|
|
40
|
+
"sn": "SN123456789",
|
|
41
|
+
"ssid": "TestSSID",
|
|
42
|
+
"iot_state": "connected",
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
# Mock zones response
|
|
47
48
|
self.irrigation_service.get_zone_by_device.return_value = {
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
"data": {
|
|
50
|
+
"zones": [
|
|
50
51
|
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
"zone_number": 1,
|
|
53
|
+
"name": "Zone 1",
|
|
54
|
+
"enabled": True,
|
|
55
|
+
"zone_id": "zone1",
|
|
56
|
+
"smart_duration": 600,
|
|
56
57
|
},
|
|
57
58
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
59
|
+
"zone_number": 2,
|
|
60
|
+
"name": "Zone 2",
|
|
61
|
+
"enabled": True,
|
|
62
|
+
"zone_id": "zone2",
|
|
63
|
+
"smart_duration": 900,
|
|
64
|
+
},
|
|
64
65
|
]
|
|
65
66
|
}
|
|
66
67
|
}
|
|
@@ -68,20 +69,20 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
68
69
|
updated_irrigation = await self.irrigation_service.update(self.test_irrigation)
|
|
69
70
|
|
|
70
71
|
# Test IoT properties
|
|
71
|
-
self.assertEqual(updated_irrigation.RSSI,
|
|
72
|
-
self.assertEqual(updated_irrigation.IP,
|
|
73
|
-
self.assertEqual(updated_irrigation.sn,
|
|
74
|
-
self.assertEqual(updated_irrigation.ssid,
|
|
72
|
+
self.assertEqual(updated_irrigation.RSSI, "-65")
|
|
73
|
+
self.assertEqual(updated_irrigation.IP, "192.168.1.100")
|
|
74
|
+
self.assertEqual(updated_irrigation.sn, "SN123456789")
|
|
75
|
+
self.assertEqual(updated_irrigation.ssid, "TestSSID")
|
|
75
76
|
self.assertTrue(updated_irrigation.available)
|
|
76
77
|
|
|
77
78
|
# Test zones
|
|
78
79
|
self.assertEqual(len(updated_irrigation.zones), 2)
|
|
79
80
|
self.assertEqual(updated_irrigation.zones[0].zone_number, 1)
|
|
80
|
-
self.assertEqual(updated_irrigation.zones[0].name,
|
|
81
|
+
self.assertEqual(updated_irrigation.zones[0].name, "Zone 1")
|
|
81
82
|
self.assertTrue(updated_irrigation.zones[0].enabled)
|
|
82
83
|
self.assertEqual(updated_irrigation.zones[0].quickrun_duration, 600)
|
|
83
84
|
self.assertEqual(updated_irrigation.zones[1].zone_number, 2)
|
|
84
|
-
self.assertEqual(updated_irrigation.zones[1].name,
|
|
85
|
+
self.assertEqual(updated_irrigation.zones[1].name, "Zone 2")
|
|
85
86
|
self.assertTrue(updated_irrigation.zones[1].enabled)
|
|
86
87
|
self.assertEqual(updated_irrigation.zones[1].quickrun_duration, 900)
|
|
87
88
|
|
|
@@ -96,7 +97,7 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
96
97
|
"mac": "IRRIG123",
|
|
97
98
|
"nickname": "Test Irrigation",
|
|
98
99
|
"device_params": {"ip": "192.168.1.100"},
|
|
99
|
-
"raw_dict": {}
|
|
100
|
+
"raw_dict": {},
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
# Mock the get_object_list to return our mock irrigation device
|
|
@@ -104,7 +105,7 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
104
105
|
|
|
105
106
|
# Get the irrigations
|
|
106
107
|
irrigations = await self.irrigation_service.get_irrigations()
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
# Verify the results
|
|
109
110
|
self.assertEqual(len(irrigations), 1)
|
|
110
111
|
self.assertIsInstance(irrigations[0], Irrigation)
|
|
@@ -115,59 +116,56 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
115
116
|
async def test_set_zone_quickrun_duration(self):
|
|
116
117
|
# Setup test irrigation with zones
|
|
117
118
|
self.test_irrigation.zones = [
|
|
118
|
-
Zone(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
Zone(
|
|
120
|
+
{
|
|
121
|
+
"zone_number": 1,
|
|
122
|
+
"name": "Zone 1",
|
|
123
|
+
"enabled": True,
|
|
124
|
+
"zone_id": "zone1",
|
|
125
|
+
"smart_duration": 400,
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
Zone(
|
|
129
|
+
{
|
|
130
|
+
"zone_number": 2,
|
|
131
|
+
"name": "Zone 2",
|
|
132
|
+
"enabled": True,
|
|
133
|
+
"zone_id": "zone2",
|
|
134
|
+
"smart_duration": 900,
|
|
135
|
+
}
|
|
136
|
+
),
|
|
132
137
|
]
|
|
133
138
|
|
|
134
139
|
# Test setting quickrun duration
|
|
135
140
|
await self.irrigation_service.set_zone_quickrun_duration(
|
|
136
|
-
self.test_irrigation,
|
|
137
|
-
1,
|
|
138
|
-
300
|
|
141
|
+
self.test_irrigation, 1, 300
|
|
139
142
|
)
|
|
140
143
|
self.assertEqual(self.test_irrigation.zones[0].quickrun_duration, 300)
|
|
141
144
|
|
|
142
145
|
# Test setting quickrun duration for non-existent zone
|
|
143
146
|
await self.irrigation_service.set_zone_quickrun_duration(
|
|
144
|
-
self.test_irrigation,
|
|
145
|
-
999,
|
|
146
|
-
300
|
|
147
|
+
self.test_irrigation, 999, 300
|
|
147
148
|
)
|
|
148
149
|
# Verify that no zones were modified
|
|
149
150
|
self.assertEqual(len(self.test_irrigation.zones), 2)
|
|
150
|
-
self.assertEqual(
|
|
151
|
-
|
|
151
|
+
self.assertEqual(
|
|
152
|
+
self.test_irrigation.zones[0].quickrun_duration, 300
|
|
153
|
+
) # First zone changed to 300
|
|
154
|
+
self.assertEqual(
|
|
155
|
+
self.test_irrigation.zones[1].quickrun_duration, 900
|
|
156
|
+
) # Second zone should be unchanged at 900
|
|
152
157
|
|
|
153
158
|
async def test_update_with_invalid_property(self):
|
|
154
159
|
self.irrigation_service.get_iot_prop.return_value = {
|
|
155
|
-
|
|
156
|
-
'props': {
|
|
157
|
-
'invalid_property': 'some_value',
|
|
158
|
-
'RSSI': '-65'
|
|
159
|
-
}
|
|
160
|
-
}
|
|
160
|
+
"data": {"props": {"invalid_property": "some_value", "RSSI": "-65"}}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
self.irrigation_service.get_zone_by_device.return_value = {
|
|
164
|
-
|
|
165
|
-
'zones': []
|
|
166
|
-
}
|
|
164
|
+
"data": {"zones": []}
|
|
167
165
|
}
|
|
168
166
|
|
|
169
167
|
updated_irrigation = await self.irrigation_service.update(self.test_irrigation)
|
|
170
|
-
self.assertEqual(updated_irrigation.RSSI,
|
|
168
|
+
self.assertEqual(updated_irrigation.RSSI, "-65")
|
|
171
169
|
# Other properties should maintain their default values
|
|
172
170
|
self.assertEqual(updated_irrigation.IP, "192.168.1.100")
|
|
173
171
|
self.assertEqual(updated_irrigation.sn, "SN123456789")
|
|
@@ -176,14 +174,12 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
176
174
|
async def test_start_zone(self):
|
|
177
175
|
# Mock the _start_zone method
|
|
178
176
|
self.irrigation_service._start_zone = AsyncMock()
|
|
179
|
-
expected_response = {
|
|
177
|
+
expected_response = {"data": {"result": "success"}}
|
|
180
178
|
self.irrigation_service._start_zone.return_value = expected_response
|
|
181
179
|
|
|
182
180
|
# Test starting a zone
|
|
183
181
|
result = await self.irrigation_service.start_zone(
|
|
184
|
-
self.test_irrigation,
|
|
185
|
-
zone_number=1,
|
|
186
|
-
quickrun_duration=300
|
|
182
|
+
self.test_irrigation, zone_number=1, quickrun_duration=300
|
|
187
183
|
)
|
|
188
184
|
|
|
189
185
|
# Verify the call was made with correct parameters
|
|
@@ -191,153 +187,165 @@ class TestIrrigationService(unittest.IsolatedAsyncioTestCase):
|
|
|
191
187
|
"https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/quickrun",
|
|
192
188
|
self.test_irrigation,
|
|
193
189
|
1,
|
|
194
|
-
300
|
|
190
|
+
300,
|
|
195
191
|
)
|
|
196
192
|
self.assertEqual(result, expected_response)
|
|
197
193
|
|
|
198
194
|
async def test_stop_running_schedule(self):
|
|
199
195
|
# Mock the _stop_running_schedule method
|
|
200
196
|
self.irrigation_service._stop_running_schedule = AsyncMock()
|
|
201
|
-
expected_response = {
|
|
197
|
+
expected_response = {"data": {"result": "stopped"}}
|
|
202
198
|
self.irrigation_service._stop_running_schedule.return_value = expected_response
|
|
203
199
|
|
|
204
200
|
# Test stopping running schedule
|
|
205
|
-
result = await self.irrigation_service.stop_running_schedule(
|
|
201
|
+
result = await self.irrigation_service.stop_running_schedule(
|
|
202
|
+
self.test_irrigation
|
|
203
|
+
)
|
|
206
204
|
|
|
207
205
|
# Verify the call was made with correct parameters
|
|
208
206
|
self.irrigation_service._stop_running_schedule.assert_awaited_once_with(
|
|
209
207
|
"https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/runningschedule",
|
|
210
208
|
self.test_irrigation,
|
|
211
|
-
"STOP"
|
|
209
|
+
"STOP",
|
|
212
210
|
)
|
|
213
211
|
self.assertEqual(result, expected_response)
|
|
214
212
|
|
|
215
213
|
async def test_update_device_props(self):
|
|
216
214
|
# Mock IoT properties response
|
|
217
215
|
self.irrigation_service.get_iot_prop.return_value = {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
216
|
+
"data": {
|
|
217
|
+
"props": {
|
|
218
|
+
"RSSI": "-70",
|
|
219
|
+
"IP": "192.168.1.101",
|
|
220
|
+
"sn": "SN987654321",
|
|
221
|
+
"ssid": "NewSSID",
|
|
222
|
+
"iot_state": "connected",
|
|
225
223
|
}
|
|
226
224
|
}
|
|
227
225
|
}
|
|
228
226
|
|
|
229
|
-
updated_irrigation = await self.irrigation_service.update_device_props(
|
|
227
|
+
updated_irrigation = await self.irrigation_service.update_device_props(
|
|
228
|
+
self.test_irrigation
|
|
229
|
+
)
|
|
230
230
|
|
|
231
231
|
# Test that properties were updated correctly
|
|
232
|
-
self.assertEqual(updated_irrigation.RSSI,
|
|
233
|
-
self.assertEqual(updated_irrigation.IP,
|
|
234
|
-
self.assertEqual(updated_irrigation.sn,
|
|
235
|
-
self.assertEqual(updated_irrigation.ssid,
|
|
232
|
+
self.assertEqual(updated_irrigation.RSSI, "-70")
|
|
233
|
+
self.assertEqual(updated_irrigation.IP, "192.168.1.101")
|
|
234
|
+
self.assertEqual(updated_irrigation.sn, "SN987654321")
|
|
235
|
+
self.assertEqual(updated_irrigation.ssid, "NewSSID")
|
|
236
236
|
self.assertTrue(updated_irrigation.available)
|
|
237
237
|
|
|
238
238
|
async def test_update_device_props_disconnected(self):
|
|
239
239
|
# Mock IoT properties response with disconnected state
|
|
240
240
|
self.irrigation_service.get_iot_prop.return_value = {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
"data": {
|
|
242
|
+
"props": {
|
|
243
|
+
"RSSI": "-80",
|
|
244
|
+
"IP": "192.168.1.102",
|
|
245
|
+
"sn": "SN555666777",
|
|
246
|
+
"ssid": "TestSSID2",
|
|
247
|
+
"iot_state": "disconnected",
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
updated_irrigation = await self.irrigation_service.update_device_props(
|
|
252
|
+
updated_irrigation = await self.irrigation_service.update_device_props(
|
|
253
|
+
self.test_irrigation
|
|
254
|
+
)
|
|
253
255
|
|
|
254
256
|
# Test that device is marked as unavailable
|
|
255
257
|
self.assertFalse(updated_irrigation.available)
|
|
256
|
-
self.assertEqual(updated_irrigation.RSSI,
|
|
257
|
-
self.assertEqual(updated_irrigation.IP,
|
|
258
|
+
self.assertEqual(updated_irrigation.RSSI, "-80")
|
|
259
|
+
self.assertEqual(updated_irrigation.IP, "192.168.1.102")
|
|
258
260
|
|
|
259
261
|
async def test_get_iot_prop(self):
|
|
260
262
|
# Mock the get_iot_prop method directly to test the public interface
|
|
261
|
-
expected_response = {
|
|
263
|
+
expected_response = {"data": {"props": {"RSSI": "-65"}}}
|
|
262
264
|
self.irrigation_service.get_iot_prop.return_value = expected_response
|
|
263
265
|
|
|
264
266
|
# Test get_iot_prop
|
|
265
267
|
result = await self.irrigation_service.get_iot_prop(self.test_irrigation)
|
|
266
268
|
|
|
267
269
|
# Verify the call was made and returned expected result
|
|
268
|
-
self.irrigation_service.get_iot_prop.assert_awaited_once_with(
|
|
270
|
+
self.irrigation_service.get_iot_prop.assert_awaited_once_with(
|
|
271
|
+
self.test_irrigation
|
|
272
|
+
)
|
|
269
273
|
self.assertEqual(result, expected_response)
|
|
270
274
|
|
|
271
275
|
async def test_get_device_info(self):
|
|
272
276
|
# Mock the _irrigation_device_info method
|
|
273
277
|
self.irrigation_service._irrigation_device_info = AsyncMock()
|
|
274
|
-
expected_response = {
|
|
278
|
+
expected_response = {"data": {"props": {"enable_schedules": True}}}
|
|
275
279
|
self.irrigation_service._irrigation_device_info.return_value = expected_response
|
|
276
280
|
|
|
277
281
|
# Test get_device_info
|
|
278
282
|
result = await self.irrigation_service.get_device_info(self.test_irrigation)
|
|
279
283
|
|
|
280
284
|
# Verify the call was made with correct parameters
|
|
281
|
-
expected_keys =
|
|
285
|
+
expected_keys = "wiring,sensor,enable_schedules,notification_enable,notification_watering_begins,notification_watering_ends,notification_watering_is_skipped,skip_low_temp,skip_wind,skip_rain,skip_saturation"
|
|
282
286
|
self.irrigation_service._irrigation_device_info.assert_awaited_once_with(
|
|
283
287
|
"https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/device_info",
|
|
284
288
|
self.test_irrigation,
|
|
285
|
-
expected_keys
|
|
289
|
+
expected_keys,
|
|
286
290
|
)
|
|
287
291
|
self.assertEqual(result, expected_response)
|
|
288
292
|
|
|
289
293
|
async def test_get_zone_by_device_method(self):
|
|
290
294
|
# Mock the get_zone_by_device method directly to test the public interface
|
|
291
|
-
expected_response = {
|
|
295
|
+
expected_response = {"data": {"zones": [{"zone_number": 1, "name": "Zone 1"}]}}
|
|
292
296
|
self.irrigation_service.get_zone_by_device.return_value = expected_response
|
|
293
297
|
|
|
294
298
|
# Test get_zone_by_device
|
|
295
299
|
result = await self.irrigation_service.get_zone_by_device(self.test_irrigation)
|
|
296
300
|
|
|
297
301
|
# Verify the call was made and returned expected result
|
|
298
|
-
self.irrigation_service.get_zone_by_device.assert_awaited_once_with(
|
|
302
|
+
self.irrigation_service.get_zone_by_device.assert_awaited_once_with(
|
|
303
|
+
self.test_irrigation
|
|
304
|
+
)
|
|
299
305
|
self.assertEqual(result, expected_response)
|
|
300
306
|
|
|
301
307
|
|
|
302
308
|
class TestZone(unittest.TestCase):
|
|
303
309
|
def test_zone_initialization_with_defaults(self):
|
|
304
310
|
# Test zone initialization with minimal data
|
|
305
|
-
zone_data = {
|
|
311
|
+
zone_data = {"zone_number": 3, "name": "Test Zone"}
|
|
306
312
|
zone = Zone(zone_data)
|
|
307
|
-
|
|
313
|
+
|
|
308
314
|
self.assertEqual(zone.zone_number, 3)
|
|
309
|
-
self.assertEqual(zone.name,
|
|
315
|
+
self.assertEqual(zone.name, "Test Zone")
|
|
310
316
|
self.assertTrue(zone.enabled) # Default value
|
|
311
|
-
self.assertEqual(zone.zone_id,
|
|
317
|
+
self.assertEqual(zone.zone_id, "zone_id") # Default value
|
|
312
318
|
self.assertEqual(zone.smart_duration, 600) # Default value
|
|
313
|
-
self.assertEqual(
|
|
319
|
+
self.assertEqual(
|
|
320
|
+
zone.quickrun_duration, 600
|
|
321
|
+
) # Default value from smart_duration
|
|
314
322
|
|
|
315
323
|
def test_zone_initialization_with_all_data(self):
|
|
316
324
|
# Test zone initialization with all data
|
|
317
325
|
zone_data = {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
326
|
+
"zone_number": 2,
|
|
327
|
+
"name": "Garden Zone",
|
|
328
|
+
"enabled": False,
|
|
329
|
+
"zone_id": "zone_garden",
|
|
330
|
+
"smart_duration": 1200,
|
|
323
331
|
}
|
|
324
332
|
zone = Zone(zone_data)
|
|
325
|
-
|
|
333
|
+
|
|
326
334
|
self.assertEqual(zone.zone_number, 2)
|
|
327
|
-
self.assertEqual(zone.name,
|
|
335
|
+
self.assertEqual(zone.name, "Garden Zone")
|
|
328
336
|
self.assertFalse(zone.enabled)
|
|
329
|
-
self.assertEqual(zone.zone_id,
|
|
337
|
+
self.assertEqual(zone.zone_id, "zone_garden")
|
|
330
338
|
self.assertEqual(zone.smart_duration, 1200)
|
|
331
339
|
self.assertEqual(zone.quickrun_duration, 1200) # Should use smart_duration
|
|
332
340
|
|
|
333
341
|
def test_zone_initialization_empty_dict(self):
|
|
334
342
|
# Test zone initialization with empty dict
|
|
335
343
|
zone = Zone({})
|
|
336
|
-
|
|
344
|
+
|
|
337
345
|
self.assertEqual(zone.zone_number, 1) # Default value
|
|
338
|
-
self.assertEqual(zone.name,
|
|
346
|
+
self.assertEqual(zone.name, "Zone 1") # Default value
|
|
339
347
|
self.assertTrue(zone.enabled) # Default value
|
|
340
|
-
self.assertEqual(zone.zone_id,
|
|
348
|
+
self.assertEqual(zone.zone_id, "zone_id") # Default value
|
|
341
349
|
self.assertEqual(zone.smart_duration, 600) # Default value
|
|
342
350
|
self.assertEqual(zone.quickrun_duration, 600) # Default value
|
|
343
351
|
|
|
@@ -350,14 +358,14 @@ class TestIrrigation(unittest.TestCase):
|
|
|
350
358
|
"product_model": "BS_WK1",
|
|
351
359
|
"mac": "IRRIG456",
|
|
352
360
|
"nickname": "Backyard Sprinkler",
|
|
353
|
-
"device_params": {"ip": "192.168.1.200"}
|
|
361
|
+
"device_params": {"ip": "192.168.1.200"},
|
|
354
362
|
}
|
|
355
363
|
irrigation = Irrigation(irrigation_data)
|
|
356
|
-
|
|
364
|
+
|
|
357
365
|
self.assertEqual(irrigation.product_model, "BS_WK1")
|
|
358
366
|
self.assertEqual(irrigation.mac, "IRRIG456")
|
|
359
367
|
self.assertEqual(irrigation.nickname, "Backyard Sprinkler")
|
|
360
|
-
|
|
368
|
+
|
|
361
369
|
# Test default values
|
|
362
370
|
self.assertEqual(irrigation.RSSI, 0)
|
|
363
371
|
self.assertEqual(irrigation.IP, "192.168.1.100")
|
|
@@ -372,10 +380,10 @@ class TestIrrigation(unittest.TestCase):
|
|
|
372
380
|
"product_type": DeviceTypes.IRRIGATION.value,
|
|
373
381
|
"product_model": "BS_WK1",
|
|
374
382
|
"mac": "IRRIG789",
|
|
375
|
-
"nickname": "Front Yard Sprinkler"
|
|
383
|
+
"nickname": "Front Yard Sprinkler",
|
|
376
384
|
}
|
|
377
385
|
irrigation = Irrigation(irrigation_data)
|
|
378
|
-
|
|
386
|
+
|
|
379
387
|
# Test inherited Device properties
|
|
380
388
|
self.assertIsInstance(irrigation, Device)
|
|
381
389
|
self.assertEqual(irrigation.type, DeviceTypes.IRRIGATION)
|
|
@@ -390,34 +398,34 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
390
398
|
self.irrigation_service.get_object_list = AsyncMock()
|
|
391
399
|
|
|
392
400
|
# Create test irrigation
|
|
393
|
-
self.test_irrigation = Irrigation(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
+
self.test_irrigation = Irrigation(
|
|
402
|
+
{
|
|
403
|
+
"device_type": DeviceTypes.IRRIGATION.value,
|
|
404
|
+
"product_model": "BS_WK1",
|
|
405
|
+
"mac": "IRRIG123",
|
|
406
|
+
"nickname": "Test Irrigation",
|
|
407
|
+
"device_params": {"ip": "192.168.1.100"},
|
|
408
|
+
"raw_dict": {},
|
|
409
|
+
}
|
|
410
|
+
)
|
|
401
411
|
|
|
402
412
|
async def test_update_with_empty_zones(self):
|
|
403
413
|
# Mock IoT properties response
|
|
404
414
|
self.irrigation_service.get_iot_prop.return_value = {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
415
|
+
"data": {
|
|
416
|
+
"props": {
|
|
417
|
+
"RSSI": "-65",
|
|
418
|
+
"IP": "192.168.1.100",
|
|
419
|
+
"sn": "SN123456789",
|
|
420
|
+
"ssid": "TestSSID",
|
|
421
|
+
"iot_state": "connected",
|
|
412
422
|
}
|
|
413
423
|
}
|
|
414
424
|
}
|
|
415
425
|
|
|
416
426
|
# Mock empty zones response
|
|
417
427
|
self.irrigation_service.get_zone_by_device.return_value = {
|
|
418
|
-
|
|
419
|
-
'zones': []
|
|
420
|
-
}
|
|
428
|
+
"data": {"zones": []}
|
|
421
429
|
}
|
|
422
430
|
|
|
423
431
|
updated_irrigation = await self.irrigation_service.update(self.test_irrigation)
|
|
@@ -428,33 +436,29 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
428
436
|
|
|
429
437
|
async def test_update_with_missing_iot_props(self):
|
|
430
438
|
# Mock IoT properties response with missing props
|
|
431
|
-
self.irrigation_service.get_iot_prop.return_value = {
|
|
432
|
-
'data': {
|
|
433
|
-
'props': {}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
439
|
+
self.irrigation_service.get_iot_prop.return_value = {"data": {"props": {}}}
|
|
436
440
|
|
|
437
441
|
self.irrigation_service.get_zone_by_device.return_value = {
|
|
438
|
-
|
|
439
|
-
'zones': []
|
|
440
|
-
}
|
|
442
|
+
"data": {"zones": []}
|
|
441
443
|
}
|
|
442
444
|
|
|
443
445
|
updated_irrigation = await self.irrigation_service.update(self.test_irrigation)
|
|
444
446
|
|
|
445
447
|
# Verify default values are used
|
|
446
448
|
self.assertEqual(updated_irrigation.RSSI, -65)
|
|
447
|
-
self.assertEqual(updated_irrigation.IP,
|
|
448
|
-
self.assertEqual(updated_irrigation.sn,
|
|
449
|
-
self.assertEqual(updated_irrigation.ssid,
|
|
450
|
-
self.assertFalse(
|
|
449
|
+
self.assertEqual(updated_irrigation.IP, "192.168.1.100")
|
|
450
|
+
self.assertEqual(updated_irrigation.sn, "SN123456789")
|
|
451
|
+
self.assertEqual(updated_irrigation.ssid, "ssid")
|
|
452
|
+
self.assertFalse(
|
|
453
|
+
updated_irrigation.available
|
|
454
|
+
) # iot_state missing, so not connected
|
|
451
455
|
|
|
452
456
|
async def test_get_irrigations_empty_device_list(self):
|
|
453
457
|
# Mock empty device list
|
|
454
458
|
self.irrigation_service.get_object_list.return_value = []
|
|
455
459
|
|
|
456
460
|
irrigations = await self.irrigation_service.get_irrigations()
|
|
457
|
-
|
|
461
|
+
|
|
458
462
|
# Verify empty list returned
|
|
459
463
|
self.assertEqual(len(irrigations), 0)
|
|
460
464
|
self.irrigation_service.get_object_list.assert_awaited_once()
|
|
@@ -464,7 +468,7 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
464
468
|
mock_camera = MagicMock()
|
|
465
469
|
mock_camera.type = DeviceTypes.CAMERA
|
|
466
470
|
mock_camera.product_model = "CAM_V1"
|
|
467
|
-
|
|
471
|
+
|
|
468
472
|
mock_bulb = MagicMock()
|
|
469
473
|
mock_bulb.type = DeviceTypes.LIGHT
|
|
470
474
|
mock_bulb.product_model = "LIGHT_V1"
|
|
@@ -472,7 +476,7 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
472
476
|
self.irrigation_service.get_object_list.return_value = [mock_camera, mock_bulb]
|
|
473
477
|
|
|
474
478
|
irrigations = await self.irrigation_service.get_irrigations()
|
|
475
|
-
|
|
479
|
+
|
|
476
480
|
# Verify no irrigation devices returned
|
|
477
481
|
self.assertEqual(len(irrigations), 0)
|
|
478
482
|
|
|
@@ -485,33 +489,35 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
485
489
|
"device_type": DeviceTypes.IRRIGATION.value,
|
|
486
490
|
"product_model": "WRONG_MODEL",
|
|
487
491
|
"mac": "IRRIG123",
|
|
488
|
-
"nickname": "Test Irrigation"
|
|
492
|
+
"nickname": "Test Irrigation",
|
|
489
493
|
}
|
|
490
494
|
|
|
491
495
|
self.irrigation_service.get_object_list.return_value = [mock_irrigation]
|
|
492
496
|
|
|
493
497
|
irrigations = await self.irrigation_service.get_irrigations()
|
|
494
|
-
|
|
498
|
+
|
|
495
499
|
# Verify no irrigation devices returned due to wrong product model
|
|
496
500
|
self.assertEqual(len(irrigations), 0)
|
|
497
501
|
|
|
498
502
|
async def test_set_zone_quickrun_duration_zone_not_found(self):
|
|
499
503
|
# Setup test irrigation with zones
|
|
500
504
|
self.test_irrigation.zones = [
|
|
501
|
-
Zone(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
505
|
+
Zone(
|
|
506
|
+
{
|
|
507
|
+
"zone_number": 1,
|
|
508
|
+
"name": "Zone 1",
|
|
509
|
+
"enabled": True,
|
|
510
|
+
"zone_id": "zone1",
|
|
511
|
+
"smart_duration": 600,
|
|
512
|
+
}
|
|
513
|
+
)
|
|
508
514
|
]
|
|
509
515
|
|
|
510
516
|
# Try to set duration for non-existent zone
|
|
511
517
|
result = await self.irrigation_service.set_zone_quickrun_duration(
|
|
512
518
|
self.test_irrigation,
|
|
513
519
|
99, # Non-existent zone
|
|
514
|
-
300
|
|
520
|
+
300,
|
|
515
521
|
)
|
|
516
522
|
|
|
517
523
|
# Verify existing zone unchanged
|
|
@@ -523,14 +529,204 @@ class TestIrrigationServiceEdgeCases(unittest.IsolatedAsyncioTestCase):
|
|
|
523
529
|
self.test_irrigation.zones = []
|
|
524
530
|
|
|
525
531
|
result = await self.irrigation_service.set_zone_quickrun_duration(
|
|
526
|
-
self.test_irrigation,
|
|
527
|
-
1,
|
|
528
|
-
300
|
|
532
|
+
self.test_irrigation, 1, 300
|
|
529
533
|
)
|
|
530
534
|
|
|
531
535
|
# Verify no error and empty zones list
|
|
532
536
|
self.assertEqual(len(self.test_irrigation.zones), 0)
|
|
533
537
|
self.assertEqual(result, self.test_irrigation)
|
|
534
538
|
|
|
535
|
-
|
|
536
|
-
|
|
539
|
+
async def test_get_schedule_runs_running_schedule(self):
|
|
540
|
+
# Mock the _get_schedule_runs method
|
|
541
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
542
|
+
mock_response = {
|
|
543
|
+
"data": {
|
|
544
|
+
"schedules": [
|
|
545
|
+
{
|
|
546
|
+
"schedule_state": "running",
|
|
547
|
+
"schedule_name": "Morning Watering",
|
|
548
|
+
"zone_runs": [
|
|
549
|
+
{
|
|
550
|
+
"zone_number": 3,
|
|
551
|
+
"zone_name": "Backyard S",
|
|
552
|
+
"start_ts": 1746376809,
|
|
553
|
+
"end_ts": 1746376869,
|
|
554
|
+
}
|
|
555
|
+
],
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
561
|
+
|
|
562
|
+
# Test get_schedule_runs with running schedule
|
|
563
|
+
result = await self.irrigation_service.get_schedule_runs(self.test_irrigation)
|
|
564
|
+
|
|
565
|
+
# Verify the call was made with correct parameters
|
|
566
|
+
self.irrigation_service._get_schedule_runs.assert_awaited_once_with(
|
|
567
|
+
"https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/schedule_runs",
|
|
568
|
+
self.test_irrigation,
|
|
569
|
+
limit=2,
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# Verify the result
|
|
573
|
+
expected_result = {"running": True, "zone_number": 3, "zone_name": "Backyard S"}
|
|
574
|
+
self.assertEqual(result, expected_result)
|
|
575
|
+
|
|
576
|
+
async def test_get_schedule_runs_past_schedule(self):
|
|
577
|
+
# Mock the _get_schedule_runs method
|
|
578
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
579
|
+
mock_response = {
|
|
580
|
+
"data": {
|
|
581
|
+
"schedules": [
|
|
582
|
+
{
|
|
583
|
+
"schedule_state": "past",
|
|
584
|
+
"schedule_name": "Evening Watering",
|
|
585
|
+
"zone_runs": [
|
|
586
|
+
{
|
|
587
|
+
"zone_number": 1,
|
|
588
|
+
"zone_name": "Front Yard",
|
|
589
|
+
"start_ts": 1746376809,
|
|
590
|
+
"end_ts": 1746376869,
|
|
591
|
+
}
|
|
592
|
+
],
|
|
593
|
+
}
|
|
594
|
+
]
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
598
|
+
|
|
599
|
+
# Test get_schedule_runs with past schedule
|
|
600
|
+
result = await self.irrigation_service.get_schedule_runs(self.test_irrigation)
|
|
601
|
+
|
|
602
|
+
# Verify the result
|
|
603
|
+
expected_result = {"running": False}
|
|
604
|
+
self.assertEqual(result, expected_result)
|
|
605
|
+
|
|
606
|
+
async def test_get_schedule_runs_no_schedules(self):
|
|
607
|
+
# Mock the _get_schedule_runs method
|
|
608
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
609
|
+
mock_response = {"data": {"schedules": []}}
|
|
610
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
611
|
+
|
|
612
|
+
# Test get_schedule_runs with no schedules
|
|
613
|
+
result = await self.irrigation_service.get_schedule_runs(self.test_irrigation)
|
|
614
|
+
|
|
615
|
+
# Verify the result
|
|
616
|
+
expected_result = {"running": False}
|
|
617
|
+
self.assertEqual(result, expected_result)
|
|
618
|
+
|
|
619
|
+
async def test_get_schedule_runs_no_data(self):
|
|
620
|
+
# Mock the _get_schedule_runs method
|
|
621
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
622
|
+
mock_response = {} # No data field
|
|
623
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
624
|
+
|
|
625
|
+
# Test get_schedule_runs with no data
|
|
626
|
+
with self.assertLogs(
|
|
627
|
+
"wyzeapy.services.irrigation_service", level="WARNING"
|
|
628
|
+
) as log:
|
|
629
|
+
result = await self.irrigation_service.get_schedule_runs(
|
|
630
|
+
self.test_irrigation
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
# Verify the result
|
|
634
|
+
expected_result = {"running": False}
|
|
635
|
+
self.assertEqual(result, expected_result)
|
|
636
|
+
|
|
637
|
+
# Verify warning was logged
|
|
638
|
+
self.assertIn(
|
|
639
|
+
"No schedule data found in response for device IRRIG123", log.output[0]
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
async def test_get_schedule_runs_multiple_schedules_running_first(self):
|
|
643
|
+
# Mock the _get_schedule_runs method
|
|
644
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
645
|
+
mock_response = {
|
|
646
|
+
"data": {
|
|
647
|
+
"schedules": [
|
|
648
|
+
{
|
|
649
|
+
"schedule_state": "running",
|
|
650
|
+
"schedule_name": "Morning Watering",
|
|
651
|
+
"zone_runs": [
|
|
652
|
+
{
|
|
653
|
+
"zone_number": 2,
|
|
654
|
+
"zone_name": "Side Yard",
|
|
655
|
+
"start_ts": 1746376809,
|
|
656
|
+
"end_ts": 1746376869,
|
|
657
|
+
}
|
|
658
|
+
],
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"schedule_state": "past",
|
|
662
|
+
"schedule_name": "Evening Watering",
|
|
663
|
+
"zone_runs": [
|
|
664
|
+
{
|
|
665
|
+
"zone_number": 1,
|
|
666
|
+
"zone_name": "Front Yard",
|
|
667
|
+
"start_ts": 1746376809,
|
|
668
|
+
"end_ts": 1746376869,
|
|
669
|
+
}
|
|
670
|
+
],
|
|
671
|
+
},
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
676
|
+
|
|
677
|
+
# Test get_schedule_runs with multiple schedules, first one running
|
|
678
|
+
result = await self.irrigation_service.get_schedule_runs(self.test_irrigation)
|
|
679
|
+
|
|
680
|
+
# Verify the result uses the first running schedule
|
|
681
|
+
expected_result = {"running": True, "zone_number": 2, "zone_name": "Side Yard"}
|
|
682
|
+
self.assertEqual(result, expected_result)
|
|
683
|
+
|
|
684
|
+
async def test_get_schedule_runs_multiple_schedules_running_second(self):
|
|
685
|
+
# Mock the _get_schedule_runs method
|
|
686
|
+
self.irrigation_service._get_schedule_runs = AsyncMock()
|
|
687
|
+
mock_response = {
|
|
688
|
+
"data": {
|
|
689
|
+
"schedules": [
|
|
690
|
+
{
|
|
691
|
+
"schedule_state": "past",
|
|
692
|
+
"schedule_name": "Evening Watering",
|
|
693
|
+
"zone_runs": [
|
|
694
|
+
{
|
|
695
|
+
"zone_number": 1,
|
|
696
|
+
"zone_name": "Front Yard",
|
|
697
|
+
"start_ts": 1746376809,
|
|
698
|
+
"end_ts": 1746376869,
|
|
699
|
+
}
|
|
700
|
+
],
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"schedule_state": "running",
|
|
704
|
+
"schedule_name": "Morning Watering",
|
|
705
|
+
"zone_runs": [
|
|
706
|
+
{
|
|
707
|
+
"zone_number": 4,
|
|
708
|
+
"zone_name": "Garden Area",
|
|
709
|
+
"start_ts": 1746376809,
|
|
710
|
+
"end_ts": 1746376869,
|
|
711
|
+
}
|
|
712
|
+
],
|
|
713
|
+
},
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
self.irrigation_service._get_schedule_runs.return_value = mock_response
|
|
718
|
+
|
|
719
|
+
# Test get_schedule_runs with multiple schedules, second one running
|
|
720
|
+
result = await self.irrigation_service.get_schedule_runs(self.test_irrigation)
|
|
721
|
+
|
|
722
|
+
# Verify the result uses the first running schedule found
|
|
723
|
+
expected_result = {
|
|
724
|
+
"running": True,
|
|
725
|
+
"zone_number": 4,
|
|
726
|
+
"zone_name": "Garden Area",
|
|
727
|
+
}
|
|
728
|
+
self.assertEqual(result, expected_result)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
if __name__ == "__main__":
|
|
732
|
+
unittest.main()
|