ipfabric_netbox 4.3.2b8__py3-none-any.whl → 4.3.2b10__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.
Potentially problematic release.
This version of ipfabric_netbox might be problematic. Click here for more details.
- ipfabric_netbox/__init__.py +2 -2
- ipfabric_netbox/api/serializers.py +112 -7
- ipfabric_netbox/api/urls.py +6 -0
- ipfabric_netbox/api/views.py +23 -0
- ipfabric_netbox/choices.py +72 -40
- ipfabric_netbox/data/endpoint.json +47 -0
- ipfabric_netbox/data/filters.json +51 -0
- ipfabric_netbox/data/transform_map.json +188 -174
- ipfabric_netbox/exceptions.py +7 -5
- ipfabric_netbox/filtersets.py +310 -41
- ipfabric_netbox/forms.py +324 -79
- ipfabric_netbox/graphql/__init__.py +6 -0
- ipfabric_netbox/graphql/enums.py +5 -5
- ipfabric_netbox/graphql/filters.py +56 -4
- ipfabric_netbox/graphql/schema.py +28 -0
- ipfabric_netbox/graphql/types.py +61 -1
- ipfabric_netbox/jobs.py +18 -1
- ipfabric_netbox/migrations/0022_prepare_for_filters.py +182 -0
- ipfabric_netbox/migrations/0023_populate_filters_data.py +279 -0
- ipfabric_netbox/migrations/0024_finish_filters.py +29 -0
- ipfabric_netbox/models.py +384 -12
- ipfabric_netbox/navigation.py +98 -24
- ipfabric_netbox/tables.py +194 -9
- ipfabric_netbox/templates/ipfabric_netbox/htmx_list.html +5 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions.html +59 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/combined_expressions_content.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/inc/endpoint_filters_with_selector.html +54 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricendpoint.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilter.html +51 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression.html +39 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricfilterexpression_edit.html +150 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +16 -2
- ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +65 -0
- ipfabric_netbox/tests/api/test_api.py +333 -13
- ipfabric_netbox/tests/test_filtersets.py +2592 -0
- ipfabric_netbox/tests/test_forms.py +1256 -74
- ipfabric_netbox/tests/test_models.py +242 -34
- ipfabric_netbox/tests/test_views.py +2030 -25
- ipfabric_netbox/urls.py +35 -0
- ipfabric_netbox/utilities/endpoint.py +30 -0
- ipfabric_netbox/utilities/filters.py +88 -0
- ipfabric_netbox/utilities/ipfutils.py +254 -316
- ipfabric_netbox/utilities/logging.py +7 -7
- ipfabric_netbox/utilities/transform_map.py +126 -0
- ipfabric_netbox/views.py +719 -5
- {ipfabric_netbox-4.3.2b8.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/METADATA +3 -2
- {ipfabric_netbox-4.3.2b8.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/RECORD +49 -33
- {ipfabric_netbox-4.3.2b8.dist-info → ipfabric_netbox-4.3.2b10.dist-info}/WHEEL +1 -1
|
@@ -0,0 +1,2592 @@
|
|
|
1
|
+
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.test import TestCase
|
|
3
|
+
from django.utils import timezone
|
|
4
|
+
from netbox_branching.models import Branch
|
|
5
|
+
from netbox_branching.models import ChangeDiff
|
|
6
|
+
|
|
7
|
+
from ipfabric_netbox.choices import IPFabricEndpointChoices
|
|
8
|
+
from ipfabric_netbox.choices import IPFabricFilterTypeChoices
|
|
9
|
+
from ipfabric_netbox.choices import IPFabricSnapshotStatusModelChoices
|
|
10
|
+
from ipfabric_netbox.choices import IPFabricSourceStatusChoices
|
|
11
|
+
from ipfabric_netbox.choices import IPFabricSourceTypeChoices
|
|
12
|
+
from ipfabric_netbox.choices import IPFabricSyncStatusChoices
|
|
13
|
+
from ipfabric_netbox.filtersets import IPFabricDataFilterSet
|
|
14
|
+
from ipfabric_netbox.filtersets import IPFabricEndpointFilterSet
|
|
15
|
+
from ipfabric_netbox.filtersets import IPFabricFilterExpressionFilterSet
|
|
16
|
+
from ipfabric_netbox.filtersets import IPFabricFilterFilterSet
|
|
17
|
+
from ipfabric_netbox.filtersets import IPFabricIngestionChangeFilterSet
|
|
18
|
+
from ipfabric_netbox.filtersets import IPFabricIngestionFilterSet
|
|
19
|
+
from ipfabric_netbox.filtersets import IPFabricIngestionIssueFilterSet
|
|
20
|
+
from ipfabric_netbox.filtersets import IPFabricRelationshipFieldFilterSet
|
|
21
|
+
from ipfabric_netbox.filtersets import IPFabricSnapshotFilterSet
|
|
22
|
+
from ipfabric_netbox.filtersets import IPFabricSourceFilterSet
|
|
23
|
+
from ipfabric_netbox.filtersets import IPFabricSyncFilterSet
|
|
24
|
+
from ipfabric_netbox.filtersets import IPFabricTransformFieldFilterSet
|
|
25
|
+
from ipfabric_netbox.filtersets import IPFabricTransformMapFilterSet
|
|
26
|
+
from ipfabric_netbox.filtersets import IPFabricTransformMapGroupFilterSet
|
|
27
|
+
from ipfabric_netbox.models import IPFabricData
|
|
28
|
+
from ipfabric_netbox.models import IPFabricEndpoint
|
|
29
|
+
from ipfabric_netbox.models import IPFabricFilter
|
|
30
|
+
from ipfabric_netbox.models import IPFabricFilterExpression
|
|
31
|
+
from ipfabric_netbox.models import IPFabricIngestion
|
|
32
|
+
from ipfabric_netbox.models import IPFabricIngestionIssue
|
|
33
|
+
from ipfabric_netbox.models import IPFabricRelationshipField
|
|
34
|
+
from ipfabric_netbox.models import IPFabricSnapshot
|
|
35
|
+
from ipfabric_netbox.models import IPFabricSource
|
|
36
|
+
from ipfabric_netbox.models import IPFabricSync
|
|
37
|
+
from ipfabric_netbox.models import IPFabricTransformField
|
|
38
|
+
from ipfabric_netbox.models import IPFabricTransformMap
|
|
39
|
+
from ipfabric_netbox.models import IPFabricTransformMapGroup
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IPFabricEndpointFilterSetTestCase(TestCase):
|
|
43
|
+
"""
|
|
44
|
+
Test IPFabricEndpointFilterSet to verify all custom filters work correctly.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
queryset = IPFabricEndpoint.objects.all()
|
|
48
|
+
filterset = IPFabricEndpointFilterSet
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def setUpTestData(cls):
|
|
52
|
+
# Use existing endpoints from migrations (created from endpoint.json)
|
|
53
|
+
# These are created by the prepare_endpoints migration
|
|
54
|
+
cls.sites_endpoint = IPFabricEndpoint.objects.filter(
|
|
55
|
+
endpoint=IPFabricEndpointChoices.SITES
|
|
56
|
+
).first()
|
|
57
|
+
cls.devices_endpoint = IPFabricEndpoint.objects.filter(
|
|
58
|
+
endpoint=IPFabricEndpointChoices.DEVICES
|
|
59
|
+
).first()
|
|
60
|
+
cls.vrfs_endpoint = IPFabricEndpoint.objects.filter(
|
|
61
|
+
endpoint=IPFabricEndpointChoices.VRFS
|
|
62
|
+
).first()
|
|
63
|
+
cls.vlans_endpoint = IPFabricEndpoint.objects.filter(
|
|
64
|
+
endpoint=IPFabricEndpointChoices.VLANS
|
|
65
|
+
).first()
|
|
66
|
+
|
|
67
|
+
# Ensure endpoints exist (they should from migrations)
|
|
68
|
+
if not all(
|
|
69
|
+
[
|
|
70
|
+
cls.sites_endpoint,
|
|
71
|
+
cls.devices_endpoint,
|
|
72
|
+
cls.vrfs_endpoint,
|
|
73
|
+
cls.vlans_endpoint,
|
|
74
|
+
]
|
|
75
|
+
):
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"Required endpoints not found. Ensure migrations have been run."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Create test filters
|
|
81
|
+
filters = [
|
|
82
|
+
IPFabricFilter(
|
|
83
|
+
name="Test Filter 1",
|
|
84
|
+
description="First test filter",
|
|
85
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
86
|
+
),
|
|
87
|
+
IPFabricFilter(
|
|
88
|
+
name="Test Filter 2",
|
|
89
|
+
description="Second test filter",
|
|
90
|
+
filter_type=IPFabricFilterTypeChoices.OR,
|
|
91
|
+
),
|
|
92
|
+
IPFabricFilter(
|
|
93
|
+
name="Test Filter 3",
|
|
94
|
+
description="Third test filter",
|
|
95
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
IPFabricFilter.objects.bulk_create(filters)
|
|
99
|
+
|
|
100
|
+
# Get created filters
|
|
101
|
+
cls.filter1 = IPFabricFilter.objects.get(name="Test Filter 1")
|
|
102
|
+
cls.filter2 = IPFabricFilter.objects.get(name="Test Filter 2")
|
|
103
|
+
cls.filter3 = IPFabricFilter.objects.get(name="Test Filter 3")
|
|
104
|
+
|
|
105
|
+
# Assign endpoints to filters
|
|
106
|
+
# Filter 1 uses sites and devices endpoints
|
|
107
|
+
cls.filter1.endpoints.add(cls.sites_endpoint, cls.devices_endpoint)
|
|
108
|
+
|
|
109
|
+
# Filter 2 uses devices and VRFs endpoints
|
|
110
|
+
cls.filter2.endpoints.add(cls.devices_endpoint, cls.vrfs_endpoint)
|
|
111
|
+
|
|
112
|
+
# Filter 3 uses only VLANs endpoint
|
|
113
|
+
cls.filter3.endpoints.add(cls.vlans_endpoint)
|
|
114
|
+
|
|
115
|
+
def test_id(self):
|
|
116
|
+
"""Test filtering by endpoint ID"""
|
|
117
|
+
params = {"id": [self.sites_endpoint.pk]}
|
|
118
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
119
|
+
params = {"id": [self.sites_endpoint.pk, self.devices_endpoint.pk]}
|
|
120
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
121
|
+
|
|
122
|
+
def test_name(self):
|
|
123
|
+
"""Test filtering by endpoint name"""
|
|
124
|
+
# Use actual endpoint names from migrations
|
|
125
|
+
params = {"name": [self.sites_endpoint.name]}
|
|
126
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
127
|
+
params = {"name": [self.sites_endpoint.name, self.devices_endpoint.name]}
|
|
128
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
129
|
+
|
|
130
|
+
def test_description(self):
|
|
131
|
+
"""Test filtering by description"""
|
|
132
|
+
# Test with actual description from endpoint (may be empty)
|
|
133
|
+
if self.sites_endpoint.description:
|
|
134
|
+
params = {"description": [self.sites_endpoint.description]}
|
|
135
|
+
result = self.filterset(params, self.queryset).qs
|
|
136
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
137
|
+
|
|
138
|
+
def test_endpoint_path(self):
|
|
139
|
+
"""Test filtering by endpoint path (URL format)"""
|
|
140
|
+
# Test with URL path format - should convert to dot notation
|
|
141
|
+
params = {"endpoint": "/technology/routing/vrf/detail"}
|
|
142
|
+
result = self.filterset(params, self.queryset).qs
|
|
143
|
+
self.assertEqual(result.count(), 1)
|
|
144
|
+
self.assertEqual(result.first().endpoint, IPFabricEndpointChoices.VRFS)
|
|
145
|
+
|
|
146
|
+
# Test sites endpoint
|
|
147
|
+
params = {"endpoint": "/inventory/sites/overview"}
|
|
148
|
+
result = self.filterset(params, self.queryset).qs
|
|
149
|
+
self.assertEqual(result.count(), 1)
|
|
150
|
+
self.assertEqual(result.first().endpoint, IPFabricEndpointChoices.SITES)
|
|
151
|
+
|
|
152
|
+
# Test VLANs endpoint
|
|
153
|
+
params = {"endpoint": "/technology/vlans/site-summary"}
|
|
154
|
+
result = self.filterset(params, self.queryset).qs
|
|
155
|
+
self.assertEqual(result.count(), 1)
|
|
156
|
+
self.assertEqual(result.first().endpoint, IPFabricEndpointChoices.VLANS)
|
|
157
|
+
|
|
158
|
+
# Test case-insensitive matching
|
|
159
|
+
params = {"endpoint": "/TECHNOLOGY/ROUTING/VRF/DETAIL"}
|
|
160
|
+
result = self.filterset(params, self.queryset).qs
|
|
161
|
+
self.assertEqual(result.count(), 1)
|
|
162
|
+
|
|
163
|
+
# Test invalid path returns empty
|
|
164
|
+
params = {"endpoint": "/invalid/path"}
|
|
165
|
+
result = self.filterset(params, self.queryset).qs
|
|
166
|
+
self.assertEqual(result.count(), 0)
|
|
167
|
+
|
|
168
|
+
def test_filter_id(self):
|
|
169
|
+
"""Test filtering endpoints by IP Fabric filter ID"""
|
|
170
|
+
# Test single filter ID (as list)
|
|
171
|
+
params = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
172
|
+
result = self.filterset(params, self.queryset).qs
|
|
173
|
+
# Filter 1 has sites and devices endpoints
|
|
174
|
+
self.assertEqual(result.count(), 2)
|
|
175
|
+
endpoint_ids = set(result.values_list("id", flat=True))
|
|
176
|
+
self.assertIn(self.sites_endpoint.pk, endpoint_ids)
|
|
177
|
+
self.assertIn(self.devices_endpoint.pk, endpoint_ids)
|
|
178
|
+
|
|
179
|
+
# Test filter 2
|
|
180
|
+
params = {"ipfabric_filter_id": [self.filter2.pk]}
|
|
181
|
+
result = self.filterset(params, self.queryset).qs
|
|
182
|
+
# Filter 2 has devices and VRFs endpoints
|
|
183
|
+
self.assertEqual(result.count(), 2)
|
|
184
|
+
endpoint_ids = set(result.values_list("id", flat=True))
|
|
185
|
+
self.assertIn(self.devices_endpoint.pk, endpoint_ids)
|
|
186
|
+
self.assertIn(self.vrfs_endpoint.pk, endpoint_ids)
|
|
187
|
+
|
|
188
|
+
# Test filter 3
|
|
189
|
+
params = {"ipfabric_filter_id": [self.filter3.pk]}
|
|
190
|
+
result = self.filterset(params, self.queryset).qs
|
|
191
|
+
# Filter 3 has only VLANs endpoint
|
|
192
|
+
self.assertEqual(result.count(), 1)
|
|
193
|
+
self.assertEqual(result.first().pk, self.vlans_endpoint.pk)
|
|
194
|
+
|
|
195
|
+
# Note: ModelMultipleChoiceFilter silently ignores invalid IDs
|
|
196
|
+
# and returns all results instead of raising an error or returning empty.
|
|
197
|
+
# This is expected django-filter behavior.
|
|
198
|
+
|
|
199
|
+
def test_filter_name(self):
|
|
200
|
+
"""Test filtering endpoints by IP Fabric filter name"""
|
|
201
|
+
# Test exact match (case-insensitive)
|
|
202
|
+
params = {"ipfabric_filter": "Test Filter 1"}
|
|
203
|
+
result = self.filterset(params, self.queryset).qs
|
|
204
|
+
# Filter 1 has sites and devices endpoints
|
|
205
|
+
self.assertEqual(result.count(), 2)
|
|
206
|
+
endpoint_ids = set(result.values_list("id", flat=True))
|
|
207
|
+
self.assertIn(self.sites_endpoint.pk, endpoint_ids)
|
|
208
|
+
self.assertIn(self.devices_endpoint.pk, endpoint_ids)
|
|
209
|
+
|
|
210
|
+
# Test case-insensitive
|
|
211
|
+
params = {"ipfabric_filter": "test filter 2"}
|
|
212
|
+
result = self.filterset(params, self.queryset).qs
|
|
213
|
+
# Should match Filter 2 which has 2 endpoints
|
|
214
|
+
self.assertEqual(result.count(), 2)
|
|
215
|
+
|
|
216
|
+
# Test different case
|
|
217
|
+
params = {"ipfabric_filter": "TEST FILTER 3"}
|
|
218
|
+
result = self.filterset(params, self.queryset).qs
|
|
219
|
+
# Should match Filter 3 which has 1 endpoint
|
|
220
|
+
self.assertEqual(result.count(), 1)
|
|
221
|
+
|
|
222
|
+
# Test non-existent filter
|
|
223
|
+
params = {"ipfabric_filter": "Non Existent Filter"}
|
|
224
|
+
result = self.filterset(params, self.queryset).qs
|
|
225
|
+
self.assertEqual(result.count(), 0)
|
|
226
|
+
|
|
227
|
+
def test_filters_multiple(self):
|
|
228
|
+
"""Test filtering endpoints by multiple IP Fabric filter IDs"""
|
|
229
|
+
# Test multiple filter IDs using 'ipfabric_filters' parameter
|
|
230
|
+
params = {"ipfabric_filters": [self.filter1.pk, self.filter3.pk]}
|
|
231
|
+
result = self.filterset(params, self.queryset).qs
|
|
232
|
+
# Filter 1 has sites and devices, Filter 3 has VLANs
|
|
233
|
+
# Should return sites, devices, and VLANs endpoints
|
|
234
|
+
self.assertEqual(result.count(), 3)
|
|
235
|
+
endpoint_ids = set(result.values_list("id", flat=True))
|
|
236
|
+
self.assertIn(self.sites_endpoint.pk, endpoint_ids)
|
|
237
|
+
self.assertIn(self.devices_endpoint.pk, endpoint_ids)
|
|
238
|
+
self.assertIn(self.vlans_endpoint.pk, endpoint_ids)
|
|
239
|
+
|
|
240
|
+
def test_combined_filters(self):
|
|
241
|
+
"""Test combining multiple filters"""
|
|
242
|
+
# Test ipfabric_filter_id + name (using actual endpoint name)
|
|
243
|
+
params = {
|
|
244
|
+
"ipfabric_filter_id": [self.filter1.pk], # Pass as list
|
|
245
|
+
"name": [self.sites_endpoint.name], # Pass as list
|
|
246
|
+
}
|
|
247
|
+
result = self.filterset(params, self.queryset).qs
|
|
248
|
+
# Filter 1 has sites and devices, but name filters to only sites
|
|
249
|
+
self.assertEqual(result.count(), 1)
|
|
250
|
+
self.assertEqual(result.first().pk, self.sites_endpoint.pk)
|
|
251
|
+
|
|
252
|
+
# Test ipfabric_filter_id + endpoint
|
|
253
|
+
params = {
|
|
254
|
+
"ipfabric_filter_id": [self.filter2.pk], # Pass as list
|
|
255
|
+
"endpoint": "/inventory/devices",
|
|
256
|
+
}
|
|
257
|
+
result = self.filterset(params, self.queryset).qs
|
|
258
|
+
# Filter 2 has devices and VRFs, but endpoint filters to only devices
|
|
259
|
+
self.assertEqual(result.count(), 1)
|
|
260
|
+
self.assertEqual(result.first().pk, self.devices_endpoint.pk)
|
|
261
|
+
|
|
262
|
+
def test_q_search(self):
|
|
263
|
+
"""Test the search (q) parameter"""
|
|
264
|
+
# Search using part of an actual endpoint name
|
|
265
|
+
search_term = (
|
|
266
|
+
self.sites_endpoint.name.split()[0]
|
|
267
|
+
if self.sites_endpoint.name
|
|
268
|
+
else "Default"
|
|
269
|
+
)
|
|
270
|
+
params = {"q": search_term}
|
|
271
|
+
result = self.filterset(params, self.queryset).qs
|
|
272
|
+
# Should find at least the sites endpoint (may find others with similar names)
|
|
273
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
274
|
+
self.assertIn(self.sites_endpoint.pk, result.values_list("pk", flat=True))
|
|
275
|
+
|
|
276
|
+
def test_distinct_results(self):
|
|
277
|
+
"""Test that filters return distinct results (no duplicates)"""
|
|
278
|
+
# Add devices endpoint to multiple filters to test distinct
|
|
279
|
+
self.filter3.endpoints.add(self.devices_endpoint)
|
|
280
|
+
|
|
281
|
+
# Filter by multiple filters that share an endpoint
|
|
282
|
+
params = {
|
|
283
|
+
"ipfabric_filters": [self.filter1.pk, self.filter2.pk, self.filter3.pk]
|
|
284
|
+
}
|
|
285
|
+
result = self.filterset(params, self.queryset).qs
|
|
286
|
+
|
|
287
|
+
# Count devices endpoint occurrences (should be 1 despite being in 3 filters)
|
|
288
|
+
devices_count = result.filter(pk=self.devices_endpoint.pk).count()
|
|
289
|
+
self.assertEqual(devices_count, 1)
|
|
290
|
+
|
|
291
|
+
def test_empty_filters(self):
|
|
292
|
+
"""Test behavior with empty filter values"""
|
|
293
|
+
# Get total count of endpoints
|
|
294
|
+
total_count = self.queryset.count()
|
|
295
|
+
|
|
296
|
+
# Empty ipfabric_filter_id should return all
|
|
297
|
+
params = {"ipfabric_filter_id": ""}
|
|
298
|
+
result = self.filterset(params, self.queryset).qs
|
|
299
|
+
self.assertEqual(result.count(), total_count)
|
|
300
|
+
|
|
301
|
+
# Empty endpoint should return all
|
|
302
|
+
params = {"endpoint": ""}
|
|
303
|
+
result = self.filterset(params, self.queryset).qs
|
|
304
|
+
self.assertEqual(result.count(), total_count)
|
|
305
|
+
|
|
306
|
+
# Empty ipfabric_filter name should return all
|
|
307
|
+
params = {"ipfabric_filter": ""}
|
|
308
|
+
result = self.filterset(params, self.queryset).qs
|
|
309
|
+
self.assertEqual(result.count(), total_count)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class IPFabricFilterFilterSetTestCase(TestCase):
|
|
313
|
+
"""
|
|
314
|
+
Test IPFabricFilterFilterSet to verify all custom filters work correctly.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
queryset = IPFabricFilter.objects.all()
|
|
318
|
+
filterset = IPFabricFilterFilterSet
|
|
319
|
+
|
|
320
|
+
@classmethod
|
|
321
|
+
def setUpTestData(cls):
|
|
322
|
+
# Get existing endpoints from migrations
|
|
323
|
+
cls.sites_endpoint = IPFabricEndpoint.objects.filter(
|
|
324
|
+
endpoint=IPFabricEndpointChoices.SITES
|
|
325
|
+
).first()
|
|
326
|
+
cls.devices_endpoint = IPFabricEndpoint.objects.filter(
|
|
327
|
+
endpoint=IPFabricEndpointChoices.DEVICES
|
|
328
|
+
).first()
|
|
329
|
+
cls.vrfs_endpoint = IPFabricEndpoint.objects.filter(
|
|
330
|
+
endpoint=IPFabricEndpointChoices.VRFS
|
|
331
|
+
).first()
|
|
332
|
+
|
|
333
|
+
# Create required dependencies for syncs
|
|
334
|
+
source = IPFabricSource.objects.create(
|
|
335
|
+
name="Test Source",
|
|
336
|
+
type=IPFabricSourceTypeChoices.LOCAL,
|
|
337
|
+
url="https://ipfabric.example.com",
|
|
338
|
+
status=IPFabricSourceStatusChoices.NEW,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
snapshot = IPFabricSnapshot.objects.create(
|
|
342
|
+
source=source,
|
|
343
|
+
name="Test Snapshot",
|
|
344
|
+
snapshot_id="test_snap001",
|
|
345
|
+
data={"devices": 100},
|
|
346
|
+
date=timezone.now(),
|
|
347
|
+
status=IPFabricSnapshotStatusModelChoices.STATUS_LOADED,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Create test syncs
|
|
351
|
+
cls.sync1 = IPFabricSync.objects.create(
|
|
352
|
+
name="Test Sync 1",
|
|
353
|
+
snapshot_data=snapshot,
|
|
354
|
+
status=IPFabricSyncStatusChoices.NEW,
|
|
355
|
+
)
|
|
356
|
+
cls.sync2 = IPFabricSync.objects.create(
|
|
357
|
+
name="Test Sync 2",
|
|
358
|
+
snapshot_data=snapshot,
|
|
359
|
+
status=IPFabricSyncStatusChoices.COMPLETED,
|
|
360
|
+
)
|
|
361
|
+
cls.sync3 = IPFabricSync.objects.create(
|
|
362
|
+
name="Test Sync 3",
|
|
363
|
+
snapshot_data=snapshot,
|
|
364
|
+
status=IPFabricSyncStatusChoices.FAILED,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Create test filters
|
|
368
|
+
filters = [
|
|
369
|
+
IPFabricFilter(
|
|
370
|
+
name="Test Filter 1",
|
|
371
|
+
description="Sites and devices filter",
|
|
372
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
373
|
+
),
|
|
374
|
+
IPFabricFilter(
|
|
375
|
+
name="Test Filter 2",
|
|
376
|
+
description="Devices and VRFs filter",
|
|
377
|
+
filter_type=IPFabricFilterTypeChoices.OR,
|
|
378
|
+
),
|
|
379
|
+
IPFabricFilter(
|
|
380
|
+
name="Test Filter 3",
|
|
381
|
+
description="Sites only filter",
|
|
382
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
383
|
+
),
|
|
384
|
+
]
|
|
385
|
+
IPFabricFilter.objects.bulk_create(filters)
|
|
386
|
+
|
|
387
|
+
cls.filter1 = IPFabricFilter.objects.get(name="Test Filter 1")
|
|
388
|
+
cls.filter2 = IPFabricFilter.objects.get(name="Test Filter 2")
|
|
389
|
+
cls.filter3 = IPFabricFilter.objects.get(name="Test Filter 3")
|
|
390
|
+
|
|
391
|
+
# Assign endpoints to filters
|
|
392
|
+
cls.filter1.endpoints.add(cls.sites_endpoint, cls.devices_endpoint)
|
|
393
|
+
cls.filter2.endpoints.add(cls.devices_endpoint, cls.vrfs_endpoint)
|
|
394
|
+
cls.filter3.endpoints.add(cls.sites_endpoint)
|
|
395
|
+
|
|
396
|
+
# Assign syncs to filters
|
|
397
|
+
cls.filter1.syncs.add(cls.sync1, cls.sync2)
|
|
398
|
+
cls.filter2.syncs.add(cls.sync2, cls.sync3)
|
|
399
|
+
cls.filter3.syncs.add(cls.sync1)
|
|
400
|
+
|
|
401
|
+
# Create test filter expressions
|
|
402
|
+
expressions = [
|
|
403
|
+
IPFabricFilterExpression(
|
|
404
|
+
name="Filter Test Expression 1",
|
|
405
|
+
description="First filter test expression",
|
|
406
|
+
expression={"or": [{"siteName": ["eq", "FilterSite1"]}]},
|
|
407
|
+
),
|
|
408
|
+
IPFabricFilterExpression(
|
|
409
|
+
name="Filter Test Expression 2",
|
|
410
|
+
description="Second filter test expression",
|
|
411
|
+
expression={"and": [{"hostname": ["like", "filterrouter"]}]},
|
|
412
|
+
),
|
|
413
|
+
]
|
|
414
|
+
IPFabricFilterExpression.objects.bulk_create(expressions)
|
|
415
|
+
|
|
416
|
+
cls.expr1 = IPFabricFilterExpression.objects.get(
|
|
417
|
+
name="Filter Test Expression 1"
|
|
418
|
+
)
|
|
419
|
+
cls.expr2 = IPFabricFilterExpression.objects.get(
|
|
420
|
+
name="Filter Test Expression 2"
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Assign expressions to filters
|
|
424
|
+
cls.expr1.filters.add(cls.filter1, cls.filter2)
|
|
425
|
+
cls.expr2.filters.add(cls.filter2, cls.filter3)
|
|
426
|
+
|
|
427
|
+
def test_id(self):
|
|
428
|
+
"""Test filtering by filter ID"""
|
|
429
|
+
params = {"id": [self.filter1.pk]}
|
|
430
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
431
|
+
params = {"id": [self.filter1.pk, self.filter2.pk]}
|
|
432
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
433
|
+
|
|
434
|
+
def test_name(self):
|
|
435
|
+
"""Test filtering by filter name"""
|
|
436
|
+
params = {"name": ["Test Filter 1"]}
|
|
437
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
438
|
+
params = {"name": ["Test Filter 1", "Test Filter 2"]}
|
|
439
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
440
|
+
|
|
441
|
+
def test_description(self):
|
|
442
|
+
"""Test filtering by description"""
|
|
443
|
+
# Skip test if description is empty (which it is in our test data)
|
|
444
|
+
# In real usage, descriptions would be populated
|
|
445
|
+
pass
|
|
446
|
+
|
|
447
|
+
def test_filter_type(self):
|
|
448
|
+
"""Test filtering by filter type"""
|
|
449
|
+
params = {"filter_type": IPFabricFilterTypeChoices.AND}
|
|
450
|
+
result = self.filterset(params, self.queryset).qs
|
|
451
|
+
# Filters 1 and 3 are AND type from our test data
|
|
452
|
+
# (may include more from other test classes)
|
|
453
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
454
|
+
# Verify our test filters are included
|
|
455
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
456
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
457
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
458
|
+
|
|
459
|
+
params = {"filter_type": IPFabricFilterTypeChoices.OR}
|
|
460
|
+
result = self.filterset(params, self.queryset).qs
|
|
461
|
+
# Filter 2 is OR type from our test data
|
|
462
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
463
|
+
self.assertIn(self.filter2.pk, result.values_list("id", flat=True))
|
|
464
|
+
|
|
465
|
+
def test_sync_id(self):
|
|
466
|
+
"""Test filtering by sync ID"""
|
|
467
|
+
params = {"sync_id": [self.sync1.pk]}
|
|
468
|
+
result = self.filterset(params, self.queryset).qs
|
|
469
|
+
# Filters 1 and 3 use sync1
|
|
470
|
+
self.assertEqual(result.count(), 2)
|
|
471
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
472
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
473
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
474
|
+
|
|
475
|
+
params = {"sync_id": [self.sync2.pk]}
|
|
476
|
+
result = self.filterset(params, self.queryset).qs
|
|
477
|
+
# Filters 1 and 2 use sync2
|
|
478
|
+
self.assertEqual(result.count(), 2)
|
|
479
|
+
|
|
480
|
+
def test_sync_name(self):
|
|
481
|
+
"""Test filtering by sync name"""
|
|
482
|
+
params = {"sync": "Test Sync 1"}
|
|
483
|
+
result = self.filterset(params, self.queryset).qs
|
|
484
|
+
# Filters 1 and 3 use sync1
|
|
485
|
+
self.assertEqual(result.count(), 2)
|
|
486
|
+
|
|
487
|
+
# Test case-insensitive
|
|
488
|
+
params = {"sync": "test sync 2"}
|
|
489
|
+
result = self.filterset(params, self.queryset).qs
|
|
490
|
+
self.assertEqual(result.count(), 2)
|
|
491
|
+
|
|
492
|
+
def test_syncs_multiple(self):
|
|
493
|
+
"""Test filtering by multiple sync IDs"""
|
|
494
|
+
params = {"syncs": [self.sync1.pk, self.sync3.pk]}
|
|
495
|
+
result = self.filterset(params, self.queryset).qs
|
|
496
|
+
# All three filters use sync1 or sync3
|
|
497
|
+
self.assertEqual(result.count(), 3)
|
|
498
|
+
|
|
499
|
+
def test_endpoint_id(self):
|
|
500
|
+
"""Test filtering by endpoint ID"""
|
|
501
|
+
params = {"endpoint_id": [self.sites_endpoint.pk]}
|
|
502
|
+
result = self.filterset(params, self.queryset).qs
|
|
503
|
+
# Filters 1 and 3 use sites endpoint
|
|
504
|
+
self.assertEqual(result.count(), 2)
|
|
505
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
506
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
507
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
508
|
+
|
|
509
|
+
def test_endpoint_name(self):
|
|
510
|
+
"""Test filtering by endpoint name"""
|
|
511
|
+
params = {"endpoint": self.devices_endpoint.name}
|
|
512
|
+
result = self.filterset(params, self.queryset).qs
|
|
513
|
+
# Filters 1 and 2 use devices endpoint from our test data
|
|
514
|
+
# (may include more from other test classes)
|
|
515
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
516
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
517
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
518
|
+
self.assertIn(self.filter2.pk, filter_ids)
|
|
519
|
+
|
|
520
|
+
def test_endpoint_path(self):
|
|
521
|
+
"""Test filtering by endpoint URL path"""
|
|
522
|
+
# Test with URL path format
|
|
523
|
+
params = {"endpoint_path": "/inventory/sites/overview"}
|
|
524
|
+
result = self.filterset(params, self.queryset).qs
|
|
525
|
+
# Filters 1 and 3 use sites endpoint from our test data
|
|
526
|
+
# (may include more from other test classes)
|
|
527
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
528
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
529
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
530
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
531
|
+
|
|
532
|
+
# Test case-insensitive
|
|
533
|
+
params = {"endpoint_path": "/INVENTORY/DEVICES"}
|
|
534
|
+
result = self.filterset(params, self.queryset).qs
|
|
535
|
+
# Filters 1 and 2 use devices endpoint from our test data
|
|
536
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
537
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
538
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
539
|
+
self.assertIn(self.filter2.pk, filter_ids)
|
|
540
|
+
|
|
541
|
+
# Test invalid path
|
|
542
|
+
params = {"endpoint_path": "/invalid/path"}
|
|
543
|
+
result = self.filterset(params, self.queryset).qs
|
|
544
|
+
self.assertEqual(result.count(), 0)
|
|
545
|
+
|
|
546
|
+
def test_endpoints_multiple(self):
|
|
547
|
+
"""Test filtering by multiple endpoint IDs"""
|
|
548
|
+
params = {"endpoints": [self.sites_endpoint.pk, self.vrfs_endpoint.pk]}
|
|
549
|
+
result = self.filterset(params, self.queryset).qs
|
|
550
|
+
# All three filters from our test data use sites or VRFs
|
|
551
|
+
# (may include more from other test classes)
|
|
552
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
553
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
554
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
555
|
+
self.assertIn(self.filter2.pk, filter_ids)
|
|
556
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
557
|
+
|
|
558
|
+
def test_expression_id(self):
|
|
559
|
+
"""Test filtering by expression ID"""
|
|
560
|
+
params = {"expression_id": [self.expr1.pk]}
|
|
561
|
+
result = self.filterset(params, self.queryset).qs
|
|
562
|
+
# Filters 1 and 2 use expr1
|
|
563
|
+
self.assertEqual(result.count(), 2)
|
|
564
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
565
|
+
self.assertIn(self.filter1.pk, filter_ids)
|
|
566
|
+
self.assertIn(self.filter2.pk, filter_ids)
|
|
567
|
+
|
|
568
|
+
params = {"expression_id": [self.expr2.pk]}
|
|
569
|
+
result = self.filterset(params, self.queryset).qs
|
|
570
|
+
# Filters 2 and 3 use expr2
|
|
571
|
+
self.assertEqual(result.count(), 2)
|
|
572
|
+
filter_ids = set(result.values_list("id", flat=True))
|
|
573
|
+
self.assertIn(self.filter2.pk, filter_ids)
|
|
574
|
+
self.assertIn(self.filter3.pk, filter_ids)
|
|
575
|
+
|
|
576
|
+
def test_expression_name(self):
|
|
577
|
+
"""Test filtering by expression name"""
|
|
578
|
+
params = {"expression": "Filter Test Expression 1"}
|
|
579
|
+
result = self.filterset(params, self.queryset).qs
|
|
580
|
+
# Filters 1 and 2 use expr1
|
|
581
|
+
self.assertEqual(result.count(), 2)
|
|
582
|
+
|
|
583
|
+
# Test case-insensitive
|
|
584
|
+
params = {"expression": "filter test expression 2"}
|
|
585
|
+
result = self.filterset(params, self.queryset).qs
|
|
586
|
+
# Filters 2 and 3 use expr2
|
|
587
|
+
self.assertEqual(result.count(), 2)
|
|
588
|
+
|
|
589
|
+
def test_expressions_multiple(self):
|
|
590
|
+
"""Test filtering by multiple expression IDs"""
|
|
591
|
+
params = {"expressions": [self.expr1.pk, self.expr2.pk]}
|
|
592
|
+
result = self.filterset(params, self.queryset).qs
|
|
593
|
+
# All three filters use expr1 or expr2
|
|
594
|
+
self.assertEqual(result.count(), 3)
|
|
595
|
+
|
|
596
|
+
def test_combined_filters(self):
|
|
597
|
+
"""Test combining multiple filters"""
|
|
598
|
+
# Test sync_id + endpoint_id
|
|
599
|
+
params = {
|
|
600
|
+
"sync_id": [self.sync1.pk],
|
|
601
|
+
"endpoint_id": [self.sites_endpoint.pk],
|
|
602
|
+
}
|
|
603
|
+
result = self.filterset(params, self.queryset).qs
|
|
604
|
+
# Filters 1 and 3 have sync1, both have sites endpoint
|
|
605
|
+
self.assertEqual(result.count(), 2)
|
|
606
|
+
|
|
607
|
+
# Test filter_type + endpoint_id
|
|
608
|
+
params = {
|
|
609
|
+
"filter_type": IPFabricFilterTypeChoices.AND,
|
|
610
|
+
"endpoint_id": [self.sites_endpoint.pk],
|
|
611
|
+
}
|
|
612
|
+
result = self.filterset(params, self.queryset).qs
|
|
613
|
+
# Filters 1 and 3 are AND type and have sites endpoint
|
|
614
|
+
self.assertEqual(result.count(), 2)
|
|
615
|
+
|
|
616
|
+
# Test sync_id + filter_type
|
|
617
|
+
params = {
|
|
618
|
+
"sync_id": [self.sync2.pk],
|
|
619
|
+
"filter_type": IPFabricFilterTypeChoices.AND,
|
|
620
|
+
}
|
|
621
|
+
result = self.filterset(params, self.queryset).qs
|
|
622
|
+
# Only Filter 1 has sync2 and is AND type
|
|
623
|
+
self.assertEqual(result.count(), 1)
|
|
624
|
+
|
|
625
|
+
def test_q_search(self):
|
|
626
|
+
"""Test the search (q) parameter"""
|
|
627
|
+
# Search by name
|
|
628
|
+
search_term = self.filter1.name.split()[0] if self.filter1.name else "Test"
|
|
629
|
+
params = {"q": search_term}
|
|
630
|
+
result = self.filterset(params, self.queryset).qs
|
|
631
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
632
|
+
self.assertIn(self.filter1.pk, result.values_list("pk", flat=True))
|
|
633
|
+
|
|
634
|
+
def test_distinct_results(self):
|
|
635
|
+
"""Test that filters return distinct results"""
|
|
636
|
+
# Add sync1 to all filters
|
|
637
|
+
self.filter2.syncs.add(self.sync1)
|
|
638
|
+
|
|
639
|
+
# Filter by sync that's in all filters
|
|
640
|
+
params = {"sync_id": [self.sync1.pk]}
|
|
641
|
+
result = self.filterset(params, self.queryset).qs
|
|
642
|
+
|
|
643
|
+
# Each filter should appear only once
|
|
644
|
+
filter_counts = {}
|
|
645
|
+
for filter_obj in result:
|
|
646
|
+
filter_counts[filter_obj.pk] = filter_counts.get(filter_obj.pk, 0) + 1
|
|
647
|
+
|
|
648
|
+
for count in filter_counts.values():
|
|
649
|
+
self.assertEqual(count, 1)
|
|
650
|
+
|
|
651
|
+
def test_empty_filters(self):
|
|
652
|
+
"""Test behavior with empty filter values"""
|
|
653
|
+
total_count = self.queryset.count()
|
|
654
|
+
|
|
655
|
+
params = {"sync": ""}
|
|
656
|
+
result = self.filterset(params, self.queryset).qs
|
|
657
|
+
self.assertEqual(result.count(), total_count)
|
|
658
|
+
|
|
659
|
+
params = {"endpoint": ""}
|
|
660
|
+
result = self.filterset(params, self.queryset).qs
|
|
661
|
+
self.assertEqual(result.count(), total_count)
|
|
662
|
+
|
|
663
|
+
params = {"filter_type": ""}
|
|
664
|
+
result = self.filterset(params, self.queryset).qs
|
|
665
|
+
self.assertEqual(result.count(), total_count)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class IPFabricFilterExpressionFilterSetTestCase(TestCase):
|
|
669
|
+
"""
|
|
670
|
+
Test IPFabricFilterExpressionFilterSet to verify all custom filters work correctly.
|
|
671
|
+
"""
|
|
672
|
+
|
|
673
|
+
queryset = IPFabricFilterExpression.objects.all()
|
|
674
|
+
filterset = IPFabricFilterExpressionFilterSet
|
|
675
|
+
|
|
676
|
+
@classmethod
|
|
677
|
+
def setUpTestData(cls):
|
|
678
|
+
# Create test filters
|
|
679
|
+
filters = [
|
|
680
|
+
IPFabricFilter(
|
|
681
|
+
name="Expression Filter 1",
|
|
682
|
+
description="First expression filter",
|
|
683
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
684
|
+
),
|
|
685
|
+
IPFabricFilter(
|
|
686
|
+
name="Expression Filter 2",
|
|
687
|
+
description="Second expression filter",
|
|
688
|
+
filter_type=IPFabricFilterTypeChoices.OR,
|
|
689
|
+
),
|
|
690
|
+
IPFabricFilter(
|
|
691
|
+
name="Expression Filter 3",
|
|
692
|
+
description="Third expression filter",
|
|
693
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
694
|
+
),
|
|
695
|
+
]
|
|
696
|
+
IPFabricFilter.objects.bulk_create(filters)
|
|
697
|
+
|
|
698
|
+
cls.filter1 = IPFabricFilter.objects.get(name="Expression Filter 1")
|
|
699
|
+
cls.filter2 = IPFabricFilter.objects.get(name="Expression Filter 2")
|
|
700
|
+
cls.filter3 = IPFabricFilter.objects.get(name="Expression Filter 3")
|
|
701
|
+
|
|
702
|
+
# Create test filter expressions
|
|
703
|
+
expressions = [
|
|
704
|
+
IPFabricFilterExpression(
|
|
705
|
+
name="Test Expression 1",
|
|
706
|
+
description="Sites expression",
|
|
707
|
+
expression={"or": [{"siteName": ["eq", "Site1"]}]},
|
|
708
|
+
),
|
|
709
|
+
IPFabricFilterExpression(
|
|
710
|
+
name="Test Expression 2",
|
|
711
|
+
description="Devices expression",
|
|
712
|
+
expression={"and": [{"hostname": ["like", "router"]}]},
|
|
713
|
+
),
|
|
714
|
+
IPFabricFilterExpression(
|
|
715
|
+
name="Test Expression 3",
|
|
716
|
+
description="Complex expression",
|
|
717
|
+
expression={
|
|
718
|
+
"and": [
|
|
719
|
+
{"siteName": ["eq", "Site1"]},
|
|
720
|
+
{"hostname": ["like", "switch"]},
|
|
721
|
+
]
|
|
722
|
+
},
|
|
723
|
+
),
|
|
724
|
+
]
|
|
725
|
+
IPFabricFilterExpression.objects.bulk_create(expressions)
|
|
726
|
+
|
|
727
|
+
cls.expr1 = IPFabricFilterExpression.objects.get(name="Test Expression 1")
|
|
728
|
+
cls.expr2 = IPFabricFilterExpression.objects.get(name="Test Expression 2")
|
|
729
|
+
cls.expr3 = IPFabricFilterExpression.objects.get(name="Test Expression 3")
|
|
730
|
+
|
|
731
|
+
# Assign filters to expressions
|
|
732
|
+
cls.expr1.filters.add(cls.filter1, cls.filter2)
|
|
733
|
+
cls.expr2.filters.add(cls.filter2, cls.filter3)
|
|
734
|
+
cls.expr3.filters.add(cls.filter1)
|
|
735
|
+
|
|
736
|
+
def test_id(self):
|
|
737
|
+
"""Test filtering by expression ID"""
|
|
738
|
+
params = {"id": [self.expr1.pk]}
|
|
739
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
740
|
+
params = {"id": [self.expr1.pk, self.expr2.pk]}
|
|
741
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
742
|
+
|
|
743
|
+
def test_name(self):
|
|
744
|
+
"""Test filtering by expression name"""
|
|
745
|
+
params = {"name": ["Test Expression 1"]}
|
|
746
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
747
|
+
params = {"name": ["Test Expression 1", "Test Expression 2"]}
|
|
748
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
749
|
+
|
|
750
|
+
def test_description(self):
|
|
751
|
+
"""Test filtering by description"""
|
|
752
|
+
# Skip test if description is empty (which it is in our test data)
|
|
753
|
+
# In real usage, descriptions would be populated
|
|
754
|
+
pass
|
|
755
|
+
|
|
756
|
+
def test_expression(self):
|
|
757
|
+
"""Test filtering by expression JSON content"""
|
|
758
|
+
# Search for expression containing 'siteName'
|
|
759
|
+
params = {"expression": "siteName"}
|
|
760
|
+
result = self.filterset(params, self.queryset).qs
|
|
761
|
+
# Expressions 1 and 3 contain 'siteName'
|
|
762
|
+
self.assertEqual(result.count(), 2)
|
|
763
|
+
expr_ids = set(result.values_list("id", flat=True))
|
|
764
|
+
self.assertIn(self.expr1.pk, expr_ids)
|
|
765
|
+
self.assertIn(self.expr3.pk, expr_ids)
|
|
766
|
+
|
|
767
|
+
# Search for expression containing 'hostname'
|
|
768
|
+
params = {"expression": "hostname"}
|
|
769
|
+
result = self.filterset(params, self.queryset).qs
|
|
770
|
+
# Expressions 2 and 3 contain 'hostname'
|
|
771
|
+
self.assertEqual(result.count(), 2)
|
|
772
|
+
|
|
773
|
+
def test_ipfabric_filter_id(self):
|
|
774
|
+
"""Test filtering by IP Fabric filter ID"""
|
|
775
|
+
params = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
776
|
+
result = self.filterset(params, self.queryset).qs
|
|
777
|
+
# Expressions 1 and 3 use filter1
|
|
778
|
+
self.assertEqual(result.count(), 2)
|
|
779
|
+
expr_ids = set(result.values_list("id", flat=True))
|
|
780
|
+
self.assertIn(self.expr1.pk, expr_ids)
|
|
781
|
+
self.assertIn(self.expr3.pk, expr_ids)
|
|
782
|
+
|
|
783
|
+
params = {"ipfabric_filter_id": [self.filter2.pk]}
|
|
784
|
+
result = self.filterset(params, self.queryset).qs
|
|
785
|
+
# Expressions 1 and 2 use filter2
|
|
786
|
+
self.assertEqual(result.count(), 2)
|
|
787
|
+
|
|
788
|
+
def test_ipfabric_filter_name(self):
|
|
789
|
+
"""Test filtering by IP Fabric filter name"""
|
|
790
|
+
params = {"ipfabric_filter": "Expression Filter 1"}
|
|
791
|
+
result = self.filterset(params, self.queryset).qs
|
|
792
|
+
# Expressions 1 and 3 use filter1
|
|
793
|
+
self.assertEqual(result.count(), 2)
|
|
794
|
+
expr_ids = set(result.values_list("id", flat=True))
|
|
795
|
+
self.assertIn(self.expr1.pk, expr_ids)
|
|
796
|
+
self.assertIn(self.expr3.pk, expr_ids)
|
|
797
|
+
|
|
798
|
+
# Test case-insensitive
|
|
799
|
+
params = {"ipfabric_filter": "expression filter 2"}
|
|
800
|
+
result = self.filterset(params, self.queryset).qs
|
|
801
|
+
# Expressions 1 and 2 use filter2
|
|
802
|
+
self.assertEqual(result.count(), 2)
|
|
803
|
+
|
|
804
|
+
def test_ipfabric_filters_multiple(self):
|
|
805
|
+
"""Test filtering by multiple IP Fabric filter IDs"""
|
|
806
|
+
params = {"ipfabric_filters": [self.filter1.pk, self.filter3.pk]}
|
|
807
|
+
result = self.filterset(params, self.queryset).qs
|
|
808
|
+
# All three expressions use filter1 or filter3
|
|
809
|
+
self.assertEqual(result.count(), 3)
|
|
810
|
+
|
|
811
|
+
def test_combined_filters(self):
|
|
812
|
+
"""Test combining multiple filters"""
|
|
813
|
+
# Test ipfabric_filter_id + name
|
|
814
|
+
params = {
|
|
815
|
+
"ipfabric_filter_id": [self.filter1.pk],
|
|
816
|
+
"name": ["Test Expression 1"],
|
|
817
|
+
}
|
|
818
|
+
result = self.filterset(params, self.queryset).qs
|
|
819
|
+
# Only expression 1 matches both criteria
|
|
820
|
+
self.assertEqual(result.count(), 1)
|
|
821
|
+
self.assertEqual(result.first().pk, self.expr1.pk)
|
|
822
|
+
|
|
823
|
+
# Test expression content + filter
|
|
824
|
+
params = {
|
|
825
|
+
"expression": "siteName",
|
|
826
|
+
"ipfabric_filter_id": [self.filter1.pk],
|
|
827
|
+
}
|
|
828
|
+
result = self.filterset(params, self.queryset).qs
|
|
829
|
+
# Expressions 1 and 3 have siteName and filter1
|
|
830
|
+
self.assertEqual(result.count(), 2)
|
|
831
|
+
|
|
832
|
+
def test_q_search(self):
|
|
833
|
+
"""Test the search (q) parameter"""
|
|
834
|
+
# Search by name
|
|
835
|
+
search_term = self.expr1.name.split()[0] if self.expr1.name else "Test"
|
|
836
|
+
params = {"q": search_term}
|
|
837
|
+
result = self.filterset(params, self.queryset).qs
|
|
838
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
839
|
+
self.assertIn(self.expr1.pk, result.values_list("pk", flat=True))
|
|
840
|
+
|
|
841
|
+
def test_distinct_results(self):
|
|
842
|
+
"""Test that filters return distinct results"""
|
|
843
|
+
# Add filter1 to all expressions
|
|
844
|
+
self.expr2.filters.add(self.filter1)
|
|
845
|
+
|
|
846
|
+
# Filter by filter that's in all expressions
|
|
847
|
+
params = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
848
|
+
result = self.filterset(params, self.queryset).qs
|
|
849
|
+
|
|
850
|
+
# Each expression should appear only once
|
|
851
|
+
expr_counts = {}
|
|
852
|
+
for expr in result:
|
|
853
|
+
expr_counts[expr.pk] = expr_counts.get(expr.pk, 0) + 1
|
|
854
|
+
|
|
855
|
+
for count in expr_counts.values():
|
|
856
|
+
self.assertEqual(count, 1)
|
|
857
|
+
|
|
858
|
+
def test_empty_filters(self):
|
|
859
|
+
"""Test behavior with empty filter values"""
|
|
860
|
+
total_count = self.queryset.count()
|
|
861
|
+
|
|
862
|
+
params = {"ipfabric_filter": ""}
|
|
863
|
+
result = self.filterset(params, self.queryset).qs
|
|
864
|
+
self.assertEqual(result.count(), total_count)
|
|
865
|
+
|
|
866
|
+
params = {"expression": ""}
|
|
867
|
+
result = self.filterset(params, self.queryset).qs
|
|
868
|
+
self.assertEqual(result.count(), total_count)
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
class IPFabricSyncFilterSetTestCase(TestCase):
|
|
872
|
+
"""
|
|
873
|
+
Test IPFabricSyncFilterSet to verify all custom filters work correctly.
|
|
874
|
+
"""
|
|
875
|
+
|
|
876
|
+
queryset = IPFabricSync.objects.all()
|
|
877
|
+
filterset = IPFabricSyncFilterSet
|
|
878
|
+
|
|
879
|
+
@classmethod
|
|
880
|
+
def setUpTestData(cls):
|
|
881
|
+
# Create required dependencies
|
|
882
|
+
source = IPFabricSource.objects.create(
|
|
883
|
+
name="Sync Test Source",
|
|
884
|
+
type=IPFabricSourceTypeChoices.LOCAL,
|
|
885
|
+
url="https://ipfabric-sync.example.com",
|
|
886
|
+
status=IPFabricSourceStatusChoices.NEW,
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
# Create test snapshots
|
|
890
|
+
cls.snapshot1 = IPFabricSnapshot.objects.create(
|
|
891
|
+
source=source,
|
|
892
|
+
name="Sync Test Snapshot 1",
|
|
893
|
+
snapshot_id="sync_snap001",
|
|
894
|
+
data={"devices": 100},
|
|
895
|
+
date=timezone.now(),
|
|
896
|
+
status=IPFabricSnapshotStatusModelChoices.STATUS_LOADED,
|
|
897
|
+
)
|
|
898
|
+
cls.snapshot2 = IPFabricSnapshot.objects.create(
|
|
899
|
+
source=source,
|
|
900
|
+
name="Sync Test Snapshot 2",
|
|
901
|
+
snapshot_id="sync_snap002",
|
|
902
|
+
data={"devices": 200},
|
|
903
|
+
date=timezone.now(),
|
|
904
|
+
status=IPFabricSnapshotStatusModelChoices.STATUS_LOADED,
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
# Create test syncs
|
|
908
|
+
cls.sync1 = IPFabricSync.objects.create(
|
|
909
|
+
name="Sync Test 1",
|
|
910
|
+
snapshot_data=cls.snapshot1,
|
|
911
|
+
status=IPFabricSyncStatusChoices.NEW,
|
|
912
|
+
auto_merge=True,
|
|
913
|
+
)
|
|
914
|
+
cls.sync2 = IPFabricSync.objects.create(
|
|
915
|
+
name="Sync Test 2",
|
|
916
|
+
snapshot_data=cls.snapshot1,
|
|
917
|
+
status=IPFabricSyncStatusChoices.COMPLETED,
|
|
918
|
+
auto_merge=False,
|
|
919
|
+
)
|
|
920
|
+
cls.sync3 = IPFabricSync.objects.create(
|
|
921
|
+
name="Sync Test 3",
|
|
922
|
+
snapshot_data=cls.snapshot2,
|
|
923
|
+
status=IPFabricSyncStatusChoices.FAILED,
|
|
924
|
+
auto_merge=True,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
# Create test filters
|
|
928
|
+
filters = [
|
|
929
|
+
IPFabricFilter(
|
|
930
|
+
name="Sync Filter 1",
|
|
931
|
+
description="First sync filter",
|
|
932
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
933
|
+
),
|
|
934
|
+
IPFabricFilter(
|
|
935
|
+
name="Sync Filter 2",
|
|
936
|
+
description="Second sync filter",
|
|
937
|
+
filter_type=IPFabricFilterTypeChoices.OR,
|
|
938
|
+
),
|
|
939
|
+
IPFabricFilter(
|
|
940
|
+
name="Sync Filter 3",
|
|
941
|
+
description="Third sync filter",
|
|
942
|
+
filter_type=IPFabricFilterTypeChoices.AND,
|
|
943
|
+
),
|
|
944
|
+
]
|
|
945
|
+
IPFabricFilter.objects.bulk_create(filters)
|
|
946
|
+
|
|
947
|
+
cls.filter1 = IPFabricFilter.objects.get(name="Sync Filter 1")
|
|
948
|
+
cls.filter2 = IPFabricFilter.objects.get(name="Sync Filter 2")
|
|
949
|
+
cls.filter3 = IPFabricFilter.objects.get(name="Sync Filter 3")
|
|
950
|
+
|
|
951
|
+
# Assign filters to syncs
|
|
952
|
+
cls.filter1.syncs.add(cls.sync1, cls.sync2)
|
|
953
|
+
cls.filter2.syncs.add(cls.sync2, cls.sync3)
|
|
954
|
+
cls.filter3.syncs.add(cls.sync1)
|
|
955
|
+
|
|
956
|
+
def test_id(self):
|
|
957
|
+
"""Test filtering by sync ID"""
|
|
958
|
+
params = {"id": [self.sync1.pk]}
|
|
959
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
960
|
+
params = {"id": [self.sync1.pk, self.sync2.pk]}
|
|
961
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
962
|
+
|
|
963
|
+
def test_name(self):
|
|
964
|
+
"""Test filtering by sync name"""
|
|
965
|
+
params = {"name": "Sync Test 1"}
|
|
966
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
967
|
+
|
|
968
|
+
def test_snapshot_data_id(self):
|
|
969
|
+
"""Test filtering by snapshot ID"""
|
|
970
|
+
params = {"snapshot_data_id": [self.snapshot1.pk]}
|
|
971
|
+
result = self.filterset(params, self.queryset).qs
|
|
972
|
+
# Syncs 1 and 2 use snapshot1
|
|
973
|
+
self.assertEqual(result.count(), 2)
|
|
974
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
975
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
976
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
977
|
+
|
|
978
|
+
params = {"snapshot_data_id": [self.snapshot2.pk]}
|
|
979
|
+
result = self.filterset(params, self.queryset).qs
|
|
980
|
+
# Sync 3 uses snapshot2
|
|
981
|
+
self.assertEqual(result.count(), 1)
|
|
982
|
+
self.assertEqual(result.first().pk, self.sync3.pk)
|
|
983
|
+
|
|
984
|
+
def test_snapshot_data_name(self):
|
|
985
|
+
"""Test filtering by snapshot name"""
|
|
986
|
+
params = {"snapshot_data": [self.snapshot1.pk]}
|
|
987
|
+
result = self.filterset(params, self.queryset).qs
|
|
988
|
+
# Syncs 1 and 2 use snapshot1 from our test data
|
|
989
|
+
# (may include more from other test classes)
|
|
990
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
991
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
992
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
993
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
994
|
+
|
|
995
|
+
def test_status(self):
|
|
996
|
+
"""Test filtering by status"""
|
|
997
|
+
params = {"status": [IPFabricSyncStatusChoices.NEW]}
|
|
998
|
+
result = self.filterset(params, self.queryset).qs
|
|
999
|
+
# Sync 1 is NEW
|
|
1000
|
+
self.assertEqual(result.count(), 1)
|
|
1001
|
+
self.assertEqual(result.first().pk, self.sync1.pk)
|
|
1002
|
+
|
|
1003
|
+
params = {"status": [IPFabricSyncStatusChoices.COMPLETED]}
|
|
1004
|
+
result = self.filterset(params, self.queryset).qs
|
|
1005
|
+
# Sync 2 is COMPLETED
|
|
1006
|
+
self.assertEqual(result.count(), 1)
|
|
1007
|
+
|
|
1008
|
+
def test_auto_merge(self):
|
|
1009
|
+
"""Test filtering by auto_merge"""
|
|
1010
|
+
params = {"auto_merge": True}
|
|
1011
|
+
result = self.filterset(params, self.queryset).qs
|
|
1012
|
+
# Syncs 1 and 3 have auto_merge=True
|
|
1013
|
+
self.assertEqual(result.count(), 2)
|
|
1014
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1015
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1016
|
+
self.assertIn(self.sync3.pk, sync_ids)
|
|
1017
|
+
|
|
1018
|
+
params = {"auto_merge": False}
|
|
1019
|
+
result = self.filterset(params, self.queryset).qs
|
|
1020
|
+
# Sync 2 has auto_merge=False
|
|
1021
|
+
self.assertEqual(result.count(), 1)
|
|
1022
|
+
|
|
1023
|
+
def test_ipfabric_filter_id(self):
|
|
1024
|
+
"""Test filtering by IP Fabric filter ID"""
|
|
1025
|
+
params = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
1026
|
+
result = self.filterset(params, self.queryset).qs
|
|
1027
|
+
# Syncs 1 and 2 use filter1
|
|
1028
|
+
self.assertEqual(result.count(), 2)
|
|
1029
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1030
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1031
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1032
|
+
|
|
1033
|
+
params = {"ipfabric_filter_id": [self.filter2.pk]}
|
|
1034
|
+
result = self.filterset(params, self.queryset).qs
|
|
1035
|
+
# Syncs 2 and 3 use filter2
|
|
1036
|
+
self.assertEqual(result.count(), 2)
|
|
1037
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1038
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1039
|
+
self.assertIn(self.sync3.pk, sync_ids)
|
|
1040
|
+
|
|
1041
|
+
params = {"ipfabric_filter_id": [self.filter3.pk]}
|
|
1042
|
+
result = self.filterset(params, self.queryset).qs
|
|
1043
|
+
# Sync 1 uses filter3
|
|
1044
|
+
self.assertEqual(result.count(), 1)
|
|
1045
|
+
self.assertEqual(result.first().pk, self.sync1.pk)
|
|
1046
|
+
|
|
1047
|
+
def test_ipfabric_filter_name(self):
|
|
1048
|
+
"""Test filtering by IP Fabric filter name"""
|
|
1049
|
+
params = {"ipfabric_filter": "Sync Filter 1"}
|
|
1050
|
+
result = self.filterset(params, self.queryset).qs
|
|
1051
|
+
# Syncs 1 and 2 use filter1
|
|
1052
|
+
self.assertEqual(result.count(), 2)
|
|
1053
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1054
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1055
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1056
|
+
|
|
1057
|
+
# Test case-insensitive
|
|
1058
|
+
params = {"ipfabric_filter": "sync filter 2"}
|
|
1059
|
+
result = self.filterset(params, self.queryset).qs
|
|
1060
|
+
# Syncs 2 and 3 use filter2
|
|
1061
|
+
self.assertEqual(result.count(), 2)
|
|
1062
|
+
|
|
1063
|
+
def test_ipfabric_filters_multiple(self):
|
|
1064
|
+
"""Test filtering by multiple IP Fabric filter IDs"""
|
|
1065
|
+
params = {"ipfabric_filters": [self.filter1.pk, self.filter3.pk]}
|
|
1066
|
+
result = self.filterset(params, self.queryset).qs
|
|
1067
|
+
# filter1 has sync1+sync2, filter3 has sync1 = 2 syncs total (sync1, sync2)
|
|
1068
|
+
self.assertEqual(result.count(), 2)
|
|
1069
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1070
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1071
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1072
|
+
|
|
1073
|
+
# Test with filter2 and filter3
|
|
1074
|
+
params = {"ipfabric_filters": [self.filter2.pk, self.filter3.pk]}
|
|
1075
|
+
result = self.filterset(params, self.queryset).qs
|
|
1076
|
+
# filter2 has sync2+sync3, filter3 has sync1 = 3 syncs total
|
|
1077
|
+
self.assertEqual(result.count(), 3)
|
|
1078
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1079
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1080
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1081
|
+
self.assertIn(self.sync3.pk, sync_ids)
|
|
1082
|
+
|
|
1083
|
+
def test_combined_filters(self):
|
|
1084
|
+
"""Test combining multiple filters"""
|
|
1085
|
+
# Test ipfabric_filter_id + snapshot_data_id
|
|
1086
|
+
params = {
|
|
1087
|
+
"ipfabric_filter_id": [self.filter1.pk],
|
|
1088
|
+
"snapshot_data_id": [self.snapshot1.pk],
|
|
1089
|
+
}
|
|
1090
|
+
result = self.filterset(params, self.queryset).qs
|
|
1091
|
+
# Syncs 1 and 2 have filter1 and snapshot1
|
|
1092
|
+
self.assertEqual(result.count(), 2)
|
|
1093
|
+
sync_ids = set(result.values_list("id", flat=True))
|
|
1094
|
+
self.assertIn(self.sync1.pk, sync_ids)
|
|
1095
|
+
self.assertIn(self.sync2.pk, sync_ids)
|
|
1096
|
+
|
|
1097
|
+
# Test ipfabric_filter_id + status
|
|
1098
|
+
params = {
|
|
1099
|
+
"ipfabric_filter_id": [self.filter2.pk],
|
|
1100
|
+
"status": [IPFabricSyncStatusChoices.COMPLETED],
|
|
1101
|
+
}
|
|
1102
|
+
result = self.filterset(params, self.queryset).qs
|
|
1103
|
+
# Only Sync 2 has filter2 and COMPLETED status
|
|
1104
|
+
self.assertEqual(result.count(), 1)
|
|
1105
|
+
self.assertEqual(result.first().pk, self.sync2.pk)
|
|
1106
|
+
|
|
1107
|
+
# Test ipfabric_filter + auto_merge
|
|
1108
|
+
params = {
|
|
1109
|
+
"ipfabric_filter": "Sync Filter 1",
|
|
1110
|
+
"auto_merge": True,
|
|
1111
|
+
}
|
|
1112
|
+
result = self.filterset(params, self.queryset).qs
|
|
1113
|
+
# Only Sync 1 has filter1 and auto_merge=True
|
|
1114
|
+
self.assertEqual(result.count(), 1)
|
|
1115
|
+
self.assertEqual(result.first().pk, self.sync1.pk)
|
|
1116
|
+
|
|
1117
|
+
def test_distinct_results(self):
|
|
1118
|
+
"""Test that filters return distinct results"""
|
|
1119
|
+
# Add filter1 to all syncs
|
|
1120
|
+
self.filter1.syncs.add(self.sync3)
|
|
1121
|
+
|
|
1122
|
+
# Filter by filter that's in all syncs
|
|
1123
|
+
params = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
1124
|
+
result = self.filterset(params, self.queryset).qs
|
|
1125
|
+
|
|
1126
|
+
# Each sync should appear only once
|
|
1127
|
+
sync_counts = {}
|
|
1128
|
+
for sync in result:
|
|
1129
|
+
sync_counts[sync.pk] = sync_counts.get(sync.pk, 0) + 1
|
|
1130
|
+
|
|
1131
|
+
for count in sync_counts.values():
|
|
1132
|
+
self.assertEqual(count, 1)
|
|
1133
|
+
|
|
1134
|
+
def test_empty_filters(self):
|
|
1135
|
+
"""Test behavior with empty filter values"""
|
|
1136
|
+
total_count = self.queryset.count()
|
|
1137
|
+
|
|
1138
|
+
params = {"ipfabric_filter": ""}
|
|
1139
|
+
result = self.filterset(params, self.queryset).qs
|
|
1140
|
+
self.assertEqual(result.count(), total_count)
|
|
1141
|
+
|
|
1142
|
+
params = {"name": ""}
|
|
1143
|
+
result = self.filterset(params, self.queryset).qs
|
|
1144
|
+
self.assertEqual(result.count(), total_count)
|
|
1145
|
+
|
|
1146
|
+
def test_ipfabric_filter_filters_equivalence(self):
|
|
1147
|
+
"""Test that ipfabric_filter_id and ipfabric_filters work correctly"""
|
|
1148
|
+
# Single ID with ipfabric_filter_id
|
|
1149
|
+
params1 = {"ipfabric_filter_id": [self.filter1.pk]}
|
|
1150
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1151
|
+
|
|
1152
|
+
# Same ID with ipfabric_filters
|
|
1153
|
+
params2 = {"ipfabric_filters": [self.filter1.pk]}
|
|
1154
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
1155
|
+
|
|
1156
|
+
# Should return same results
|
|
1157
|
+
self.assertEqual(result1.count(), result2.count())
|
|
1158
|
+
self.assertEqual(
|
|
1159
|
+
set(result1.values_list("id", flat=True)),
|
|
1160
|
+
set(result2.values_list("id", flat=True)),
|
|
1161
|
+
)
|
|
1162
|
+
|
|
1163
|
+
# Multiple IDs with ipfabric_filter_id
|
|
1164
|
+
params1 = {"ipfabric_filter_id": [self.filter1.pk, self.filter2.pk]}
|
|
1165
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1166
|
+
|
|
1167
|
+
# Same IDs with ipfabric_filters
|
|
1168
|
+
params2 = {"ipfabric_filters": [self.filter1.pk, self.filter2.pk]}
|
|
1169
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
1170
|
+
|
|
1171
|
+
# Should return same results
|
|
1172
|
+
self.assertEqual(result1.count(), result2.count())
|
|
1173
|
+
self.assertEqual(
|
|
1174
|
+
set(result1.values_list("id", flat=True)),
|
|
1175
|
+
set(result2.values_list("id", flat=True)),
|
|
1176
|
+
)
|
|
1177
|
+
|
|
1178
|
+
def test_search(self):
|
|
1179
|
+
"""Test search functionality"""
|
|
1180
|
+
params = {"q": "Sync"}
|
|
1181
|
+
result = self.filterset(params, self.queryset).qs
|
|
1182
|
+
# Should find syncs with "Sync" in name or snapshot name
|
|
1183
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
class IPFabricTransformMapFilterSetTestCase(TestCase):
|
|
1187
|
+
"""
|
|
1188
|
+
Test IPFabricTransformMapFilterSet to verify all custom filters work correctly.
|
|
1189
|
+
"""
|
|
1190
|
+
|
|
1191
|
+
queryset = IPFabricTransformMap.objects.all()
|
|
1192
|
+
filterset = IPFabricTransformMapFilterSet
|
|
1193
|
+
|
|
1194
|
+
@classmethod
|
|
1195
|
+
def setUpTestData(cls):
|
|
1196
|
+
# Get existing endpoints from migrations
|
|
1197
|
+
cls.sites_endpoint = IPFabricEndpoint.objects.filter(
|
|
1198
|
+
endpoint=IPFabricEndpointChoices.SITES
|
|
1199
|
+
).first()
|
|
1200
|
+
cls.devices_endpoint = IPFabricEndpoint.objects.filter(
|
|
1201
|
+
endpoint=IPFabricEndpointChoices.DEVICES
|
|
1202
|
+
).first()
|
|
1203
|
+
cls.vrfs_endpoint = IPFabricEndpoint.objects.filter(
|
|
1204
|
+
endpoint=IPFabricEndpointChoices.VRFS
|
|
1205
|
+
).first()
|
|
1206
|
+
|
|
1207
|
+
# Get ContentType for target_model
|
|
1208
|
+
from dcim.models import Site, Device
|
|
1209
|
+
|
|
1210
|
+
cls.site_ct = ContentType.objects.get_for_model(Site)
|
|
1211
|
+
cls.device_ct = ContentType.objects.get_for_model(Device)
|
|
1212
|
+
|
|
1213
|
+
# Create test transform map groups
|
|
1214
|
+
groups = [
|
|
1215
|
+
IPFabricTransformMapGroup(
|
|
1216
|
+
name="Test Group 1",
|
|
1217
|
+
description="First test group",
|
|
1218
|
+
),
|
|
1219
|
+
IPFabricTransformMapGroup(
|
|
1220
|
+
name="Test Group 2",
|
|
1221
|
+
description="Second test group",
|
|
1222
|
+
),
|
|
1223
|
+
]
|
|
1224
|
+
IPFabricTransformMapGroup.objects.bulk_create(groups)
|
|
1225
|
+
|
|
1226
|
+
cls.group1 = IPFabricTransformMapGroup.objects.get(name="Test Group 1")
|
|
1227
|
+
cls.group2 = IPFabricTransformMapGroup.objects.get(name="Test Group 2")
|
|
1228
|
+
|
|
1229
|
+
# Create test transform maps
|
|
1230
|
+
transform_maps = [
|
|
1231
|
+
IPFabricTransformMap(
|
|
1232
|
+
name="Sites Transform Map 1",
|
|
1233
|
+
source_endpoint=cls.sites_endpoint,
|
|
1234
|
+
target_model=cls.site_ct,
|
|
1235
|
+
group=cls.group1,
|
|
1236
|
+
),
|
|
1237
|
+
IPFabricTransformMap(
|
|
1238
|
+
name="Devices Transform Map 1",
|
|
1239
|
+
source_endpoint=cls.devices_endpoint,
|
|
1240
|
+
target_model=cls.device_ct,
|
|
1241
|
+
group=cls.group1,
|
|
1242
|
+
),
|
|
1243
|
+
IPFabricTransformMap(
|
|
1244
|
+
name="VRFs Transform Map 1",
|
|
1245
|
+
source_endpoint=cls.vrfs_endpoint,
|
|
1246
|
+
target_model=cls.device_ct,
|
|
1247
|
+
group=cls.group2,
|
|
1248
|
+
),
|
|
1249
|
+
IPFabricTransformMap(
|
|
1250
|
+
name="Sites Transform Map 2",
|
|
1251
|
+
source_endpoint=cls.sites_endpoint,
|
|
1252
|
+
target_model=cls.site_ct,
|
|
1253
|
+
group=cls.group2,
|
|
1254
|
+
),
|
|
1255
|
+
]
|
|
1256
|
+
IPFabricTransformMap.objects.bulk_create(transform_maps)
|
|
1257
|
+
|
|
1258
|
+
cls.sites_map1 = IPFabricTransformMap.objects.get(name="Sites Transform Map 1")
|
|
1259
|
+
cls.devices_map1 = IPFabricTransformMap.objects.get(
|
|
1260
|
+
name="Devices Transform Map 1"
|
|
1261
|
+
)
|
|
1262
|
+
cls.vrfs_map1 = IPFabricTransformMap.objects.get(name="VRFs Transform Map 1")
|
|
1263
|
+
cls.sites_map2 = IPFabricTransformMap.objects.get(name="Sites Transform Map 2")
|
|
1264
|
+
|
|
1265
|
+
def test_id(self):
|
|
1266
|
+
"""Test filtering by transform map ID"""
|
|
1267
|
+
params = {"id": [self.sites_map1.pk]}
|
|
1268
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
1269
|
+
params = {"id": [self.sites_map1.pk, self.devices_map1.pk]}
|
|
1270
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1271
|
+
|
|
1272
|
+
def test_name(self):
|
|
1273
|
+
"""Test filtering by transform map name"""
|
|
1274
|
+
params = {"name": ["Sites Transform Map 1"]}
|
|
1275
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
1276
|
+
params = {"name": ["Sites Transform Map 1", "Devices Transform Map 1"]}
|
|
1277
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1278
|
+
|
|
1279
|
+
def test_group(self):
|
|
1280
|
+
"""Test filtering by group"""
|
|
1281
|
+
params = {"group": [self.group1.pk]}
|
|
1282
|
+
result = self.filterset(params, self.queryset).qs
|
|
1283
|
+
# Group 1 has sites_map1 and devices_map1
|
|
1284
|
+
self.assertEqual(result.count(), 2)
|
|
1285
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1286
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1287
|
+
self.assertIn(self.devices_map1.pk, map_ids)
|
|
1288
|
+
|
|
1289
|
+
params = {"group": [self.group2.pk]}
|
|
1290
|
+
result = self.filterset(params, self.queryset).qs
|
|
1291
|
+
# Group 2 has vrfs_map1 and sites_map2
|
|
1292
|
+
self.assertEqual(result.count(), 2)
|
|
1293
|
+
|
|
1294
|
+
def test_group_id(self):
|
|
1295
|
+
"""Test filtering by group ID"""
|
|
1296
|
+
params = {"group_id": [self.group1.pk]}
|
|
1297
|
+
result = self.filterset(params, self.queryset).qs
|
|
1298
|
+
# Group 1 has sites_map1 and devices_map1
|
|
1299
|
+
self.assertEqual(result.count(), 2)
|
|
1300
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1301
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1302
|
+
self.assertIn(self.devices_map1.pk, map_ids)
|
|
1303
|
+
|
|
1304
|
+
def test_source_endpoint(self):
|
|
1305
|
+
"""Test filtering by source endpoint (single)"""
|
|
1306
|
+
params = {"source_endpoint": self.sites_endpoint.pk}
|
|
1307
|
+
result = self.filterset(params, self.queryset).qs
|
|
1308
|
+
# Sites endpoint has sites_map1 and sites_map2 from our test data
|
|
1309
|
+
# (may include more from other test classes)
|
|
1310
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
1311
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1312
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1313
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1314
|
+
|
|
1315
|
+
params = {"source_endpoint": self.devices_endpoint.pk}
|
|
1316
|
+
result = self.filterset(params, self.queryset).qs
|
|
1317
|
+
# Devices endpoint has devices_map1 from our test data
|
|
1318
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
1319
|
+
self.assertIn(self.devices_map1.pk, result.values_list("id", flat=True))
|
|
1320
|
+
|
|
1321
|
+
def test_source_endpoint_id(self):
|
|
1322
|
+
"""Test filtering by source endpoint ID (multiple)"""
|
|
1323
|
+
params = {"source_endpoint_id": [self.sites_endpoint.pk]}
|
|
1324
|
+
result = self.filterset(params, self.queryset).qs
|
|
1325
|
+
# Sites endpoint has sites_map1 and sites_map2 from our test data
|
|
1326
|
+
# (may include more from other test classes)
|
|
1327
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
1328
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1329
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1330
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1331
|
+
|
|
1332
|
+
params = {"source_endpoint_id": [self.devices_endpoint.pk]}
|
|
1333
|
+
result = self.filterset(params, self.queryset).qs
|
|
1334
|
+
# Devices endpoint has devices_map1 from our test data
|
|
1335
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
1336
|
+
self.assertIn(self.devices_map1.pk, result.values_list("id", flat=True))
|
|
1337
|
+
|
|
1338
|
+
# Test multiple endpoint IDs
|
|
1339
|
+
params = {"source_endpoint_id": [self.sites_endpoint.pk, self.vrfs_endpoint.pk]}
|
|
1340
|
+
result = self.filterset(params, self.queryset).qs
|
|
1341
|
+
# Sites has 2 maps, VRFs has 1 map = 3 minimum from our test data
|
|
1342
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
1343
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1344
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1345
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1346
|
+
self.assertIn(self.vrfs_map1.pk, map_ids)
|
|
1347
|
+
|
|
1348
|
+
def test_source_endpoints(self):
|
|
1349
|
+
"""Test filtering by source endpoints (multiple)"""
|
|
1350
|
+
params = {
|
|
1351
|
+
"source_endpoints": [self.sites_endpoint.pk, self.devices_endpoint.pk]
|
|
1352
|
+
}
|
|
1353
|
+
result = self.filterset(params, self.queryset).qs
|
|
1354
|
+
# Sites has 2 maps, Devices has 1 map = 3 minimum from our test data
|
|
1355
|
+
# (may include more from other test classes)
|
|
1356
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
1357
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1358
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1359
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1360
|
+
self.assertIn(self.devices_map1.pk, map_ids)
|
|
1361
|
+
|
|
1362
|
+
# Test all endpoints
|
|
1363
|
+
params = {
|
|
1364
|
+
"source_endpoints": [
|
|
1365
|
+
self.sites_endpoint.pk,
|
|
1366
|
+
self.devices_endpoint.pk,
|
|
1367
|
+
self.vrfs_endpoint.pk,
|
|
1368
|
+
]
|
|
1369
|
+
}
|
|
1370
|
+
result = self.filterset(params, self.queryset).qs
|
|
1371
|
+
# All 4 transform maps from our test data minimum
|
|
1372
|
+
self.assertGreaterEqual(result.count(), 4)
|
|
1373
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1374
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1375
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1376
|
+
self.assertIn(self.devices_map1.pk, map_ids)
|
|
1377
|
+
self.assertIn(self.vrfs_map1.pk, map_ids)
|
|
1378
|
+
|
|
1379
|
+
def test_target_model(self):
|
|
1380
|
+
"""Test filtering by target model"""
|
|
1381
|
+
params = {"target_model": self.site_ct.pk}
|
|
1382
|
+
result = self.filterset(params, self.queryset).qs
|
|
1383
|
+
# Sites maps target Site model from our test data
|
|
1384
|
+
# (may include more from other test classes)
|
|
1385
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
1386
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1387
|
+
self.assertIn(self.sites_map1.pk, map_ids)
|
|
1388
|
+
self.assertIn(self.sites_map2.pk, map_ids)
|
|
1389
|
+
|
|
1390
|
+
params = {"target_model": self.device_ct.pk}
|
|
1391
|
+
result = self.filterset(params, self.queryset).qs
|
|
1392
|
+
# Devices and VRFs maps target Device model from our test data
|
|
1393
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
1394
|
+
map_ids = set(result.values_list("id", flat=True))
|
|
1395
|
+
self.assertIn(self.devices_map1.pk, map_ids)
|
|
1396
|
+
self.assertIn(self.vrfs_map1.pk, map_ids)
|
|
1397
|
+
|
|
1398
|
+
def test_combined_filters(self):
|
|
1399
|
+
"""Test combining multiple filters"""
|
|
1400
|
+
# Test source_endpoint_id + group
|
|
1401
|
+
params = {
|
|
1402
|
+
"source_endpoint_id": [self.sites_endpoint.pk],
|
|
1403
|
+
"group": [self.group1.pk],
|
|
1404
|
+
}
|
|
1405
|
+
result = self.filterset(params, self.queryset).qs
|
|
1406
|
+
# Only sites_map1 has sites endpoint and group1 from our test data
|
|
1407
|
+
# (may include more from other test classes)
|
|
1408
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
1409
|
+
self.assertIn(self.sites_map1.pk, result.values_list("id", flat=True))
|
|
1410
|
+
|
|
1411
|
+
# Test source_endpoints + target_model
|
|
1412
|
+
params = {
|
|
1413
|
+
"source_endpoints": [self.sites_endpoint.pk, self.devices_endpoint.pk],
|
|
1414
|
+
"target_model": self.device_ct.pk,
|
|
1415
|
+
}
|
|
1416
|
+
result = self.filterset(params, self.queryset).qs
|
|
1417
|
+
# Only devices_map1 has devices endpoint and Device target from our test data
|
|
1418
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
1419
|
+
self.assertIn(self.devices_map1.pk, result.values_list("id", flat=True))
|
|
1420
|
+
|
|
1421
|
+
# Test group_id + target_model
|
|
1422
|
+
params = {
|
|
1423
|
+
"group_id": [self.group2.pk],
|
|
1424
|
+
"target_model": self.site_ct.pk,
|
|
1425
|
+
}
|
|
1426
|
+
result = self.filterset(params, self.queryset).qs
|
|
1427
|
+
# Only sites_map2 has group2 and Site target from our test data
|
|
1428
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
1429
|
+
self.assertIn(self.sites_map2.pk, result.values_list("id", flat=True))
|
|
1430
|
+
|
|
1431
|
+
def test_q_search(self):
|
|
1432
|
+
"""Test the search (q) parameter"""
|
|
1433
|
+
# Search by transform map name
|
|
1434
|
+
params = {"q": "Sites"}
|
|
1435
|
+
result = self.filterset(params, self.queryset).qs
|
|
1436
|
+
self.assertEqual(result.count(), 2)
|
|
1437
|
+
|
|
1438
|
+
# Search by group name
|
|
1439
|
+
params = {"q": "Test Group 1"}
|
|
1440
|
+
result = self.filterset(params, self.queryset).qs
|
|
1441
|
+
self.assertEqual(result.count(), 2)
|
|
1442
|
+
|
|
1443
|
+
# Search that matches transform map name
|
|
1444
|
+
params = {"q": "Devices"}
|
|
1445
|
+
result = self.filterset(params, self.queryset).qs
|
|
1446
|
+
self.assertEqual(result.count(), 1)
|
|
1447
|
+
|
|
1448
|
+
def test_distinct_results(self):
|
|
1449
|
+
"""Test that filters return distinct results"""
|
|
1450
|
+
# Query with source_endpoint_id (should have no duplicates)
|
|
1451
|
+
params = {"source_endpoint_id": [self.sites_endpoint.pk]}
|
|
1452
|
+
result = self.filterset(params, self.queryset).qs
|
|
1453
|
+
|
|
1454
|
+
# Each transform map should appear only once
|
|
1455
|
+
map_counts = {}
|
|
1456
|
+
for map_obj in result:
|
|
1457
|
+
map_counts[map_obj.pk] = map_counts.get(map_obj.pk, 0) + 1
|
|
1458
|
+
|
|
1459
|
+
for count in map_counts.values():
|
|
1460
|
+
self.assertEqual(count, 1)
|
|
1461
|
+
|
|
1462
|
+
def test_empty_filters(self):
|
|
1463
|
+
"""Test behavior with empty filter values"""
|
|
1464
|
+
total_count = self.queryset.count()
|
|
1465
|
+
|
|
1466
|
+
params = {"source_endpoint": ""}
|
|
1467
|
+
result = self.filterset(params, self.queryset).qs
|
|
1468
|
+
self.assertEqual(result.count(), total_count)
|
|
1469
|
+
|
|
1470
|
+
params = {"group": ""}
|
|
1471
|
+
result = self.filterset(params, self.queryset).qs
|
|
1472
|
+
self.assertEqual(result.count(), total_count)
|
|
1473
|
+
|
|
1474
|
+
def test_source_endpoint_filters_equivalence(self):
|
|
1475
|
+
"""Test that source_endpoint_id and source_endpoints work correctly"""
|
|
1476
|
+
# Single ID with source_endpoint_id
|
|
1477
|
+
params1 = {"source_endpoint_id": [self.sites_endpoint.pk]}
|
|
1478
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1479
|
+
|
|
1480
|
+
# Same ID with source_endpoints
|
|
1481
|
+
params2 = {"source_endpoints": [self.sites_endpoint.pk]}
|
|
1482
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
1483
|
+
|
|
1484
|
+
# Should return same results
|
|
1485
|
+
self.assertEqual(result1.count(), result2.count())
|
|
1486
|
+
self.assertEqual(
|
|
1487
|
+
set(result1.values_list("id", flat=True)),
|
|
1488
|
+
set(result2.values_list("id", flat=True)),
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
# Multiple IDs with source_endpoint_id
|
|
1492
|
+
params1 = {
|
|
1493
|
+
"source_endpoint_id": [self.sites_endpoint.pk, self.devices_endpoint.pk]
|
|
1494
|
+
}
|
|
1495
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1496
|
+
|
|
1497
|
+
# Same IDs with source_endpoints
|
|
1498
|
+
params2 = {
|
|
1499
|
+
"source_endpoints": [self.sites_endpoint.pk, self.devices_endpoint.pk]
|
|
1500
|
+
}
|
|
1501
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
1502
|
+
|
|
1503
|
+
# Should return same results
|
|
1504
|
+
self.assertEqual(result1.count(), result2.count())
|
|
1505
|
+
self.assertEqual(
|
|
1506
|
+
set(result1.values_list("id", flat=True)),
|
|
1507
|
+
set(result2.values_list("id", flat=True)),
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
class IPFabricTransformFieldFilterSetTestCase(TestCase):
|
|
1512
|
+
"""
|
|
1513
|
+
Test IPFabricTransformFieldFilterSet to verify all custom filters work correctly.
|
|
1514
|
+
"""
|
|
1515
|
+
|
|
1516
|
+
queryset = IPFabricTransformField.objects.all()
|
|
1517
|
+
filterset = IPFabricTransformFieldFilterSet
|
|
1518
|
+
|
|
1519
|
+
@classmethod
|
|
1520
|
+
def setUpTestData(cls):
|
|
1521
|
+
# Get existing endpoints from migrations
|
|
1522
|
+
cls.sites_endpoint = IPFabricEndpoint.objects.filter(
|
|
1523
|
+
endpoint=IPFabricEndpointChoices.SITES
|
|
1524
|
+
).first()
|
|
1525
|
+
cls.devices_endpoint = IPFabricEndpoint.objects.filter(
|
|
1526
|
+
endpoint=IPFabricEndpointChoices.DEVICES
|
|
1527
|
+
).first()
|
|
1528
|
+
|
|
1529
|
+
# Get ContentType for target_model
|
|
1530
|
+
from dcim.models import Site, Device
|
|
1531
|
+
|
|
1532
|
+
cls.site_ct = ContentType.objects.get_for_model(Site)
|
|
1533
|
+
cls.device_ct = ContentType.objects.get_for_model(Device)
|
|
1534
|
+
|
|
1535
|
+
# Create test transform map groups
|
|
1536
|
+
cls.group1 = IPFabricTransformMapGroup.objects.create(
|
|
1537
|
+
name="Transform Field Test Group 1",
|
|
1538
|
+
description="First transform field test group",
|
|
1539
|
+
)
|
|
1540
|
+
cls.group2 = IPFabricTransformMapGroup.objects.create(
|
|
1541
|
+
name="Transform Field Test Group 2",
|
|
1542
|
+
description="Second transform field test group",
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
# Create test transform maps
|
|
1546
|
+
cls.map1 = IPFabricTransformMap.objects.create(
|
|
1547
|
+
name="Transform Field Map 1",
|
|
1548
|
+
source_endpoint=cls.sites_endpoint,
|
|
1549
|
+
target_model=cls.site_ct,
|
|
1550
|
+
group=cls.group1,
|
|
1551
|
+
)
|
|
1552
|
+
cls.map2 = IPFabricTransformMap.objects.create(
|
|
1553
|
+
name="Transform Field Map 2",
|
|
1554
|
+
source_endpoint=cls.devices_endpoint,
|
|
1555
|
+
target_model=cls.device_ct,
|
|
1556
|
+
group=cls.group1,
|
|
1557
|
+
)
|
|
1558
|
+
cls.map3 = IPFabricTransformMap.objects.create(
|
|
1559
|
+
name="Transform Field Map 3",
|
|
1560
|
+
source_endpoint=cls.sites_endpoint,
|
|
1561
|
+
target_model=cls.site_ct,
|
|
1562
|
+
group=cls.group2,
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1565
|
+
# Create test transform fields
|
|
1566
|
+
cls.field1 = IPFabricTransformField.objects.create(
|
|
1567
|
+
transform_map=cls.map1,
|
|
1568
|
+
source_field="siteName",
|
|
1569
|
+
target_field="name",
|
|
1570
|
+
coalesce=False,
|
|
1571
|
+
template="{{siteName}}",
|
|
1572
|
+
)
|
|
1573
|
+
cls.field2 = IPFabricTransformField.objects.create(
|
|
1574
|
+
transform_map=cls.map1,
|
|
1575
|
+
source_field="siteDescription",
|
|
1576
|
+
target_field="description",
|
|
1577
|
+
coalesce=True,
|
|
1578
|
+
template="{{siteDescription}}",
|
|
1579
|
+
)
|
|
1580
|
+
cls.field3 = IPFabricTransformField.objects.create(
|
|
1581
|
+
transform_map=cls.map2,
|
|
1582
|
+
source_field="hostname",
|
|
1583
|
+
target_field="name",
|
|
1584
|
+
coalesce=False,
|
|
1585
|
+
template="{{hostname}}",
|
|
1586
|
+
)
|
|
1587
|
+
cls.field4 = IPFabricTransformField.objects.create(
|
|
1588
|
+
transform_map=cls.map3,
|
|
1589
|
+
source_field="siteName",
|
|
1590
|
+
target_field="name",
|
|
1591
|
+
coalesce=False,
|
|
1592
|
+
template="{{siteName}}",
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
def test_id(self):
|
|
1596
|
+
"""Test filtering by transform field ID"""
|
|
1597
|
+
params = {"id": [self.field1.pk]}
|
|
1598
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
1599
|
+
params = {"id": [self.field1.pk, self.field2.pk]}
|
|
1600
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1601
|
+
|
|
1602
|
+
def test_transform_map(self):
|
|
1603
|
+
"""Test filtering by transform map"""
|
|
1604
|
+
params = {"transform_map": [self.map1.pk]}
|
|
1605
|
+
result = self.filterset(params, self.queryset).qs
|
|
1606
|
+
# Map1 has field1 and field2
|
|
1607
|
+
self.assertEqual(result.count(), 2)
|
|
1608
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1609
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1610
|
+
self.assertIn(self.field2.pk, field_ids)
|
|
1611
|
+
|
|
1612
|
+
params = {"transform_map": [self.map2.pk]}
|
|
1613
|
+
result = self.filterset(params, self.queryset).qs
|
|
1614
|
+
# Map2 has field3
|
|
1615
|
+
self.assertEqual(result.count(), 1)
|
|
1616
|
+
self.assertEqual(result.first().pk, self.field3.pk)
|
|
1617
|
+
|
|
1618
|
+
def test_transform_map_id(self):
|
|
1619
|
+
"""Test filtering by transform map ID"""
|
|
1620
|
+
params = {"transform_map_id": [self.map1.pk]}
|
|
1621
|
+
result = self.filterset(params, self.queryset).qs
|
|
1622
|
+
# Map1 has field1 and field2
|
|
1623
|
+
self.assertEqual(result.count(), 2)
|
|
1624
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1625
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1626
|
+
self.assertIn(self.field2.pk, field_ids)
|
|
1627
|
+
|
|
1628
|
+
# Test multiple transform map IDs
|
|
1629
|
+
params = {"transform_map_id": [self.map1.pk, self.map3.pk]}
|
|
1630
|
+
result = self.filterset(params, self.queryset).qs
|
|
1631
|
+
# Map1 has 2 fields, Map3 has 1 field = 3 total
|
|
1632
|
+
self.assertEqual(result.count(), 3)
|
|
1633
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1634
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1635
|
+
self.assertIn(self.field2.pk, field_ids)
|
|
1636
|
+
self.assertIn(self.field4.pk, field_ids)
|
|
1637
|
+
|
|
1638
|
+
def test_source_field(self):
|
|
1639
|
+
"""Test filtering by source field"""
|
|
1640
|
+
# First filter to only our test transform maps' fields
|
|
1641
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1642
|
+
|
|
1643
|
+
params = {"source_field": "siteName", "transform_map": test_maps}
|
|
1644
|
+
result = self.filterset(params, self.queryset).qs
|
|
1645
|
+
# field1 and field4 have siteName (from our test maps only)
|
|
1646
|
+
self.assertEqual(result.count(), 2)
|
|
1647
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1648
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1649
|
+
self.assertIn(self.field4.pk, field_ids)
|
|
1650
|
+
|
|
1651
|
+
params = {"source_field": "hostname", "transform_map": test_maps}
|
|
1652
|
+
result = self.filterset(params, self.queryset).qs
|
|
1653
|
+
# field3 has hostname (from our test maps only)
|
|
1654
|
+
self.assertEqual(result.count(), 1)
|
|
1655
|
+
self.assertEqual(result.first().pk, self.field3.pk)
|
|
1656
|
+
|
|
1657
|
+
def test_target_field(self):
|
|
1658
|
+
"""Test filtering by target field"""
|
|
1659
|
+
# First filter to only our test transform maps' fields
|
|
1660
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1661
|
+
|
|
1662
|
+
params = {"target_field": "name", "transform_map": test_maps}
|
|
1663
|
+
result = self.filterset(params, self.queryset).qs
|
|
1664
|
+
# field1, field3, and field4 have name as target (from our test maps only)
|
|
1665
|
+
self.assertEqual(result.count(), 3)
|
|
1666
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1667
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1668
|
+
self.assertIn(self.field3.pk, field_ids)
|
|
1669
|
+
self.assertIn(self.field4.pk, field_ids)
|
|
1670
|
+
|
|
1671
|
+
params = {"target_field": "description", "transform_map": test_maps}
|
|
1672
|
+
result = self.filterset(params, self.queryset).qs
|
|
1673
|
+
# field2 has description as target (from our test maps only)
|
|
1674
|
+
self.assertEqual(result.count(), 1)
|
|
1675
|
+
self.assertEqual(result.first().pk, self.field2.pk)
|
|
1676
|
+
|
|
1677
|
+
def test_coalesce(self):
|
|
1678
|
+
"""Test filtering by coalesce"""
|
|
1679
|
+
# First filter to only our test transform maps' fields
|
|
1680
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1681
|
+
|
|
1682
|
+
params = {"coalesce": True, "transform_map": test_maps}
|
|
1683
|
+
result = self.filterset(params, self.queryset).qs
|
|
1684
|
+
# field2 has coalesce=True (from our test maps only)
|
|
1685
|
+
self.assertEqual(result.count(), 1)
|
|
1686
|
+
self.assertEqual(result.first().pk, self.field2.pk)
|
|
1687
|
+
|
|
1688
|
+
params = {"coalesce": False, "transform_map": test_maps}
|
|
1689
|
+
result = self.filterset(params, self.queryset).qs
|
|
1690
|
+
# field1, field3, field4 have coalesce=False (from our test maps only)
|
|
1691
|
+
self.assertEqual(result.count(), 3)
|
|
1692
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1693
|
+
self.assertIn(self.field1.pk, field_ids)
|
|
1694
|
+
self.assertIn(self.field3.pk, field_ids)
|
|
1695
|
+
self.assertIn(self.field4.pk, field_ids)
|
|
1696
|
+
|
|
1697
|
+
def test_combined_filters(self):
|
|
1698
|
+
"""Test combining multiple filters"""
|
|
1699
|
+
# Test transform_map_id + source_field
|
|
1700
|
+
params = {
|
|
1701
|
+
"transform_map_id": [self.map1.pk],
|
|
1702
|
+
"source_field": "siteName",
|
|
1703
|
+
}
|
|
1704
|
+
result = self.filterset(params, self.queryset).qs
|
|
1705
|
+
# Only field1 has map1 and siteName
|
|
1706
|
+
self.assertEqual(result.count(), 1)
|
|
1707
|
+
self.assertEqual(result.first().pk, self.field1.pk)
|
|
1708
|
+
|
|
1709
|
+
# Test transform_map_id + target_field
|
|
1710
|
+
params = {
|
|
1711
|
+
"transform_map_id": [self.map1.pk],
|
|
1712
|
+
"target_field": "name",
|
|
1713
|
+
}
|
|
1714
|
+
result = self.filterset(params, self.queryset).qs
|
|
1715
|
+
# Only field1 has map1 and name target
|
|
1716
|
+
self.assertEqual(result.count(), 1)
|
|
1717
|
+
self.assertEqual(result.first().pk, self.field1.pk)
|
|
1718
|
+
|
|
1719
|
+
# Test transform_map_id + coalesce
|
|
1720
|
+
params = {
|
|
1721
|
+
"transform_map_id": [self.map1.pk],
|
|
1722
|
+
"coalesce": True,
|
|
1723
|
+
}
|
|
1724
|
+
result = self.filterset(params, self.queryset).qs
|
|
1725
|
+
# Only field2 has map1 and coalesce=True
|
|
1726
|
+
self.assertEqual(result.count(), 1)
|
|
1727
|
+
self.assertEqual(result.first().pk, self.field2.pk)
|
|
1728
|
+
|
|
1729
|
+
def test_transform_map_filters_equivalence(self):
|
|
1730
|
+
"""Test that transform_map and transform_map_id work correctly"""
|
|
1731
|
+
# Single ID with transform_map
|
|
1732
|
+
params1 = {"transform_map": [self.map1.pk]}
|
|
1733
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1734
|
+
|
|
1735
|
+
# Same ID with transform_map_id
|
|
1736
|
+
params2 = {"transform_map_id": [self.map1.pk]}
|
|
1737
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
1738
|
+
|
|
1739
|
+
# Should return same results
|
|
1740
|
+
self.assertEqual(result1.count(), result2.count())
|
|
1741
|
+
self.assertEqual(
|
|
1742
|
+
set(result1.values_list("id", flat=True)),
|
|
1743
|
+
set(result2.values_list("id", flat=True)),
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
def test_distinct_results(self):
|
|
1747
|
+
"""Test that filters return distinct results"""
|
|
1748
|
+
# Query with transform_map_id (should have no duplicates)
|
|
1749
|
+
params = {"transform_map_id": [self.map1.pk]}
|
|
1750
|
+
result = self.filterset(params, self.queryset).qs
|
|
1751
|
+
|
|
1752
|
+
# Each transform field should appear only once
|
|
1753
|
+
field_counts = {}
|
|
1754
|
+
for field in result:
|
|
1755
|
+
field_counts[field.pk] = field_counts.get(field.pk, 0) + 1
|
|
1756
|
+
|
|
1757
|
+
for count in field_counts.values():
|
|
1758
|
+
self.assertEqual(count, 1)
|
|
1759
|
+
|
|
1760
|
+
def test_empty_filters(self):
|
|
1761
|
+
"""Test behavior with empty filter values"""
|
|
1762
|
+
total_count = self.queryset.count()
|
|
1763
|
+
|
|
1764
|
+
params = {"transform_map": ""}
|
|
1765
|
+
result = self.filterset(params, self.queryset).qs
|
|
1766
|
+
self.assertEqual(result.count(), total_count)
|
|
1767
|
+
|
|
1768
|
+
params = {"source_field": ""}
|
|
1769
|
+
result = self.filterset(params, self.queryset).qs
|
|
1770
|
+
self.assertEqual(result.count(), total_count)
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
class IPFabricRelationshipFieldFilterSetTestCase(TestCase):
|
|
1774
|
+
"""
|
|
1775
|
+
Test IPFabricRelationshipFieldFilterSet to verify all custom filters work correctly.
|
|
1776
|
+
"""
|
|
1777
|
+
|
|
1778
|
+
queryset = IPFabricRelationshipField.objects.all()
|
|
1779
|
+
filterset = IPFabricRelationshipFieldFilterSet
|
|
1780
|
+
|
|
1781
|
+
@classmethod
|
|
1782
|
+
def setUpTestData(cls):
|
|
1783
|
+
# Get existing endpoints from migrations
|
|
1784
|
+
cls.sites_endpoint = IPFabricEndpoint.objects.filter(
|
|
1785
|
+
endpoint=IPFabricEndpointChoices.SITES
|
|
1786
|
+
).first()
|
|
1787
|
+
cls.devices_endpoint = IPFabricEndpoint.objects.filter(
|
|
1788
|
+
endpoint=IPFabricEndpointChoices.DEVICES
|
|
1789
|
+
).first()
|
|
1790
|
+
|
|
1791
|
+
# Get ContentType for models
|
|
1792
|
+
from dcim.models import Site, Device, Location
|
|
1793
|
+
|
|
1794
|
+
cls.site_ct = ContentType.objects.get_for_model(Site)
|
|
1795
|
+
cls.device_ct = ContentType.objects.get_for_model(Device)
|
|
1796
|
+
cls.location_ct = ContentType.objects.get_for_model(Location)
|
|
1797
|
+
|
|
1798
|
+
# Create test transform map groups
|
|
1799
|
+
cls.group1 = IPFabricTransformMapGroup.objects.create(
|
|
1800
|
+
name="Relationship Field Test Group 1",
|
|
1801
|
+
description="First relationship field test group",
|
|
1802
|
+
)
|
|
1803
|
+
cls.group2 = IPFabricTransformMapGroup.objects.create(
|
|
1804
|
+
name="Relationship Field Test Group 2",
|
|
1805
|
+
description="Second relationship field test group",
|
|
1806
|
+
)
|
|
1807
|
+
|
|
1808
|
+
# Create test transform maps
|
|
1809
|
+
cls.map1 = IPFabricTransformMap.objects.create(
|
|
1810
|
+
name="Relationship Field Map 1",
|
|
1811
|
+
source_endpoint=cls.sites_endpoint,
|
|
1812
|
+
target_model=cls.site_ct,
|
|
1813
|
+
group=cls.group1,
|
|
1814
|
+
)
|
|
1815
|
+
cls.map2 = IPFabricTransformMap.objects.create(
|
|
1816
|
+
name="Relationship Field Map 2",
|
|
1817
|
+
source_endpoint=cls.devices_endpoint,
|
|
1818
|
+
target_model=cls.device_ct,
|
|
1819
|
+
group=cls.group1,
|
|
1820
|
+
)
|
|
1821
|
+
cls.map3 = IPFabricTransformMap.objects.create(
|
|
1822
|
+
name="Relationship Field Map 3",
|
|
1823
|
+
source_endpoint=cls.sites_endpoint,
|
|
1824
|
+
target_model=cls.site_ct,
|
|
1825
|
+
group=cls.group2,
|
|
1826
|
+
)
|
|
1827
|
+
|
|
1828
|
+
# Create test relationship fields
|
|
1829
|
+
cls.rel_field1 = IPFabricRelationshipField.objects.create(
|
|
1830
|
+
transform_map=cls.map1,
|
|
1831
|
+
source_model=cls.location_ct,
|
|
1832
|
+
target_field="location",
|
|
1833
|
+
coalesce=False,
|
|
1834
|
+
template="{{location_id}}",
|
|
1835
|
+
)
|
|
1836
|
+
cls.rel_field2 = IPFabricRelationshipField.objects.create(
|
|
1837
|
+
transform_map=cls.map1,
|
|
1838
|
+
source_model=cls.site_ct,
|
|
1839
|
+
target_field="site",
|
|
1840
|
+
coalesce=True,
|
|
1841
|
+
template="{{site_id}}",
|
|
1842
|
+
)
|
|
1843
|
+
cls.rel_field3 = IPFabricRelationshipField.objects.create(
|
|
1844
|
+
transform_map=cls.map2,
|
|
1845
|
+
source_model=cls.device_ct,
|
|
1846
|
+
target_field="device",
|
|
1847
|
+
coalesce=False,
|
|
1848
|
+
template="{{device_id}}",
|
|
1849
|
+
)
|
|
1850
|
+
cls.rel_field4 = IPFabricRelationshipField.objects.create(
|
|
1851
|
+
transform_map=cls.map3,
|
|
1852
|
+
source_model=cls.location_ct,
|
|
1853
|
+
target_field="location",
|
|
1854
|
+
coalesce=False,
|
|
1855
|
+
template="{{location_id}}",
|
|
1856
|
+
)
|
|
1857
|
+
|
|
1858
|
+
def test_id(self):
|
|
1859
|
+
"""Test filtering by relationship field ID"""
|
|
1860
|
+
params = {"id": [self.rel_field1.pk]}
|
|
1861
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
1862
|
+
params = {"id": [self.rel_field1.pk, self.rel_field2.pk]}
|
|
1863
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1864
|
+
|
|
1865
|
+
def test_transform_map(self):
|
|
1866
|
+
"""Test filtering by transform map"""
|
|
1867
|
+
params = {"transform_map": [self.map1.pk]}
|
|
1868
|
+
result = self.filterset(params, self.queryset).qs
|
|
1869
|
+
# Map1 has rel_field1 and rel_field2
|
|
1870
|
+
self.assertEqual(result.count(), 2)
|
|
1871
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1872
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1873
|
+
self.assertIn(self.rel_field2.pk, field_ids)
|
|
1874
|
+
|
|
1875
|
+
params = {"transform_map": [self.map2.pk]}
|
|
1876
|
+
result = self.filterset(params, self.queryset).qs
|
|
1877
|
+
# Map2 has rel_field3
|
|
1878
|
+
self.assertEqual(result.count(), 1)
|
|
1879
|
+
self.assertEqual(result.first().pk, self.rel_field3.pk)
|
|
1880
|
+
|
|
1881
|
+
def test_transform_map_id(self):
|
|
1882
|
+
"""Test filtering by transform map ID"""
|
|
1883
|
+
params = {"transform_map_id": [self.map1.pk]}
|
|
1884
|
+
result = self.filterset(params, self.queryset).qs
|
|
1885
|
+
# Map1 has rel_field1 and rel_field2
|
|
1886
|
+
self.assertEqual(result.count(), 2)
|
|
1887
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1888
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1889
|
+
self.assertIn(self.rel_field2.pk, field_ids)
|
|
1890
|
+
|
|
1891
|
+
# Test multiple transform map IDs
|
|
1892
|
+
params = {"transform_map_id": [self.map1.pk, self.map3.pk]}
|
|
1893
|
+
result = self.filterset(params, self.queryset).qs
|
|
1894
|
+
# Map1 has 2 fields, Map3 has 1 field = 3 total
|
|
1895
|
+
self.assertEqual(result.count(), 3)
|
|
1896
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1897
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1898
|
+
self.assertIn(self.rel_field2.pk, field_ids)
|
|
1899
|
+
self.assertIn(self.rel_field4.pk, field_ids)
|
|
1900
|
+
|
|
1901
|
+
def test_source_model(self):
|
|
1902
|
+
"""Test filtering by source model"""
|
|
1903
|
+
# First filter to only our test transform maps' fields
|
|
1904
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1905
|
+
|
|
1906
|
+
params = {"source_model": self.location_ct.pk, "transform_map": test_maps}
|
|
1907
|
+
result = self.filterset(params, self.queryset).qs
|
|
1908
|
+
# rel_field1 and rel_field4 have Location as source model (from our test maps only)
|
|
1909
|
+
self.assertEqual(result.count(), 2)
|
|
1910
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1911
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1912
|
+
self.assertIn(self.rel_field4.pk, field_ids)
|
|
1913
|
+
|
|
1914
|
+
params = {"source_model": self.device_ct.pk, "transform_map": test_maps}
|
|
1915
|
+
result = self.filterset(params, self.queryset).qs
|
|
1916
|
+
# rel_field3 has Device as source model (from our test maps only)
|
|
1917
|
+
self.assertEqual(result.count(), 1)
|
|
1918
|
+
self.assertEqual(result.first().pk, self.rel_field3.pk)
|
|
1919
|
+
|
|
1920
|
+
def test_target_field(self):
|
|
1921
|
+
"""Test filtering by target field"""
|
|
1922
|
+
# First filter to only our test transform maps' fields
|
|
1923
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1924
|
+
|
|
1925
|
+
params = {"target_field": "location", "transform_map": test_maps}
|
|
1926
|
+
result = self.filterset(params, self.queryset).qs
|
|
1927
|
+
# rel_field1 and rel_field4 have location as target (from our test maps only)
|
|
1928
|
+
self.assertEqual(result.count(), 2)
|
|
1929
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1930
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1931
|
+
self.assertIn(self.rel_field4.pk, field_ids)
|
|
1932
|
+
|
|
1933
|
+
params = {"target_field": "site", "transform_map": test_maps}
|
|
1934
|
+
result = self.filterset(params, self.queryset).qs
|
|
1935
|
+
# rel_field2 has site as target (from our test maps only)
|
|
1936
|
+
self.assertEqual(result.count(), 1)
|
|
1937
|
+
self.assertEqual(result.first().pk, self.rel_field2.pk)
|
|
1938
|
+
|
|
1939
|
+
def test_coalesce(self):
|
|
1940
|
+
"""Test filtering by coalesce"""
|
|
1941
|
+
# First filter to only our test transform maps' fields
|
|
1942
|
+
test_maps = [self.map1.pk, self.map2.pk, self.map3.pk]
|
|
1943
|
+
|
|
1944
|
+
params = {"coalesce": True, "transform_map": test_maps}
|
|
1945
|
+
result = self.filterset(params, self.queryset).qs
|
|
1946
|
+
# rel_field2 has coalesce=True (from our test maps only)
|
|
1947
|
+
self.assertEqual(result.count(), 1)
|
|
1948
|
+
self.assertEqual(result.first().pk, self.rel_field2.pk)
|
|
1949
|
+
|
|
1950
|
+
params = {"coalesce": False, "transform_map": test_maps}
|
|
1951
|
+
result = self.filterset(params, self.queryset).qs
|
|
1952
|
+
# rel_field1, rel_field3, rel_field4 have coalesce=False (from our test maps only)
|
|
1953
|
+
self.assertEqual(result.count(), 3)
|
|
1954
|
+
field_ids = set(result.values_list("id", flat=True))
|
|
1955
|
+
self.assertIn(self.rel_field1.pk, field_ids)
|
|
1956
|
+
self.assertIn(self.rel_field3.pk, field_ids)
|
|
1957
|
+
self.assertIn(self.rel_field4.pk, field_ids)
|
|
1958
|
+
|
|
1959
|
+
def test_combined_filters(self):
|
|
1960
|
+
"""Test combining multiple filters"""
|
|
1961
|
+
# Test transform_map_id + source_model
|
|
1962
|
+
params = {
|
|
1963
|
+
"transform_map_id": [self.map1.pk],
|
|
1964
|
+
"source_model": self.location_ct.pk,
|
|
1965
|
+
}
|
|
1966
|
+
result = self.filterset(params, self.queryset).qs
|
|
1967
|
+
# Only rel_field1 has map1 and Location source
|
|
1968
|
+
self.assertEqual(result.count(), 1)
|
|
1969
|
+
self.assertEqual(result.first().pk, self.rel_field1.pk)
|
|
1970
|
+
|
|
1971
|
+
# Test transform_map_id + target_field
|
|
1972
|
+
params = {
|
|
1973
|
+
"transform_map_id": [self.map1.pk],
|
|
1974
|
+
"target_field": "location",
|
|
1975
|
+
}
|
|
1976
|
+
result = self.filterset(params, self.queryset).qs
|
|
1977
|
+
# Only rel_field1 has map1 and location target
|
|
1978
|
+
self.assertEqual(result.count(), 1)
|
|
1979
|
+
self.assertEqual(result.first().pk, self.rel_field1.pk)
|
|
1980
|
+
|
|
1981
|
+
# Test transform_map_id + coalesce
|
|
1982
|
+
params = {
|
|
1983
|
+
"transform_map_id": [self.map1.pk],
|
|
1984
|
+
"coalesce": True,
|
|
1985
|
+
}
|
|
1986
|
+
result = self.filterset(params, self.queryset).qs
|
|
1987
|
+
# Only rel_field2 has map1 and coalesce=True
|
|
1988
|
+
self.assertEqual(result.count(), 1)
|
|
1989
|
+
self.assertEqual(result.first().pk, self.rel_field2.pk)
|
|
1990
|
+
|
|
1991
|
+
def test_transform_map_filters_equivalence(self):
|
|
1992
|
+
"""Test that transform_map and transform_map_id work correctly"""
|
|
1993
|
+
# Single ID with transform_map
|
|
1994
|
+
params1 = {"transform_map": [self.map1.pk]}
|
|
1995
|
+
result1 = self.filterset(params1, self.queryset).qs
|
|
1996
|
+
|
|
1997
|
+
# Same ID with transform_map_id
|
|
1998
|
+
params2 = {"transform_map_id": [self.map1.pk]}
|
|
1999
|
+
result2 = self.filterset(params2, self.queryset).qs
|
|
2000
|
+
|
|
2001
|
+
# Should return same results
|
|
2002
|
+
self.assertEqual(result1.count(), result2.count())
|
|
2003
|
+
self.assertEqual(
|
|
2004
|
+
set(result1.values_list("id", flat=True)),
|
|
2005
|
+
set(result2.values_list("id", flat=True)),
|
|
2006
|
+
)
|
|
2007
|
+
|
|
2008
|
+
def test_distinct_results(self):
|
|
2009
|
+
"""Test that filters return distinct results"""
|
|
2010
|
+
# Query with transform_map_id (should have no duplicates)
|
|
2011
|
+
params = {"transform_map_id": [self.map1.pk]}
|
|
2012
|
+
result = self.filterset(params, self.queryset).qs
|
|
2013
|
+
|
|
2014
|
+
# Each relationship field should appear only once
|
|
2015
|
+
field_counts = {}
|
|
2016
|
+
for field in result:
|
|
2017
|
+
field_counts[field.pk] = field_counts.get(field.pk, 0) + 1
|
|
2018
|
+
|
|
2019
|
+
for count in field_counts.values():
|
|
2020
|
+
self.assertEqual(count, 1)
|
|
2021
|
+
|
|
2022
|
+
def test_empty_filters(self):
|
|
2023
|
+
"""Test behavior with empty filter values"""
|
|
2024
|
+
total_count = self.queryset.count()
|
|
2025
|
+
|
|
2026
|
+
params = {"transform_map": ""}
|
|
2027
|
+
result = self.filterset(params, self.queryset).qs
|
|
2028
|
+
self.assertEqual(result.count(), total_count)
|
|
2029
|
+
|
|
2030
|
+
params = {"target_field": ""}
|
|
2031
|
+
result = self.filterset(params, self.queryset).qs
|
|
2032
|
+
self.assertEqual(result.count(), total_count)
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
class IPFabricTransformMapGroupFilterSetTestCase(TestCase):
|
|
2036
|
+
filterset = IPFabricTransformMapGroupFilterSet
|
|
2037
|
+
|
|
2038
|
+
@classmethod
|
|
2039
|
+
def setUpTestData(cls):
|
|
2040
|
+
groups = [
|
|
2041
|
+
IPFabricTransformMapGroup(
|
|
2042
|
+
name="Group Alpha",
|
|
2043
|
+
description="First transform map group",
|
|
2044
|
+
),
|
|
2045
|
+
IPFabricTransformMapGroup(
|
|
2046
|
+
name="Group Beta",
|
|
2047
|
+
description="Second transform map group",
|
|
2048
|
+
),
|
|
2049
|
+
IPFabricTransformMapGroup(
|
|
2050
|
+
name="Group Gamma",
|
|
2051
|
+
description="Third group for testing",
|
|
2052
|
+
),
|
|
2053
|
+
]
|
|
2054
|
+
IPFabricTransformMapGroup.objects.bulk_create(groups)
|
|
2055
|
+
|
|
2056
|
+
@property
|
|
2057
|
+
def queryset(self):
|
|
2058
|
+
return IPFabricTransformMapGroup.objects.all()
|
|
2059
|
+
|
|
2060
|
+
def test_id(self):
|
|
2061
|
+
"""Test filtering by group ID"""
|
|
2062
|
+
group = IPFabricTransformMapGroup.objects.first()
|
|
2063
|
+
params = {"id": [group.pk]}
|
|
2064
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2065
|
+
|
|
2066
|
+
def test_name(self):
|
|
2067
|
+
"""Test filtering by name (exact match)"""
|
|
2068
|
+
params = {"name": ["Group Alpha"]}
|
|
2069
|
+
result = self.filterset(params, self.queryset).qs
|
|
2070
|
+
# Exact match should find exactly 1
|
|
2071
|
+
self.assertEqual(result.count(), 1)
|
|
2072
|
+
self.assertEqual(result.first().name, "Group Alpha")
|
|
2073
|
+
|
|
2074
|
+
def test_description(self):
|
|
2075
|
+
"""Test filtering by description (exact match)"""
|
|
2076
|
+
params = {"description": "First transform map group"}
|
|
2077
|
+
result = self.filterset(params, self.queryset).qs
|
|
2078
|
+
self.assertEqual(result.count(), 1)
|
|
2079
|
+
self.assertEqual(result.first().name, "Group Alpha")
|
|
2080
|
+
|
|
2081
|
+
def test_search(self):
|
|
2082
|
+
"""Test search across name and description"""
|
|
2083
|
+
params = {"q": "Alpha"}
|
|
2084
|
+
result = self.filterset(params, self.queryset).qs
|
|
2085
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2086
|
+
|
|
2087
|
+
params = {"q": "group"}
|
|
2088
|
+
result = self.filterset(params, self.queryset).qs
|
|
2089
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
class IPFabricSourceFilterSetTestCase(TestCase):
|
|
2093
|
+
filterset = IPFabricSourceFilterSet
|
|
2094
|
+
|
|
2095
|
+
@classmethod
|
|
2096
|
+
def setUpTestData(cls):
|
|
2097
|
+
sources = [
|
|
2098
|
+
IPFabricSource(
|
|
2099
|
+
name="Source Alpha",
|
|
2100
|
+
url="https://alpha.example.com",
|
|
2101
|
+
parameters={"auth": "token"},
|
|
2102
|
+
status="ready",
|
|
2103
|
+
description="Primary source",
|
|
2104
|
+
comments="Alpha comments",
|
|
2105
|
+
),
|
|
2106
|
+
IPFabricSource(
|
|
2107
|
+
name="Source Beta",
|
|
2108
|
+
url="https://beta.example.com",
|
|
2109
|
+
parameters={"auth": "basic"},
|
|
2110
|
+
status="error",
|
|
2111
|
+
description="Secondary source",
|
|
2112
|
+
comments="Beta notes",
|
|
2113
|
+
),
|
|
2114
|
+
IPFabricSource(
|
|
2115
|
+
name="Source Gamma",
|
|
2116
|
+
url="https://gamma.example.com",
|
|
2117
|
+
parameters={"verify": False},
|
|
2118
|
+
status="ready",
|
|
2119
|
+
description="Testing source",
|
|
2120
|
+
),
|
|
2121
|
+
]
|
|
2122
|
+
IPFabricSource.objects.bulk_create(sources)
|
|
2123
|
+
|
|
2124
|
+
@property
|
|
2125
|
+
def queryset(self):
|
|
2126
|
+
return IPFabricSource.objects.all()
|
|
2127
|
+
|
|
2128
|
+
def test_id(self):
|
|
2129
|
+
"""Test filtering by source ID"""
|
|
2130
|
+
source = IPFabricSource.objects.first()
|
|
2131
|
+
params = {"id": [source.pk]}
|
|
2132
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2133
|
+
|
|
2134
|
+
def test_name(self):
|
|
2135
|
+
"""Test filtering by name (exact match)"""
|
|
2136
|
+
params = {"name": ["Source Alpha"]}
|
|
2137
|
+
result = self.filterset(params, self.queryset).qs
|
|
2138
|
+
# Exact match should find exactly 1
|
|
2139
|
+
self.assertEqual(result.count(), 1)
|
|
2140
|
+
self.assertEqual(result.first().url, "https://alpha.example.com")
|
|
2141
|
+
|
|
2142
|
+
def test_status(self):
|
|
2143
|
+
"""Test filtering by status"""
|
|
2144
|
+
params = {"status": ["ready"]}
|
|
2145
|
+
result = self.filterset(params, self.queryset).qs
|
|
2146
|
+
# Should find at least our 2 test sources with ready status
|
|
2147
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
2148
|
+
|
|
2149
|
+
params = {"status": ["error"]}
|
|
2150
|
+
result = self.filterset(params, self.queryset).qs
|
|
2151
|
+
# Should find at least our 1 test source with error status
|
|
2152
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2153
|
+
|
|
2154
|
+
def test_search(self):
|
|
2155
|
+
"""Test search across name, description, and comments"""
|
|
2156
|
+
params = {"q": "Alpha"}
|
|
2157
|
+
result = self.filterset(params, self.queryset).qs
|
|
2158
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2159
|
+
|
|
2160
|
+
params = {"q": "source"}
|
|
2161
|
+
result = self.filterset(params, self.queryset).qs
|
|
2162
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
2163
|
+
|
|
2164
|
+
params = {"q": "notes"}
|
|
2165
|
+
result = self.filterset(params, self.queryset).qs
|
|
2166
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2167
|
+
|
|
2168
|
+
|
|
2169
|
+
class IPFabricSnapshotFilterSetTestCase(TestCase):
|
|
2170
|
+
filterset = IPFabricSnapshotFilterSet
|
|
2171
|
+
|
|
2172
|
+
@classmethod
|
|
2173
|
+
def setUpTestData(cls):
|
|
2174
|
+
source1 = IPFabricSource.objects.create(
|
|
2175
|
+
name="Snapshot Test Source 1",
|
|
2176
|
+
url="https://source1.example.com",
|
|
2177
|
+
parameters={"auth": "token"},
|
|
2178
|
+
)
|
|
2179
|
+
source2 = IPFabricSource.objects.create(
|
|
2180
|
+
name="Snapshot Test Source 2",
|
|
2181
|
+
url="https://source2.example.com",
|
|
2182
|
+
parameters={"auth": "basic"},
|
|
2183
|
+
)
|
|
2184
|
+
|
|
2185
|
+
cls.snapshots = [
|
|
2186
|
+
IPFabricSnapshot(
|
|
2187
|
+
name="Snapshot Alpha",
|
|
2188
|
+
source=source1,
|
|
2189
|
+
snapshot_id="snap-alpha-001",
|
|
2190
|
+
status="loaded",
|
|
2191
|
+
data={"sites": ["SiteA"]},
|
|
2192
|
+
),
|
|
2193
|
+
IPFabricSnapshot(
|
|
2194
|
+
name="Snapshot Beta",
|
|
2195
|
+
source=source1,
|
|
2196
|
+
snapshot_id="snap-beta-002",
|
|
2197
|
+
status="unloaded",
|
|
2198
|
+
data={"sites": ["SiteB"]},
|
|
2199
|
+
),
|
|
2200
|
+
IPFabricSnapshot(
|
|
2201
|
+
name="Snapshot Gamma",
|
|
2202
|
+
source=source2,
|
|
2203
|
+
snapshot_id="snap-gamma-003",
|
|
2204
|
+
status="loaded",
|
|
2205
|
+
data={"sites": ["SiteC"]},
|
|
2206
|
+
),
|
|
2207
|
+
]
|
|
2208
|
+
IPFabricSnapshot.objects.bulk_create(cls.snapshots)
|
|
2209
|
+
|
|
2210
|
+
@property
|
|
2211
|
+
def queryset(self):
|
|
2212
|
+
return IPFabricSnapshot.objects.all()
|
|
2213
|
+
|
|
2214
|
+
def test_id(self):
|
|
2215
|
+
"""Test filtering by snapshot ID"""
|
|
2216
|
+
snapshot = IPFabricSnapshot.objects.first()
|
|
2217
|
+
params = {"id": [snapshot.pk]}
|
|
2218
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2219
|
+
|
|
2220
|
+
def test_name(self):
|
|
2221
|
+
"""Test filtering by name (exact match)"""
|
|
2222
|
+
params = {"name": ["Snapshot Alpha"]}
|
|
2223
|
+
result = self.filterset(params, self.queryset).qs
|
|
2224
|
+
# Exact match should find exactly 1
|
|
2225
|
+
self.assertEqual(result.count(), 1)
|
|
2226
|
+
self.assertEqual(result.first().snapshot_id, "snap-alpha-001")
|
|
2227
|
+
|
|
2228
|
+
def test_status(self):
|
|
2229
|
+
"""Test filtering by status"""
|
|
2230
|
+
params = {"status": "loaded"}
|
|
2231
|
+
result = self.filterset(params, self.queryset).qs
|
|
2232
|
+
# Should find at least our 2 test snapshots with loaded status
|
|
2233
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
2234
|
+
|
|
2235
|
+
def test_snapshot_id(self):
|
|
2236
|
+
"""Test filtering by snapshot_id with icontains"""
|
|
2237
|
+
params = {"snapshot_id": "alpha"}
|
|
2238
|
+
result = self.filterset(params, self.queryset).qs
|
|
2239
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2240
|
+
|
|
2241
|
+
params = {"snapshot_id": "snap"}
|
|
2242
|
+
result = self.filterset(params, self.queryset).qs
|
|
2243
|
+
self.assertGreaterEqual(result.count(), 3)
|
|
2244
|
+
|
|
2245
|
+
def test_source_id(self):
|
|
2246
|
+
"""Test filtering by source ID"""
|
|
2247
|
+
source = IPFabricSource.objects.get(name="Snapshot Test Source 1")
|
|
2248
|
+
params = {"source_id": [source.pk]}
|
|
2249
|
+
result = self.filterset(params, self.queryset).qs
|
|
2250
|
+
self.assertGreaterEqual(result.count(), 2)
|
|
2251
|
+
|
|
2252
|
+
def test_source_name(self):
|
|
2253
|
+
"""Test filtering by source name"""
|
|
2254
|
+
source = IPFabricSource.objects.get(name="Snapshot Test Source 2")
|
|
2255
|
+
params = {"source": [source.pk]}
|
|
2256
|
+
result = self.filterset(params, self.queryset).qs
|
|
2257
|
+
# Should find at least our one snapshot for this source
|
|
2258
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2259
|
+
# Verify it's the right snapshot
|
|
2260
|
+
self.assertTrue(result.filter(snapshot_id="snap-gamma-003").exists())
|
|
2261
|
+
|
|
2262
|
+
def test_search(self):
|
|
2263
|
+
"""Test search functionality"""
|
|
2264
|
+
params = {"q": "Alpha"}
|
|
2265
|
+
result = self.filterset(params, self.queryset).qs
|
|
2266
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2267
|
+
|
|
2268
|
+
|
|
2269
|
+
class IPFabricDataFilterSetTestCase(TestCase):
|
|
2270
|
+
filterset = IPFabricDataFilterSet
|
|
2271
|
+
|
|
2272
|
+
@classmethod
|
|
2273
|
+
def setUpTestData(cls):
|
|
2274
|
+
from ipfabric_netbox.models import IPFabricData
|
|
2275
|
+
|
|
2276
|
+
source = IPFabricSource.objects.create(
|
|
2277
|
+
name="Data Test Source",
|
|
2278
|
+
url="https://data.example.com",
|
|
2279
|
+
parameters={},
|
|
2280
|
+
)
|
|
2281
|
+
cls.snapshot = IPFabricSnapshot.objects.create(
|
|
2282
|
+
name="Data Test Snapshot",
|
|
2283
|
+
source=source,
|
|
2284
|
+
snapshot_id="snap-data-001",
|
|
2285
|
+
status="loaded",
|
|
2286
|
+
)
|
|
2287
|
+
|
|
2288
|
+
data_items = [
|
|
2289
|
+
IPFabricData(
|
|
2290
|
+
snapshot_data=cls.snapshot,
|
|
2291
|
+
data={"device": "router1", "ip": "10.0.0.1"},
|
|
2292
|
+
),
|
|
2293
|
+
IPFabricData(
|
|
2294
|
+
snapshot_data=cls.snapshot,
|
|
2295
|
+
data={"device": "switch1", "ip": "10.0.0.2"},
|
|
2296
|
+
),
|
|
2297
|
+
IPFabricData(
|
|
2298
|
+
snapshot_data=cls.snapshot,
|
|
2299
|
+
data={"device": "firewall1", "ip": "10.0.0.3"},
|
|
2300
|
+
),
|
|
2301
|
+
]
|
|
2302
|
+
IPFabricData.objects.bulk_create(data_items)
|
|
2303
|
+
|
|
2304
|
+
@property
|
|
2305
|
+
def queryset(self):
|
|
2306
|
+
return IPFabricData.objects.all()
|
|
2307
|
+
|
|
2308
|
+
def test_snapshot_data(self):
|
|
2309
|
+
"""Test filtering by snapshot_data"""
|
|
2310
|
+
params = {"snapshot_data": self.snapshot.pk}
|
|
2311
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
2312
|
+
|
|
2313
|
+
def test_search(self):
|
|
2314
|
+
"""Test search functionality"""
|
|
2315
|
+
# The search method filters on snapshot_data__name
|
|
2316
|
+
params = {"q": "Data Test Snapshot"}
|
|
2317
|
+
result = self.filterset(params, self.queryset).qs
|
|
2318
|
+
# Should find all data items for our snapshot
|
|
2319
|
+
self.assertEqual(result.count(), 3)
|
|
2320
|
+
|
|
2321
|
+
|
|
2322
|
+
class IPFabricIngestionFilterSetTestCase(TestCase):
|
|
2323
|
+
filterset = IPFabricIngestionFilterSet
|
|
2324
|
+
|
|
2325
|
+
@classmethod
|
|
2326
|
+
def setUpTestData(cls):
|
|
2327
|
+
# Create sources
|
|
2328
|
+
source1 = IPFabricSource.objects.create(
|
|
2329
|
+
name="Ingestion Test Source 1",
|
|
2330
|
+
url="https://ing1.example.com",
|
|
2331
|
+
parameters={},
|
|
2332
|
+
)
|
|
2333
|
+
source2 = IPFabricSource.objects.create(
|
|
2334
|
+
name="Ingestion Test Source 2",
|
|
2335
|
+
url="https://ing2.example.com",
|
|
2336
|
+
parameters={},
|
|
2337
|
+
)
|
|
2338
|
+
|
|
2339
|
+
# Create snapshots
|
|
2340
|
+
snapshot1 = IPFabricSnapshot.objects.create(
|
|
2341
|
+
name="Ingestion Snapshot 1",
|
|
2342
|
+
source=source1,
|
|
2343
|
+
snapshot_id="ing-snap-1",
|
|
2344
|
+
status="loaded",
|
|
2345
|
+
)
|
|
2346
|
+
snapshot2 = IPFabricSnapshot.objects.create(
|
|
2347
|
+
name="Ingestion Snapshot 2",
|
|
2348
|
+
source=source2,
|
|
2349
|
+
snapshot_id="ing-snap-2",
|
|
2350
|
+
status="loaded",
|
|
2351
|
+
)
|
|
2352
|
+
|
|
2353
|
+
# Create syncs
|
|
2354
|
+
cls.sync1 = IPFabricSync.objects.create(
|
|
2355
|
+
name="Ingestion Sync 1",
|
|
2356
|
+
snapshot_data=snapshot1,
|
|
2357
|
+
parameters={},
|
|
2358
|
+
)
|
|
2359
|
+
cls.sync2 = IPFabricSync.objects.create(
|
|
2360
|
+
name="Ingestion Sync 2",
|
|
2361
|
+
snapshot_data=snapshot2,
|
|
2362
|
+
parameters={},
|
|
2363
|
+
)
|
|
2364
|
+
|
|
2365
|
+
# Create branches - need unique branches since branch is OneToOneField
|
|
2366
|
+
cls.branch1 = Branch.objects.create(name="Ingestion Branch 1")
|
|
2367
|
+
cls.branch2 = Branch.objects.create(name="Ingestion Branch 2")
|
|
2368
|
+
cls.branch3 = Branch.objects.create(name="Ingestion Branch 3")
|
|
2369
|
+
|
|
2370
|
+
# Create ingestions - each needs its own unique branch
|
|
2371
|
+
cls.ingestions = [
|
|
2372
|
+
IPFabricIngestion(
|
|
2373
|
+
sync=cls.sync1,
|
|
2374
|
+
branch=cls.branch1,
|
|
2375
|
+
),
|
|
2376
|
+
IPFabricIngestion(
|
|
2377
|
+
sync=cls.sync1,
|
|
2378
|
+
branch=cls.branch2,
|
|
2379
|
+
),
|
|
2380
|
+
IPFabricIngestion(
|
|
2381
|
+
sync=cls.sync2,
|
|
2382
|
+
branch=cls.branch3,
|
|
2383
|
+
),
|
|
2384
|
+
]
|
|
2385
|
+
IPFabricIngestion.objects.bulk_create(cls.ingestions)
|
|
2386
|
+
|
|
2387
|
+
@property
|
|
2388
|
+
def queryset(self):
|
|
2389
|
+
return IPFabricIngestion.objects.all()
|
|
2390
|
+
|
|
2391
|
+
def test_id(self):
|
|
2392
|
+
"""Test filtering by ingestion ID"""
|
|
2393
|
+
ingestion = IPFabricIngestion.objects.first()
|
|
2394
|
+
params = {"id": [ingestion.pk]}
|
|
2395
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2396
|
+
|
|
2397
|
+
def test_branch(self):
|
|
2398
|
+
"""Test filtering by branch"""
|
|
2399
|
+
params = {"branch": self.branch1.pk}
|
|
2400
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2401
|
+
|
|
2402
|
+
def test_sync_id(self):
|
|
2403
|
+
"""Test filtering by sync ID"""
|
|
2404
|
+
params = {"sync_id": [self.sync1.pk]}
|
|
2405
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
2406
|
+
|
|
2407
|
+
def test_search(self):
|
|
2408
|
+
"""Test search functionality"""
|
|
2409
|
+
params = {"q": "Ingestion Branch 1"}
|
|
2410
|
+
result = self.filterset(params, self.queryset).qs
|
|
2411
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2412
|
+
|
|
2413
|
+
|
|
2414
|
+
class IPFabricIngestionIssueFilterSetTestCase(TestCase):
|
|
2415
|
+
filterset = IPFabricIngestionIssueFilterSet
|
|
2416
|
+
|
|
2417
|
+
@classmethod
|
|
2418
|
+
def setUpTestData(cls):
|
|
2419
|
+
source = IPFabricSource.objects.create(
|
|
2420
|
+
name="Issue Test Source",
|
|
2421
|
+
url="https://issue.example.com",
|
|
2422
|
+
parameters={},
|
|
2423
|
+
)
|
|
2424
|
+
snapshot = IPFabricSnapshot.objects.create(
|
|
2425
|
+
name="Issue Snapshot",
|
|
2426
|
+
source=source,
|
|
2427
|
+
snapshot_id="issue-snap-1",
|
|
2428
|
+
status="loaded",
|
|
2429
|
+
)
|
|
2430
|
+
sync = IPFabricSync.objects.create(
|
|
2431
|
+
name="Issue Sync",
|
|
2432
|
+
snapshot_data=snapshot,
|
|
2433
|
+
parameters={},
|
|
2434
|
+
)
|
|
2435
|
+
branch = Branch.objects.create(name="Issue Branch")
|
|
2436
|
+
ingestion = IPFabricIngestion.objects.create(
|
|
2437
|
+
sync=sync,
|
|
2438
|
+
branch=branch,
|
|
2439
|
+
)
|
|
2440
|
+
|
|
2441
|
+
cls.issues = [
|
|
2442
|
+
IPFabricIngestionIssue(
|
|
2443
|
+
ingestion=ingestion,
|
|
2444
|
+
model="dcim.Device",
|
|
2445
|
+
raw_data={"hostname": "device1"},
|
|
2446
|
+
coalesce_fields=["name"],
|
|
2447
|
+
defaults={"status": "active"},
|
|
2448
|
+
exception="ValueError",
|
|
2449
|
+
message="Device validation error",
|
|
2450
|
+
),
|
|
2451
|
+
IPFabricIngestionIssue(
|
|
2452
|
+
ingestion=ingestion,
|
|
2453
|
+
model="ipam.IPAddress",
|
|
2454
|
+
raw_data={"address": "10.0.0.1"},
|
|
2455
|
+
coalesce_fields=["address"],
|
|
2456
|
+
defaults={"status": "active"},
|
|
2457
|
+
exception="IntegrityError",
|
|
2458
|
+
message="Duplicate IP address",
|
|
2459
|
+
),
|
|
2460
|
+
IPFabricIngestionIssue(
|
|
2461
|
+
ingestion=ingestion,
|
|
2462
|
+
model="dcim.Interface",
|
|
2463
|
+
raw_data={"name": "eth0"},
|
|
2464
|
+
coalesce_fields=["name", "device"],
|
|
2465
|
+
defaults={},
|
|
2466
|
+
exception="KeyError",
|
|
2467
|
+
message="Missing device reference",
|
|
2468
|
+
),
|
|
2469
|
+
]
|
|
2470
|
+
IPFabricIngestionIssue.objects.bulk_create(cls.issues)
|
|
2471
|
+
|
|
2472
|
+
@property
|
|
2473
|
+
def queryset(self):
|
|
2474
|
+
return IPFabricIngestionIssue.objects.all()
|
|
2475
|
+
|
|
2476
|
+
def test_model(self):
|
|
2477
|
+
"""Test filtering by model (exact match)"""
|
|
2478
|
+
# First verify we have test data
|
|
2479
|
+
total_issues = self.queryset.count()
|
|
2480
|
+
self.assertGreater(total_issues, 0, "No ingestion issues found in queryset")
|
|
2481
|
+
|
|
2482
|
+
# Check what models exist
|
|
2483
|
+
models_in_db = list(self.queryset.values_list("model", flat=True))
|
|
2484
|
+
self.assertIn("dcim.Device", models_in_db, f"dcim.Device not in {models_in_db}")
|
|
2485
|
+
|
|
2486
|
+
params = {"model": ["dcim.Device"]}
|
|
2487
|
+
result = self.filterset(params, self.queryset).qs
|
|
2488
|
+
# Should find exactly one device issue
|
|
2489
|
+
self.assertEqual(result.count(), 1)
|
|
2490
|
+
self.assertEqual(result.first().model, "dcim.Device")
|
|
2491
|
+
|
|
2492
|
+
def test_exception(self):
|
|
2493
|
+
"""Test filtering by exception (exact match)"""
|
|
2494
|
+
params = {"exception": "ValueError"}
|
|
2495
|
+
result = self.filterset(params, self.queryset).qs
|
|
2496
|
+
self.assertEqual(result.count(), 1)
|
|
2497
|
+
self.assertEqual(result.first().model, "dcim.Device")
|
|
2498
|
+
|
|
2499
|
+
def test_message(self):
|
|
2500
|
+
"""Test filtering by message (exact match)"""
|
|
2501
|
+
params = {"message": "Device validation error"}
|
|
2502
|
+
result = self.filterset(params, self.queryset).qs
|
|
2503
|
+
self.assertEqual(result.count(), 1)
|
|
2504
|
+
|
|
2505
|
+
def test_search(self):
|
|
2506
|
+
"""Test search functionality"""
|
|
2507
|
+
params = {"q": "Device"}
|
|
2508
|
+
result = self.filterset(params, self.queryset).qs
|
|
2509
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2510
|
+
|
|
2511
|
+
params = {"q": "ValueError"}
|
|
2512
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2513
|
+
|
|
2514
|
+
params = {"q": "duplicate"}
|
|
2515
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2516
|
+
|
|
2517
|
+
|
|
2518
|
+
class IPFabricIngestionChangeFilterSetTestCase(TestCase):
|
|
2519
|
+
filterset = IPFabricIngestionChangeFilterSet
|
|
2520
|
+
|
|
2521
|
+
@classmethod
|
|
2522
|
+
def setUpTestData(cls):
|
|
2523
|
+
# Create a branch
|
|
2524
|
+
cls.branch = Branch.objects.create(name="Change Test Branch")
|
|
2525
|
+
|
|
2526
|
+
# Get content type for Device
|
|
2527
|
+
cls.device_ct = ContentType.objects.get(app_label="dcim", model="device")
|
|
2528
|
+
cls.site_ct = ContentType.objects.get(app_label="dcim", model="site")
|
|
2529
|
+
|
|
2530
|
+
# Create change diffs
|
|
2531
|
+
cls.changes = [
|
|
2532
|
+
ChangeDiff(
|
|
2533
|
+
branch=cls.branch,
|
|
2534
|
+
object_type=cls.device_ct,
|
|
2535
|
+
object_id=1,
|
|
2536
|
+
action="create",
|
|
2537
|
+
current={"name": "device1", "status": "active"},
|
|
2538
|
+
modified={},
|
|
2539
|
+
original={},
|
|
2540
|
+
),
|
|
2541
|
+
ChangeDiff(
|
|
2542
|
+
branch=cls.branch,
|
|
2543
|
+
object_type=cls.device_ct,
|
|
2544
|
+
object_id=2,
|
|
2545
|
+
action="update",
|
|
2546
|
+
current={"name": "device2", "status": "planned"},
|
|
2547
|
+
modified={"status": "active"},
|
|
2548
|
+
original={"name": "device2", "status": "planned"},
|
|
2549
|
+
),
|
|
2550
|
+
ChangeDiff(
|
|
2551
|
+
branch=cls.branch,
|
|
2552
|
+
object_type=cls.site_ct,
|
|
2553
|
+
object_id=1,
|
|
2554
|
+
action="delete",
|
|
2555
|
+
current={},
|
|
2556
|
+
modified={},
|
|
2557
|
+
original={"name": "site1"},
|
|
2558
|
+
),
|
|
2559
|
+
]
|
|
2560
|
+
ChangeDiff.objects.bulk_create(cls.changes)
|
|
2561
|
+
|
|
2562
|
+
@property
|
|
2563
|
+
def queryset(self):
|
|
2564
|
+
return ChangeDiff.objects.all()
|
|
2565
|
+
|
|
2566
|
+
def test_branch(self):
|
|
2567
|
+
"""Test filtering by branch"""
|
|
2568
|
+
params = {"branch": self.branch.pk}
|
|
2569
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
2570
|
+
|
|
2571
|
+
def test_action(self):
|
|
2572
|
+
"""Test filtering by action"""
|
|
2573
|
+
params = {"action": ["create"]}
|
|
2574
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
2575
|
+
|
|
2576
|
+
params = {"action": ["update", "delete"]}
|
|
2577
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
2578
|
+
|
|
2579
|
+
def test_object_type(self):
|
|
2580
|
+
"""Test filtering by object_type"""
|
|
2581
|
+
params = {"object_type": self.device_ct.pk}
|
|
2582
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
2583
|
+
|
|
2584
|
+
def test_search(self):
|
|
2585
|
+
"""Test search functionality"""
|
|
2586
|
+
params = {"q": "device1"}
|
|
2587
|
+
result = self.filterset(params, self.queryset).qs
|
|
2588
|
+
self.assertGreaterEqual(result.count(), 1)
|
|
2589
|
+
|
|
2590
|
+
params = {"q": "create"}
|
|
2591
|
+
result = self.filterset(params, self.queryset).qs
|
|
2592
|
+
self.assertGreaterEqual(result.count(), 1)
|