wyzeapy 0.5.30__py3-none-any.whl → 0.5.31__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.
@@ -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
- "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
- })
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
- 'data': {
36
- 'props': {
37
- 'RSSI': '-65',
38
- 'IP': '192.168.1.100',
39
- 'sn': 'SN123456789',
40
- 'ssid': 'TestSSID',
41
- 'iot_state': 'connected'
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
- 'data': {
49
- 'zones': [
49
+ "data": {
50
+ "zones": [
50
51
  {
51
- 'zone_number': 1,
52
- 'name': 'Zone 1',
53
- 'enabled': True,
54
- 'zone_id': 'zone1',
55
- 'smart_duration': 600
52
+ "zone_number": 1,
53
+ "name": "Zone 1",
54
+ "enabled": True,
55
+ "zone_id": "zone1",
56
+ "smart_duration": 600,
56
57
  },
57
58
  {
58
- 'zone_number': 2,
59
- 'name': 'Zone 2',
60
- 'enabled': True,
61
- 'zone_id': 'zone2',
62
- 'smart_duration': 900
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, '-65')
72
- self.assertEqual(updated_irrigation.IP, '192.168.1.100')
73
- self.assertEqual(updated_irrigation.sn, 'SN123456789')
74
- self.assertEqual(updated_irrigation.ssid, 'TestSSID')
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, 'Zone 1')
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, 'Zone 2')
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
- 'zone_number': 1,
120
- 'name': 'Zone 1',
121
- 'enabled': True,
122
- 'zone_id': 'zone1',
123
- 'smart_duration': 400
124
- }),
125
- Zone({
126
- 'zone_number': 2,
127
- 'name': 'Zone 2',
128
- 'enabled': True,
129
- 'zone_id': 'zone2',
130
- 'smart_duration': 900
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(self.test_irrigation.zones[0].quickrun_duration, 300) # First zone changed to 300
151
- self.assertEqual(self.test_irrigation.zones[1].quickrun_duration, 900) # Second zone should be unchanged at 900
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
- 'data': {
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
- 'data': {
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, '-65')
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 = {'data': {'result': 'success'}}
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 = {'data': {'result': 'stopped'}}
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(self.test_irrigation)
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
- 'data': {
219
- 'props': {
220
- 'RSSI': '-70',
221
- 'IP': '192.168.1.101',
222
- 'sn': 'SN987654321',
223
- 'ssid': 'NewSSID',
224
- 'iot_state': 'connected'
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(self.test_irrigation)
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, '-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')
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
- 'data': {
242
- 'props': {
243
- 'RSSI': '-80',
244
- 'IP': '192.168.1.102',
245
- 'sn': 'SN555666777',
246
- 'ssid': 'TestSSID2',
247
- 'iot_state': 'disconnected'
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(self.test_irrigation)
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, '-80')
257
- self.assertEqual(updated_irrigation.IP, '192.168.1.102')
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 = {'data': {'props': {'RSSI': '-65'}}}
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(self.test_irrigation)
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 = {'data': {'props': {'enable_schedules': True}}}
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 = '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'
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 = {'data': {'zones': [{'zone_number': 1, 'name': 'Zone 1'}]}}
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(self.test_irrigation)
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 = {'zone_number': 3, 'name': 'Test Zone'}
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, 'Test Zone')
315
+ self.assertEqual(zone.name, "Test Zone")
310
316
  self.assertTrue(zone.enabled) # Default value
311
- self.assertEqual(zone.zone_id, 'zone_id') # Default value
317
+ self.assertEqual(zone.zone_id, "zone_id") # Default value
312
318
  self.assertEqual(zone.smart_duration, 600) # Default value
313
- self.assertEqual(zone.quickrun_duration, 600) # Default value from smart_duration
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
- 'zone_number': 2,
319
- 'name': 'Garden Zone',
320
- 'enabled': False,
321
- 'zone_id': 'zone_garden',
322
- 'smart_duration': 1200
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, 'Garden Zone')
335
+ self.assertEqual(zone.name, "Garden Zone")
328
336
  self.assertFalse(zone.enabled)
329
- self.assertEqual(zone.zone_id, 'zone_garden')
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, 'Zone 1') # Default value
346
+ self.assertEqual(zone.name, "Zone 1") # Default value
339
347
  self.assertTrue(zone.enabled) # Default value
340
- self.assertEqual(zone.zone_id, 'zone_id') # Default value
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
- "device_type": DeviceTypes.IRRIGATION.value,
395
- "product_model": "BS_WK1",
396
- "mac": "IRRIG123",
397
- "nickname": "Test Irrigation",
398
- "device_params": {"ip": "192.168.1.100"},
399
- "raw_dict": {}
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
- 'data': {
406
- 'props': {
407
- 'RSSI': '-65',
408
- 'IP': '192.168.1.100',
409
- 'sn': 'SN123456789',
410
- 'ssid': 'TestSSID',
411
- 'iot_state': 'connected'
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
- 'data': {
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
- 'data': {
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, '192.168.1.100')
448
- self.assertEqual(updated_irrigation.sn, 'SN123456789')
449
- self.assertEqual(updated_irrigation.ssid, 'ssid')
450
- self.assertFalse(updated_irrigation.available) # iot_state missing, so not connected
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
- 'zone_number': 1,
503
- 'name': 'Zone 1',
504
- 'enabled': True,
505
- 'zone_id': 'zone1',
506
- 'smart_duration': 600
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
- if __name__ == '__main__':
536
- unittest.main()
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()