ipfabric_netbox 4.2.2__py3-none-any.whl → 4.2.2b1__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.

Files changed (30) hide show
  1. ipfabric_netbox/__init__.py +1 -1
  2. ipfabric_netbox/api/__init__.py +0 -1
  3. ipfabric_netbox/api/serializers.py +147 -90
  4. ipfabric_netbox/api/urls.py +4 -4
  5. ipfabric_netbox/api/views.py +18 -19
  6. ipfabric_netbox/choices.py +0 -12
  7. ipfabric_netbox/filtersets.py +67 -4
  8. ipfabric_netbox/forms.py +99 -140
  9. ipfabric_netbox/graphql/__init__.py +23 -0
  10. ipfabric_netbox/graphql/enums.py +35 -0
  11. ipfabric_netbox/graphql/filters.py +317 -0
  12. ipfabric_netbox/graphql/schema.py +101 -0
  13. ipfabric_netbox/graphql/types.py +216 -0
  14. ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +31 -0
  15. ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +17 -0
  16. ipfabric_netbox/migrations/0018_remove_type_field.py +17 -0
  17. ipfabric_netbox/models.py +10 -10
  18. ipfabric_netbox/tables.py +30 -9
  19. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +1 -1
  20. ipfabric_netbox/tests/api/__init__.py +0 -0
  21. ipfabric_netbox/tests/api/test_api.py +879 -0
  22. ipfabric_netbox/tests/test_forms.py +1440 -0
  23. ipfabric_netbox/tests/test_models.py +47 -11
  24. ipfabric_netbox/utilities/ipfutils.py +43 -23
  25. ipfabric_netbox/views.py +6 -8
  26. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b1.dist-info}/METADATA +8 -7
  27. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b1.dist-info}/RECORD +28 -19
  28. {ipfabric_netbox-4.2.2.dist-info → ipfabric_netbox-4.2.2b1.dist-info}/WHEEL +1 -1
  29. ipfabric_netbox/api/nested_serializers.py +0 -78
  30. ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +0 -71
@@ -0,0 +1,879 @@
1
+ from django.contrib.contenttypes.models import ContentType
2
+ from django.utils import timezone
3
+ from rest_framework import status
4
+ from utilities.testing import APIViewTestCases
5
+
6
+ from ipfabric_netbox.models import IPFabricData
7
+ from ipfabric_netbox.models import IPFabricIngestion
8
+ from ipfabric_netbox.models import IPFabricIngestionIssue
9
+ from ipfabric_netbox.models import IPFabricRelationshipField
10
+ from ipfabric_netbox.models import IPFabricSnapshot
11
+ from ipfabric_netbox.models import IPFabricSource
12
+ from ipfabric_netbox.models import IPFabricSync
13
+ from ipfabric_netbox.models import IPFabricTransformField
14
+ from ipfabric_netbox.models import IPFabricTransformMap
15
+ from ipfabric_netbox.models import IPFabricTransformMapGroup
16
+
17
+
18
+ BASE = "/api/plugins/ipfabric/"
19
+
20
+
21
+ class IPFabricTransformMapGroupTest(APIViewTestCases.APIViewTestCase):
22
+ model = IPFabricTransformMapGroup
23
+ graphql_base_name = "ipfabric_transform_map_group"
24
+ brief_fields = [
25
+ "description",
26
+ "id",
27
+ "name",
28
+ ]
29
+ create_data = [
30
+ {"name": "Group A"},
31
+ {"name": "Group B", "description": "Description of group B"},
32
+ {"name": "Group C"},
33
+ ]
34
+ bulk_update_data = {
35
+ "description": "Updated Group Description",
36
+ }
37
+
38
+ def _get_list_url(self):
39
+ return f"{BASE}transform-map-group/"
40
+
41
+ def _get_detail_url(self, instance):
42
+ return f"{BASE}transform-map-group/{instance.pk}/"
43
+
44
+ @classmethod
45
+ def setUpTestData(cls):
46
+ IPFabricTransformMapGroup.objects.create(name="Group D")
47
+ IPFabricTransformMapGroup.objects.create(
48
+ name="Group E", description="Description of group E"
49
+ )
50
+ IPFabricTransformMapGroup.objects.create(name="Group F")
51
+
52
+
53
+ class IPFabricTransformMapTest(APIViewTestCases.APIViewTestCase):
54
+ model = IPFabricTransformMap
55
+ graphql_base_name = "ipfabric_transform_map"
56
+ brief_fields = [
57
+ "group",
58
+ "id",
59
+ "name",
60
+ "source_model",
61
+ "target_model",
62
+ ]
63
+
64
+ def _get_list_url(self):
65
+ return f"{BASE}transform-map/"
66
+
67
+ def _get_detail_url(self, instance):
68
+ return f"{BASE}transform-map/{instance.pk}/"
69
+
70
+ @classmethod
71
+ def setUpTestData(cls):
72
+ groups = (
73
+ IPFabricTransformMapGroup.objects.create(name="Group A"),
74
+ IPFabricTransformMapGroup.objects.create(name="Group B"),
75
+ IPFabricTransformMapGroup.objects.create(name="Group C"),
76
+ )
77
+
78
+ IPFabricTransformMap.objects.create(
79
+ name="TransformMap D",
80
+ source_model="site",
81
+ target_model=ContentType.objects.get(app_label="dcim", model="site"),
82
+ )
83
+ IPFabricTransformMap.objects.create(
84
+ name="TransformMap E",
85
+ source_model="site",
86
+ target_model=ContentType.objects.get(app_label="dcim", model="site"),
87
+ group=groups[0],
88
+ )
89
+ IPFabricTransformMap.objects.create(
90
+ name="TransformMap F",
91
+ source_model="ipaddress",
92
+ target_model=ContentType.objects.get(app_label="ipam", model="ipaddress"),
93
+ group=groups[0],
94
+ )
95
+
96
+ cls.create_data = [
97
+ {
98
+ "name": "Transform Map A",
99
+ "source_model": "site",
100
+ "target_model": "dcim.site",
101
+ "group": groups[1].pk,
102
+ },
103
+ {
104
+ "name": "Transform Map B",
105
+ "source_model": "device",
106
+ "target_model": "dcim.device",
107
+ "group": groups[1].pk,
108
+ },
109
+ {
110
+ "name": "Transform Map C",
111
+ "source_model": "vrf",
112
+ "target_model": "ipam.vrf",
113
+ "group": groups[1].pk,
114
+ },
115
+ ]
116
+ cls.bulk_update_data = {
117
+ "group": groups[2].pk,
118
+ }
119
+
120
+
121
+ class IPFabricTransformFieldTest(APIViewTestCases.APIViewTestCase):
122
+ model = IPFabricTransformField
123
+ graphql_base_name = "ipfabric_transform_field"
124
+ # in this case brief fields are the same, but they are needed fot the test
125
+ brief_fields = [
126
+ "coalesce",
127
+ "id",
128
+ "source_field",
129
+ "target_field",
130
+ "template",
131
+ "transform_map",
132
+ ]
133
+
134
+ def _get_list_url(self):
135
+ return f"{BASE}transform-field/"
136
+
137
+ def _get_detail_url(self, instance):
138
+ return f"{BASE}transform-field/{instance.pk}/"
139
+
140
+ @classmethod
141
+ def setUpTestData(cls):
142
+ # Create groups for transform maps
143
+ groups = (
144
+ IPFabricTransformMapGroup.objects.create(name="Field Test Group A"),
145
+ IPFabricTransformMapGroup.objects.create(name="Field Test Group B"),
146
+ IPFabricTransformMapGroup.objects.create(name="Field Test Group C"),
147
+ )
148
+
149
+ # Create transform maps for the fields to reference
150
+ transform_maps = [
151
+ IPFabricTransformMap.objects.create(
152
+ name="Field Map A",
153
+ source_model="site",
154
+ target_model=ContentType.objects.get(app_label="dcim", model="site"),
155
+ group=groups[0],
156
+ ),
157
+ IPFabricTransformMap.objects.create(
158
+ name="Field Map B",
159
+ source_model="device",
160
+ target_model=ContentType.objects.get(app_label="dcim", model="device"),
161
+ group=groups[0],
162
+ ),
163
+ IPFabricTransformMap.objects.create(
164
+ name="Field Map C",
165
+ source_model="ipaddress",
166
+ target_model=ContentType.objects.get(
167
+ app_label="ipam", model="ipaddress"
168
+ ),
169
+ group=groups[1],
170
+ ),
171
+ ]
172
+
173
+ # Create existing transform fields for testing
174
+ IPFabricTransformField.objects.create(
175
+ transform_map=transform_maps[0],
176
+ source_field="hostname",
177
+ target_field="name",
178
+ coalesce=False,
179
+ template="{{ hostname }}",
180
+ )
181
+ IPFabricTransformField.objects.create(
182
+ transform_map=transform_maps[0],
183
+ source_field="site_name",
184
+ target_field="description",
185
+ coalesce=True,
186
+ template="Site: {{ site_name }}",
187
+ )
188
+ IPFabricTransformField.objects.create(
189
+ transform_map=transform_maps[1],
190
+ source_field="device_type",
191
+ target_field="platform",
192
+ coalesce=False,
193
+ template="",
194
+ )
195
+
196
+ cls.create_data = [
197
+ {
198
+ "transform_map": transform_maps[1].pk,
199
+ "source_field": "ip_address",
200
+ "target_field": "primary_ip4",
201
+ "coalesce": False,
202
+ "template": "{{ ip_address }}",
203
+ },
204
+ {
205
+ "transform_map": transform_maps[1].pk,
206
+ "source_field": "location",
207
+ "target_field": "site",
208
+ "coalesce": True,
209
+ "template": "{{ location | default('Unknown') }}",
210
+ },
211
+ {
212
+ "transform_map": transform_maps[2].pk,
213
+ "source_field": "subnet",
214
+ "target_field": "address",
215
+ "coalesce": False,
216
+ "template": "{{ subnet }}",
217
+ },
218
+ ]
219
+ cls.bulk_update_data = {
220
+ "coalesce": True,
221
+ }
222
+
223
+
224
+ class IPFabricRelationshipFieldTest(APIViewTestCases.APIViewTestCase):
225
+ model = IPFabricRelationshipField
226
+ graphql_base_name = "ipfabric_relationship_field"
227
+ # in this case brief fields are the same, but they are needed fot the test
228
+ brief_fields = [
229
+ "coalesce",
230
+ "id",
231
+ "source_model",
232
+ "target_field",
233
+ "template",
234
+ "transform_map",
235
+ ]
236
+
237
+ def _get_list_url(self):
238
+ return f"{BASE}relationship-field/"
239
+
240
+ def _get_detail_url(self, instance):
241
+ return f"{BASE}relationship-field/{instance.pk}/"
242
+
243
+ @classmethod
244
+ def setUpTestData(cls):
245
+ # Create groups for transform maps
246
+ groups = (
247
+ IPFabricTransformMapGroup.objects.create(name="Relationship Test Group A"),
248
+ IPFabricTransformMapGroup.objects.create(name="Relationship Test Group B"),
249
+ IPFabricTransformMapGroup.objects.create(name="Relationship Test Group C"),
250
+ )
251
+
252
+ # Create transform maps for the relationship fields to reference
253
+ transform_maps = [
254
+ IPFabricTransformMap.objects.create(
255
+ name="Relationship Map A",
256
+ source_model="site",
257
+ target_model=ContentType.objects.get(app_label="dcim", model="site"),
258
+ group=groups[0],
259
+ ),
260
+ IPFabricTransformMap.objects.create(
261
+ name="Relationship Map B",
262
+ source_model="device",
263
+ target_model=ContentType.objects.get(app_label="dcim", model="device"),
264
+ group=groups[0],
265
+ ),
266
+ IPFabricTransformMap.objects.create(
267
+ name="Relationship Map C",
268
+ source_model="ipaddress",
269
+ target_model=ContentType.objects.get(
270
+ app_label="ipam", model="ipaddress"
271
+ ),
272
+ group=groups[1],
273
+ ),
274
+ ]
275
+
276
+ # Create existing relationship fields for testing
277
+ IPFabricRelationshipField.objects.create(
278
+ transform_map=transform_maps[0],
279
+ source_model=ContentType.objects.get(app_label="dcim", model="site"),
280
+ target_field="location",
281
+ coalesce=False,
282
+ template="{{ site.location_id }}",
283
+ )
284
+ IPFabricRelationshipField.objects.create(
285
+ transform_map=transform_maps[0],
286
+ source_model=ContentType.objects.get(app_label="dcim", model="device"),
287
+ target_field="site",
288
+ coalesce=True,
289
+ template="{{ device.site_id }}",
290
+ )
291
+ IPFabricRelationshipField.objects.create(
292
+ transform_map=transform_maps[1],
293
+ source_model=ContentType.objects.get(app_label="ipam", model="ipaddress"),
294
+ target_field="interface",
295
+ coalesce=False,
296
+ template="{{ ipaddress.interface_id }}",
297
+ )
298
+
299
+ cls.create_data = [
300
+ {
301
+ "transform_map": transform_maps[1].pk,
302
+ "source_model": "dcim.device",
303
+ "target_field": "primary_ip4",
304
+ "coalesce": False,
305
+ "template": "{{ device.primary_ip4_id }}",
306
+ },
307
+ {
308
+ "transform_map": transform_maps[1].pk,
309
+ "source_model": "dcim.interface",
310
+ "target_field": "device",
311
+ "coalesce": True,
312
+ "template": "{{ interface.device_id }}",
313
+ },
314
+ {
315
+ "transform_map": transform_maps[2].pk,
316
+ "source_model": "ipam.prefix",
317
+ "target_field": "vrf",
318
+ "coalesce": False,
319
+ "template": "{{ prefix.vrf_id }}",
320
+ },
321
+ ]
322
+ cls.bulk_update_data = {
323
+ "coalesce": True,
324
+ }
325
+
326
+
327
+ class IPFabricSourceTest(APIViewTestCases.APIViewTestCase):
328
+ model = IPFabricSource
329
+ brief_fields = [
330
+ "display",
331
+ "id",
332
+ "name",
333
+ "status",
334
+ "type",
335
+ "url",
336
+ ]
337
+ bulk_update_data = {
338
+ "url": "https://updated.local",
339
+ }
340
+ graphql_base_name = "ipfabric_source"
341
+
342
+ def _get_list_url(self):
343
+ return f"{BASE}source/"
344
+
345
+ def _get_detail_url(self, instance):
346
+ return f"{BASE}source/{instance.pk}/"
347
+
348
+ @classmethod
349
+ def setUpTestData(cls):
350
+ IPFabricSource.objects.create(
351
+ name="Source A",
352
+ url="https://a.local",
353
+ parameters={"auth": "t", "verify": True},
354
+ last_synced=timezone.now(),
355
+ )
356
+ IPFabricSource.objects.create(
357
+ name="Source B",
358
+ url="https://b.local",
359
+ parameters={"auth": "t", "verify": False},
360
+ last_synced=timezone.now(),
361
+ )
362
+ IPFabricSource.objects.create(
363
+ name="Source C",
364
+ url="https://c.local",
365
+ parameters={"auth": "t", "verify": False},
366
+ last_synced=timezone.now(),
367
+ )
368
+
369
+ cls.create_data = [
370
+ {
371
+ "name": "NewSrc 1",
372
+ "url": "https://nb1.example",
373
+ "parameters": {"auth": "t", "verify": False},
374
+ "type": "local",
375
+ },
376
+ {
377
+ "name": "NewSrc 2",
378
+ "url": "https://nb2.example",
379
+ "parameters": {"auth": "t", "verify": True},
380
+ "type": "local",
381
+ },
382
+ {
383
+ "name": "NewSrc 3",
384
+ "url": "https://nb3.example",
385
+ "parameters": {"auth": "t", "verify": True},
386
+ "type": "local",
387
+ },
388
+ ]
389
+
390
+
391
+ class IPFabricSnapshotTest(
392
+ APIViewTestCases.GetObjectViewTestCase,
393
+ APIViewTestCases.ListObjectsViewTestCase,
394
+ APIViewTestCases.GraphQLTestCase,
395
+ ):
396
+ model = IPFabricSnapshot
397
+ graphql_base_name = "ipfabric_snapshot"
398
+ brief_fields = [
399
+ "data",
400
+ "date",
401
+ "display",
402
+ "id",
403
+ "name",
404
+ "snapshot_id",
405
+ "source",
406
+ "status",
407
+ ]
408
+
409
+ def _get_list_url(self):
410
+ return f"{BASE}snapshot/"
411
+
412
+ def _get_detail_url(self, instance):
413
+ return f"{BASE}snapshot/{instance.pk}/"
414
+
415
+ @classmethod
416
+ def setUpTestData(cls):
417
+ sources = (
418
+ IPFabricSource.objects.create(
419
+ name="Source A",
420
+ url="https://src.local",
421
+ parameters={"auth": "t", "verify": True},
422
+ last_synced=timezone.now(),
423
+ ),
424
+ IPFabricSource.objects.create(
425
+ name="Source B",
426
+ url="https://srcb.local",
427
+ parameters={"auth": "t", "verify": True},
428
+ last_synced=timezone.now(),
429
+ ),
430
+ IPFabricSource.objects.create(
431
+ name="Source C",
432
+ url="https://srcc.local",
433
+ parameters={"auth": "t", "verify": True},
434
+ last_synced=timezone.now(),
435
+ ),
436
+ )
437
+
438
+ cls.snapshots = (
439
+ IPFabricSnapshot.objects.create(
440
+ name="Snapshot One",
441
+ source=sources[0],
442
+ snapshot_id="snap-1",
443
+ status="loaded",
444
+ data={"sites": ["SiteA", "SiteB", "RemoteC"]},
445
+ date=timezone.now(),
446
+ last_updated=timezone.now(),
447
+ ),
448
+ IPFabricSnapshot.objects.create(
449
+ name="Another Name",
450
+ source=sources[0],
451
+ snapshot_id="snap-2",
452
+ status="loaded",
453
+ data={"sites": []},
454
+ date=timezone.now(),
455
+ last_updated=timezone.now(),
456
+ ),
457
+ IPFabricSnapshot.objects.create(
458
+ name="Third Snapshot",
459
+ source=sources[0],
460
+ snapshot_id="snap-3",
461
+ status="unloaded",
462
+ data={"sites": ["SiteD"]},
463
+ date=timezone.now(),
464
+ last_updated=timezone.now(),
465
+ ),
466
+ )
467
+
468
+ def test_sites_action_lists_all_and_filters(self):
469
+ self.add_permissions("ipfabric_netbox.view_ipfabricsnapshot")
470
+ # list all
471
+ url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/"
472
+ resp = self.client.get(url, **self.header)
473
+ self.assertHttpStatus(resp, status.HTTP_200_OK)
474
+ body = resp.json()
475
+ self.assertIn(body.__class__, (list, dict))
476
+ if isinstance(body, dict):
477
+ labels = [i["name"] for i in body["results"]]
478
+ self.assertEqual(labels, self.snapshots[0].data["sites"])
479
+ # filter
480
+ url = f"{BASE}snapshot/{self.snapshots[0].pk}/sites/?q=site"
481
+ resp = self.client.get(url, **self.header)
482
+ self.assertHttpStatus(resp, status.HTTP_200_OK)
483
+ body = resp.json()
484
+ if isinstance(body, dict) and body.get("results"):
485
+ labels = [i["name"].lower() for i in body["results"]]
486
+ self.assertTrue(all("site" in name for name in labels))
487
+
488
+ def test_raw_patch_and_delete(self):
489
+ self.add_permissions(
490
+ "ipfabric_netbox.view_ipfabricsnapshot",
491
+ "ipfabric_netbox.change_ipfabricsnapshot",
492
+ "ipfabric_netbox.delete_ipfabricsnapshot",
493
+ )
494
+ # initial count
495
+ self.assertEqual(
496
+ IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 0
497
+ )
498
+ # PATCH raw
499
+ url = f"{BASE}snapshot/{self.snapshots[0].pk}/raw/"
500
+ payload = {
501
+ "data": [
502
+ {"data": {"example": 1}, "type": "device"},
503
+ {"data": {"foo": "bar"}, "type": "interface"},
504
+ ]
505
+ }
506
+ resp = self.client.patch(url, data=payload, format="json", **self.header)
507
+ self.assertHttpStatus(resp, status.HTTP_200_OK)
508
+ self.assertEqual(resp.data, {"status": "success"})
509
+ self.assertEqual(
510
+ IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 2
511
+ )
512
+ # DELETE raw
513
+ resp = self.client.delete(url, **self.header)
514
+ self.assertHttpStatus(resp, status.HTTP_200_OK)
515
+ self.assertEqual(resp.data, {"status": "success"})
516
+ self.assertEqual(
517
+ IPFabricData.objects.filter(snapshot_data=self.snapshots[0]).count(), 0
518
+ )
519
+
520
+
521
+ class IPFabricSyncTest(APIViewTestCases.APIViewTestCase):
522
+ model = IPFabricSync
523
+ graphql_base_name = "ipfabric_sync"
524
+ brief_fields = [
525
+ "auto_merge",
526
+ "id",
527
+ "last_synced",
528
+ "name",
529
+ "parameters",
530
+ "status",
531
+ ]
532
+ create_data = [
533
+ {
534
+ "name": "Test Sync A",
535
+ "parameters": {"site": True, "device": False},
536
+ },
537
+ {
538
+ "name": "Test Sync B",
539
+ "parameters": {"ipaddress": True, "prefix": True},
540
+ "auto_merge": True,
541
+ },
542
+ {
543
+ "name": "Test Sync C",
544
+ "parameters": {"device": True, "interface": True},
545
+ "interval": 60,
546
+ },
547
+ ]
548
+ bulk_update_data = {
549
+ "auto_merge": True,
550
+ }
551
+
552
+ def _get_list_url(self):
553
+ return f"{BASE}sync/"
554
+
555
+ def _get_detail_url(self, instance):
556
+ return f"{BASE}sync/{instance.pk}/"
557
+
558
+ @classmethod
559
+ def setUpTestData(cls):
560
+ # Create sources for the snapshots
561
+ sources = (
562
+ IPFabricSource.objects.create(
563
+ name="Sync Test Source A",
564
+ url="https://sync-a.local",
565
+ parameters={"auth": "token", "verify": True},
566
+ last_synced=timezone.now(),
567
+ ),
568
+ IPFabricSource.objects.create(
569
+ name="Sync Test Source B",
570
+ url="https://sync-b.local",
571
+ parameters={"auth": "token", "verify": False},
572
+ last_synced=timezone.now(),
573
+ ),
574
+ IPFabricSource.objects.create(
575
+ name="Sync Test Source C",
576
+ url="https://sync-c.local",
577
+ parameters={"auth": "token", "verify": True},
578
+ last_synced=timezone.now(),
579
+ ),
580
+ )
581
+
582
+ # Create snapshots for the syncs
583
+ snapshots = (
584
+ IPFabricSnapshot.objects.create(
585
+ name="Sync Test Snapshot A",
586
+ source=sources[0],
587
+ snapshot_id="sync-snap-a",
588
+ status="loaded",
589
+ data={"sites": ["SyncSiteA", "SyncSiteB"]},
590
+ date=timezone.now(),
591
+ last_updated=timezone.now(),
592
+ ),
593
+ IPFabricSnapshot.objects.create(
594
+ name="Sync Test Snapshot B",
595
+ source=sources[1],
596
+ snapshot_id="sync-snap-b",
597
+ status="loaded",
598
+ data={"devices": ["SyncDevice1", "SyncDevice2"]},
599
+ date=timezone.now(),
600
+ last_updated=timezone.now(),
601
+ ),
602
+ IPFabricSnapshot.objects.create(
603
+ name="Sync Test Snapshot C",
604
+ source=sources[2],
605
+ snapshot_id="sync-snap-c",
606
+ status="unloaded",
607
+ data={"interfaces": ["SyncInterface1"]},
608
+ date=timezone.now(),
609
+ last_updated=timezone.now(),
610
+ ),
611
+ )
612
+
613
+ # Create syncs for testing
614
+ IPFabricSync.objects.create(
615
+ name="Sync Test D",
616
+ snapshot_data=snapshots[0],
617
+ parameters={"site": True, "device": False},
618
+ )
619
+ IPFabricSync.objects.create(
620
+ name="Sync Test E",
621
+ snapshot_data=snapshots[1],
622
+ parameters={"device": True, "interface": True},
623
+ auto_merge=True,
624
+ )
625
+ IPFabricSync.objects.create(
626
+ name="Sync Test F",
627
+ snapshot_data=snapshots[2],
628
+ parameters={"ipaddress": True, "prefix": False},
629
+ interval=30,
630
+ )
631
+
632
+ # Update create_data to reference the snapshots
633
+ cls.create_data[0]["snapshot_data"] = snapshots[0].pk
634
+ cls.create_data[1]["snapshot_data"] = snapshots[1].pk
635
+ cls.create_data[2]["snapshot_data"] = snapshots[2].pk
636
+ cls.create_data[0]["parameters"] = {"site": True, "device": False}
637
+ cls.create_data[1]["parameters"] = {"ipaddress": True, "prefix": True}
638
+ cls.create_data[2]["parameters"] = {"device": True, "interface": True}
639
+
640
+
641
+ class IPFabricIngestionTest(
642
+ APIViewTestCases.GetObjectViewTestCase,
643
+ APIViewTestCases.ListObjectsViewTestCase,
644
+ APIViewTestCases.GraphQLTestCase,
645
+ ):
646
+ model = IPFabricIngestion
647
+ graphql_base_name = "ipfabric_ingestion"
648
+ brief_fields = [
649
+ "branch",
650
+ "id",
651
+ "name",
652
+ "sync",
653
+ ]
654
+
655
+ def _get_list_url(self):
656
+ return f"{BASE}ingestion/"
657
+
658
+ def _get_detail_url(self, instance):
659
+ return f"{BASE}ingestion/{instance.pk}/"
660
+
661
+ @classmethod
662
+ def setUpTestData(cls):
663
+ # Create sources for the snapshots
664
+ sources = (
665
+ IPFabricSource.objects.create(
666
+ name="Ingestion Test Source A",
667
+ url="https://ingestion-a.local",
668
+ parameters={"auth": "token", "verify": True},
669
+ last_synced=timezone.now(),
670
+ ),
671
+ IPFabricSource.objects.create(
672
+ name="Ingestion Test Source B",
673
+ url="https://ingestion-b.local",
674
+ parameters={"auth": "token", "verify": False},
675
+ last_synced=timezone.now(),
676
+ ),
677
+ IPFabricSource.objects.create(
678
+ name="Ingestion Test Source C",
679
+ url="https://ingestion-c.local",
680
+ parameters={"auth": "token", "verify": True},
681
+ last_synced=timezone.now(),
682
+ ),
683
+ )
684
+
685
+ # Create snapshots for the syncs
686
+ snapshots = (
687
+ IPFabricSnapshot.objects.create(
688
+ name="Ingestion Test Snapshot A",
689
+ source=sources[0],
690
+ snapshot_id="ing-snap-a",
691
+ status="loaded",
692
+ data={"sites": ["SiteA", "SiteB"]},
693
+ date=timezone.now(),
694
+ last_updated=timezone.now(),
695
+ ),
696
+ IPFabricSnapshot.objects.create(
697
+ name="Ingestion Test Snapshot B",
698
+ source=sources[1],
699
+ snapshot_id="ing-snap-b",
700
+ status="loaded",
701
+ data={"devices": ["Device1", "Device2"]},
702
+ date=timezone.now(),
703
+ last_updated=timezone.now(),
704
+ ),
705
+ IPFabricSnapshot.objects.create(
706
+ name="Ingestion Test Snapshot C",
707
+ source=sources[2],
708
+ snapshot_id="ing-snap-c",
709
+ status="unloaded",
710
+ data={"interfaces": ["Interface1"]},
711
+ date=timezone.now(),
712
+ last_updated=timezone.now(),
713
+ ),
714
+ )
715
+
716
+ # Create syncs for the ingestions
717
+ syncs = (
718
+ IPFabricSync.objects.create(
719
+ name="Ingestion Test Sync A",
720
+ snapshot_data=snapshots[0],
721
+ parameters={"site": True, "device": False},
722
+ ),
723
+ IPFabricSync.objects.create(
724
+ name="Ingestion Test Sync B",
725
+ snapshot_data=snapshots[1],
726
+ parameters={"device": True, "interface": True},
727
+ ),
728
+ IPFabricSync.objects.create(
729
+ name="Ingestion Test Sync C",
730
+ snapshot_data=snapshots[2],
731
+ parameters={"ipaddress": True, "prefix": False},
732
+ ),
733
+ )
734
+
735
+ # Create ingestions for testing
736
+ IPFabricIngestion.objects.create(sync=syncs[0])
737
+ IPFabricIngestion.objects.create(sync=syncs[1])
738
+ IPFabricIngestion.objects.create(sync=syncs[2])
739
+
740
+
741
+ class IPFabricIngestionIssueTest(
742
+ APIViewTestCases.GetObjectViewTestCase,
743
+ APIViewTestCases.ListObjectsViewTestCase,
744
+ APIViewTestCases.GraphQLTestCase,
745
+ ):
746
+ model = IPFabricIngestionIssue
747
+ graphql_base_name = "ipfabric_ingestion_issue"
748
+ brief_fields = [
749
+ "exception",
750
+ "id",
751
+ "ingestion",
752
+ "message",
753
+ "model",
754
+ ]
755
+
756
+ def _get_list_url(self):
757
+ return f"{BASE}ingestion-issues/"
758
+
759
+ def _get_detail_url(self, instance):
760
+ return f"{BASE}ingestion-issues/{instance.pk}/"
761
+
762
+ @classmethod
763
+ def setUpTestData(cls):
764
+ # Create sources for the snapshots
765
+ sources = (
766
+ IPFabricSource.objects.create(
767
+ name="Issue Test Source A",
768
+ url="https://issue-a.local",
769
+ parameters={"auth": "token", "verify": True},
770
+ last_synced=timezone.now(),
771
+ ),
772
+ IPFabricSource.objects.create(
773
+ name="Issue Test Source B",
774
+ url="https://issue-b.local",
775
+ parameters={"auth": "token", "verify": False},
776
+ last_synced=timezone.now(),
777
+ ),
778
+ IPFabricSource.objects.create(
779
+ name="Issue Test Source C",
780
+ url="https://issue-c.local",
781
+ parameters={"auth": "token", "verify": True},
782
+ last_synced=timezone.now(),
783
+ ),
784
+ )
785
+
786
+ # Create snapshots for the syncs
787
+ snapshots = (
788
+ IPFabricSnapshot.objects.create(
789
+ name="Issue Test Snapshot A",
790
+ source=sources[0],
791
+ snapshot_id="issue-snap-a",
792
+ status="loaded",
793
+ data={"sites": ["IssueTestSiteA", "IssueTestSiteB"]},
794
+ date=timezone.now(),
795
+ last_updated=timezone.now(),
796
+ ),
797
+ IPFabricSnapshot.objects.create(
798
+ name="Issue Test Snapshot B",
799
+ source=sources[1],
800
+ snapshot_id="issue-snap-b",
801
+ status="loaded",
802
+ data={"devices": ["IssueTestDevice1", "IssueTestDevice2"]},
803
+ date=timezone.now(),
804
+ last_updated=timezone.now(),
805
+ ),
806
+ IPFabricSnapshot.objects.create(
807
+ name="Issue Test Snapshot C",
808
+ source=sources[2],
809
+ snapshot_id="issue-snap-c",
810
+ status="unloaded",
811
+ data={"interfaces": ["IssueTestInterface1"]},
812
+ date=timezone.now(),
813
+ last_updated=timezone.now(),
814
+ ),
815
+ )
816
+
817
+ # Create syncs for the ingestions
818
+ syncs = (
819
+ IPFabricSync.objects.create(
820
+ name="Issue Test Sync A",
821
+ snapshot_data=snapshots[0],
822
+ parameters={"site": True, "device": False},
823
+ ),
824
+ IPFabricSync.objects.create(
825
+ name="Issue Test Sync B",
826
+ snapshot_data=snapshots[1],
827
+ parameters={"device": True, "interface": True},
828
+ ),
829
+ IPFabricSync.objects.create(
830
+ name="Issue Test Sync C",
831
+ snapshot_data=snapshots[2],
832
+ parameters={"ipaddress": True, "prefix": False},
833
+ ),
834
+ )
835
+
836
+ # Create ingestions for the issues
837
+ ingestions = (
838
+ IPFabricIngestion.objects.create(sync=syncs[0]),
839
+ IPFabricIngestion.objects.create(sync=syncs[1]),
840
+ IPFabricIngestion.objects.create(sync=syncs[2]),
841
+ )
842
+
843
+ # Create ingestion issues for testing
844
+ IPFabricIngestionIssue.objects.create(
845
+ ingestion=ingestions[0],
846
+ model="dcim.site",
847
+ message="Failed to create site due to validation error",
848
+ raw_data='{"name": "Invalid Site", "slug": ""}',
849
+ coalesce_fields="name,slug",
850
+ defaults="{}",
851
+ exception="ValidationError: Slug field cannot be empty",
852
+ )
853
+ IPFabricIngestionIssue.objects.create(
854
+ ingestion=ingestions[0],
855
+ model="dcim.device",
856
+ message="Device type not found",
857
+ raw_data='{"hostname": "test-device", "device_type": "NonExistentType"}',
858
+ coalesce_fields="hostname",
859
+ defaults='{"status": "active"}',
860
+ exception="DoesNotExist: DeviceType matching query does not exist",
861
+ )
862
+ IPFabricIngestionIssue.objects.create(
863
+ ingestion=ingestions[1],
864
+ model="dcim.interface",
865
+ message="Interface creation failed - invalid MAC address",
866
+ raw_data='{"name": "eth0", "mac_address": "invalid-mac", "device": 1}',
867
+ coalesce_fields="name,device",
868
+ defaults='{"type": "1000base-t"}',
869
+ exception="ValidationError: Enter a valid MAC address",
870
+ )
871
+ IPFabricIngestionIssue.objects.create(
872
+ ingestion=ingestions[2],
873
+ model="ipam.ipaddress",
874
+ message="IP address already exists",
875
+ raw_data='{"address": "192.168.1.1/24", "status": "active"}',
876
+ coalesce_fields="address",
877
+ defaults='{"dns_name": ""}',
878
+ exception="IntegrityError: IP address 192.168.1.1/24 already exists",
879
+ )