-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathtest_static_routes.py
More file actions
447 lines (346 loc) · 17.4 KB
/
Copy pathtest_static_routes.py
File metadata and controls
447 lines (346 loc) · 17.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
"""Tests for the UniFi Network Rules Static Routes functionality."""
import asyncio
from unittest.mock import AsyncMock, Mock, patch
import pytest
from custom_components.unifi_network_rules.coordinator import UnifiRuleUpdateCoordinator
from custom_components.unifi_network_rules.models.static_route import (
StaticRoute,
StaticRouteRequest,
)
from custom_components.unifi_network_rules.switch import UnifiStaticRouteSwitch
from custom_components.unifi_network_rules.udm.routes import RoutesMixin
@pytest.fixture
def static_route_data():
"""Return example static route data from UniFi API."""
return {
"_id": "67507951234567890abcdef0",
"name": "Guest Network Route",
"enabled": True,
"site_id": "600ee7b12345678901234567",
"static-route_network": "192.168.2.0/24",
"static-route_interface": "600ee7b12345678901234568",
"gateway_device": "60:60:60:60:60:60",
"gateway_type": "default",
"static-route_type": "interface-route",
"type": "static-route",
"static-route_distance": 1,
}
@pytest.fixture
def static_route_data_minimal():
"""Return minimal static route data."""
return {
"_id": "67507951234567890abcdef1",
"static-route_network": "10.0.100.0/24",
"gateway_device": "192.168.1.1",
"gateway_type": "default",
"static-route_type": "static-route",
"type": "static-route",
}
@pytest.fixture
def routes_mixin():
"""Return a RoutesMixin instance with mocked controller."""
mixin = RoutesMixin()
mixin.controller = AsyncMock()
return mixin
class TestStaticRouteModel:
"""Tests for StaticRoute model."""
def test_static_route_initialization(self, static_route_data):
"""Test StaticRoute initialization with complete data."""
route = StaticRoute(static_route_data)
assert route.id == "67507951234567890abcdef0"
assert route.name == "Guest Network Route"
assert route.enabled is True
assert route.destination == "192.168.2.0/24"
assert route.gateway == "60:60:60:60:60:60"
assert route.interface == "600ee7b12345678901234568"
assert route.route_type == "interface-route"
assert route.gateway_type == "default"
assert route.site_id == "600ee7b12345678901234567"
assert route.distance == 1
def test_static_route_initialization_minimal(self, static_route_data_minimal):
"""Test StaticRoute initialization with minimal data."""
route = StaticRoute(static_route_data_minimal)
assert route.id == "67507951234567890abcdef1"
assert route.name == "Route 10.0.100.0/24" # Generated name
assert route.enabled is True # Default value
assert route.destination == "10.0.100.0/24"
assert route.gateway == "192.168.1.1"
assert route.interface is None
assert route.route_type == "static-route"
assert route.gateway_type == "default"
assert route.distance is None
def test_static_route_defaults(self):
"""Test StaticRoute applies proper defaults."""
minimal_data = {"_id": "test123", "static-route_network": "172.16.0.0/16"}
route = StaticRoute(minimal_data)
assert route.enabled is True
assert route.name == "Route 172.16.0.0/16"
assert route.raw["type"] == "static-route"
def test_static_route_string_representation(self, static_route_data):
"""Test StaticRoute string representations."""
route = StaticRoute(static_route_data)
str_repr = str(route)
assert "Guest Network Route" in str_repr
assert "192.168.2.0/24" in str_repr
assert "60:60:60:60:60:60" in str_repr
repr_str = repr(route)
assert "StaticRoute" in repr_str
assert "67507951234567890abcdef0" in repr_str
assert "enabled=True" in repr_str
class TestStaticRouteRequest:
"""Tests for StaticRouteRequest API request objects."""
def test_create_get_request(self):
"""Test creating GET request for static routes."""
request = StaticRouteRequest.create_get_request()
assert request.method == "get"
assert request.path == "/rest/routing"
assert request.data is None
def test_create_update_request(self, static_route_data):
"""Test creating PUT request to update static route."""
route = StaticRoute(static_route_data)
request = StaticRouteRequest.create_update_request(route)
assert request.method == "put"
assert request.path == "/rest/routing/67507951234567890abcdef0"
assert request.data == route.raw
class TestRoutesMixinStaticRoutes:
"""Tests for RoutesMixin static route methods."""
@pytest.mark.asyncio
async def test_get_static_routes_success(self, routes_mixin, static_route_data):
"""Test successful retrieval of static routes."""
api_response = {"meta": {"rc": "ok"}, "data": [static_route_data]}
routes_mixin.controller.request.return_value = api_response
routes = await routes_mixin.get_static_routes()
assert len(routes) == 1
assert isinstance(routes[0], StaticRoute)
assert routes[0].id == "67507951234567890abcdef0"
assert routes[0].name == "Guest Network Route"
routes_mixin.controller.request.assert_called_once()
@pytest.mark.asyncio
async def test_get_static_routes_empty_response(self, routes_mixin):
"""Test handling empty response from API."""
api_response = {"meta": {"rc": "ok"}, "data": []}
routes_mixin.controller.request.return_value = api_response
routes = await routes_mixin.get_static_routes()
assert routes == []
@pytest.mark.asyncio
async def test_get_static_routes_no_data_key(self, routes_mixin):
"""Test handling response without data key."""
api_response = {"meta": {"rc": "ok"}}
routes_mixin.controller.request.return_value = api_response
routes = await routes_mixin.get_static_routes()
assert routes == []
@pytest.mark.asyncio
async def test_get_static_routes_api_error(self, routes_mixin):
"""Test handling API errors."""
routes_mixin.controller.request.side_effect = Exception("API Error")
routes = await routes_mixin.get_static_routes()
assert routes == []
@pytest.mark.asyncio
async def test_update_static_route_success(self, routes_mixin, static_route_data):
"""Test successful static route update."""
route = StaticRoute(static_route_data)
routes_mixin.controller.request.return_value = {"meta": {"rc": "ok"}}
result = await routes_mixin.update_static_route(route)
assert result is True
routes_mixin.controller.request.assert_called_once()
call_args = routes_mixin.controller.request.call_args[0][0]
assert call_args.method == "put"
assert call_args.path == "/rest/routing/67507951234567890abcdef0"
@pytest.mark.asyncio
async def test_update_static_route_api_error(self, routes_mixin, static_route_data):
"""Test handling API errors during update."""
route = StaticRoute(static_route_data)
routes_mixin.controller.request.side_effect = Exception("Update failed")
result = await routes_mixin.update_static_route(route)
assert result is False
@pytest.mark.asyncio
async def test_toggle_static_route_enable(self, routes_mixin, static_route_data):
"""Test setting static route to enabled state."""
# Start with disabled route
static_route_data["enabled"] = False
route = StaticRoute(static_route_data)
routes_mixin.controller.request.return_value = {"meta": {"rc": "ok"}}
# Explicitly set target_state to True
result = await routes_mixin.toggle_static_route(route, target_state=True)
assert result is True
routes_mixin.controller.request.assert_called_once()
call_args = routes_mixin.controller.request.call_args[0][0]
assert call_args.data["enabled"] is True
@pytest.mark.asyncio
async def test_toggle_static_route_disable(self, routes_mixin, static_route_data):
"""Test setting static route to disabled state."""
# Start with enabled route
static_route_data["enabled"] = True
route = StaticRoute(static_route_data)
routes_mixin.controller.request.return_value = {"meta": {"rc": "ok"}}
# Explicitly set target_state to False
result = await routes_mixin.toggle_static_route(route, target_state=False)
assert result is True
routes_mixin.controller.request.assert_called_once()
call_args = routes_mixin.controller.request.call_args[0][0]
assert call_args.data["enabled"] is False
@pytest.mark.asyncio
async def test_toggle_static_route_api_error(self, routes_mixin, static_route_data):
"""Test handling API errors during toggle."""
route = StaticRoute(static_route_data)
routes_mixin.controller.request.side_effect = Exception("Toggle failed")
# Explicitly set target_state
result = await routes_mixin.toggle_static_route(route, target_state=True)
assert result is False
class TestUnifiStaticRouteSwitch:
"""Tests for UnifiStaticRouteSwitch entity."""
@pytest.fixture
def mock_coordinator(self):
"""Return a mocked coordinator."""
coordinator = Mock(spec=UnifiRuleUpdateCoordinator)
coordinator.api = AsyncMock()
coordinator.hass = Mock()
# Add required attributes for helper functions
coordinator.firewall_zones = []
# Properly handle async_create_task to prevent coroutine warnings
def mock_async_create_task(coro):
if asyncio.iscoroutine(coro):
try:
loop = asyncio.get_running_loop()
return loop.create_task(coro)
except RuntimeError:
coro.close()
return Mock()
return Mock()
coordinator.hass.async_create_task = mock_async_create_task
return coordinator
def test_static_route_switch_initialization(self, mock_coordinator, static_route_data):
"""Test StaticRouteSwitch initialization."""
route = StaticRoute(static_route_data)
switch = UnifiStaticRouteSwitch(
coordinator=mock_coordinator, rule_data=route, rule_type="static_routes", entry_id="test_entry"
)
assert switch._attr_icon == "mdi:map-marker-path"
assert switch.coordinator == mock_coordinator
def test_static_route_switch_properties(self, mock_coordinator, static_route_data):
"""Test static route switch has correct properties."""
route = StaticRoute(static_route_data)
switch = UnifiStaticRouteSwitch(coordinator=mock_coordinator, rule_data=route, rule_type="static_routes")
# Test that the switch has the expected properties
assert hasattr(switch, "coordinator")
assert switch.coordinator == mock_coordinator
assert hasattr(switch, "_rule_type")
assert switch._rule_type == "static_routes"
assert hasattr(switch, "_rule_data")
assert isinstance(switch._rule_data, StaticRoute)
def test_static_route_switch_api_integration(self, mock_coordinator, static_route_data):
"""Test static route switch integrates with API properly."""
route = StaticRoute(static_route_data)
mock_coordinator.api.toggle_static_route = Mock()
UnifiStaticRouteSwitch(coordinator=mock_coordinator, rule_data=route, rule_type="static_routes")
# Test that the API method exists and is accessible
assert hasattr(mock_coordinator.api, "toggle_static_route")
assert callable(mock_coordinator.api.toggle_static_route)
class TestStaticRoutesIntegration:
"""Tests for static routes integration with coordinator and triggers."""
@pytest.mark.asyncio
async def test_coordinator_static_routes_update(self):
"""Test coordinator updates static routes collection."""
with patch("custom_components.unifi_network_rules.coordination.coordinator.UDMAPI") as mock_api_class:
mock_api = AsyncMock()
mock_api.get_static_routes.return_value = [
StaticRoute({"_id": "route1", "static-route_network": "192.168.1.0/24"}),
StaticRoute({"_id": "route2", "static-route_network": "10.0.0.0/8"}),
]
mock_api_class.return_value = mock_api
# Test that coordinator would call the API method
result = await mock_api.get_static_routes()
assert len(result) == 2
assert isinstance(result[0], StaticRoute)
def test_static_routes_trigger_integration(self):
"""Test static routes are included in trigger system."""
from custom_components.unifi_network_rules.unified_change_detector import UnifiedChangeDetector
from custom_components.unifi_network_rules.unified_trigger import VALID_CHANGE_TYPES
# Test "route" is in valid change types
assert "route" in VALID_CHANGE_TYPES
# Test change detector has static routes mapping
mock_hass = Mock()
mock_coordinator = Mock()
detector = UnifiedChangeDetector(mock_hass, mock_coordinator)
assert "static_routes" in detector._rule_type_mapping
assert detector._rule_type_mapping["static_routes"] == "route"
def test_static_routes_backup_integration(self):
"""Test static routes are included in backup service mapping."""
from custom_components.unifi_network_rules.services.backup_services import async_backup_rules_service
# This tests that the backup service would handle static routes
# The actual rule_type_map is defined within the should_restore function
# We test that the functionality exists by checking the pattern
assert callable(async_backup_rules_service)
def test_switch_rule_types_includes_static_routes(self):
"""Test RULE_TYPES includes static routes."""
from custom_components.unifi_network_rules.switch import RULE_TYPES
assert "static_routes" in RULE_TYPES
assert RULE_TYPES["static_routes"] == "Static Route"
class TestStaticRoutesErrorHandling:
"""Tests for error handling in static routes functionality."""
@pytest.mark.asyncio
async def test_invalid_route_data_handling(self):
"""Test handling of invalid route data."""
# Test with missing required fields
invalid_data = {"name": "Invalid Route"}
route = StaticRoute(invalid_data)
# Should handle gracefully with defaults
assert route.id == ""
assert route.destination == ""
assert route.enabled is True
@pytest.mark.asyncio
async def test_api_timeout_handling(self, routes_mixin):
"""Test handling of API timeouts."""
routes_mixin.controller.request.side_effect = TimeoutError("Request timeout")
routes = await routes_mixin.get_static_routes()
assert routes == []
@pytest.mark.asyncio
async def test_malformed_api_response(self, routes_mixin):
"""Test handling of malformed API responses."""
# Test with malformed JSON-like response
routes_mixin.controller.request.return_value = {"invalid": "structure"}
routes = await routes_mixin.get_static_routes()
assert routes == []
@pytest.mark.asyncio
async def test_network_error_handling(self, routes_mixin):
"""Test handling of network-related errors."""
import aiohttp
routes_mixin.controller.request.side_effect = aiohttp.ClientError("Network error")
result = await routes_mixin.get_static_routes()
assert result == []
class TestStaticRoutesPerformance:
"""Tests for static routes performance considerations."""
@pytest.mark.asyncio
async def test_large_routes_collection_handling(self, routes_mixin):
"""Test handling large collections of static routes efficiently."""
# Generate a large number of static routes
large_route_data = []
for i in range(100):
large_route_data.append(
{
"_id": f"route_{i:03d}",
"name": f"Route {i}",
"static-route_network": f"10.{i}.0.0/24",
"gateway_device": "192.168.1.1",
"enabled": i % 2 == 0, # Alternate enabled/disabled
"type": "static-route",
}
)
api_response = {"meta": {"rc": "ok"}, "data": large_route_data}
routes_mixin.controller.request.return_value = api_response
routes = await routes_mixin.get_static_routes()
assert len(routes) == 100
assert all(isinstance(route, StaticRoute) for route in routes)
# Test that enabled states are preserved
enabled_count = sum(1 for route in routes if route.enabled)
assert enabled_count == 50 # Half should be enabled
def test_static_route_memory_efficiency(self, static_route_data):
"""Test that StaticRoute objects are memory efficient."""
# Create multiple route objects and ensure they don't duplicate data unnecessarily
routes = [StaticRoute(static_route_data.copy()) for _ in range(10)]
# Each route should have its own raw data copy
for i, route in enumerate(routes):
route.raw["test_field"] = f"unique_{i}"
# Verify data isolation
unique_values = {route.raw.get("test_field") for route in routes}
assert len(unique_values) == 10