netbox-config-diff 2.14.1__tar.gz → 2.14.3__tar.gz

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.
Files changed (113) hide show
  1. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/PKG-INFO +1 -1
  2. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/__init__.py +1 -1
  3. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/api/serializers.py +15 -12
  4. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/graphql/filters.py +16 -16
  5. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff.egg-info/PKG-INFO +1 -1
  6. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff.egg-info/SOURCES.txt +1 -0
  7. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/tests/conftest.py +14 -0
  8. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/tests/factories.py +13 -0
  9. netbox_config_diff-2.14.3/tests/test_configurtion_request.py +229 -0
  10. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/LICENSE +0 -0
  11. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/MANIFEST.in +0 -0
  12. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/README.md +0 -0
  13. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/development/configuration.py +0 -0
  14. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-diff.png +0 -0
  15. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-error.png +0 -0
  16. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-list.png +0 -0
  17. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-missing-extra.png +0 -0
  18. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-ok.png +0 -0
  19. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/compliance-patch.png +0 -0
  20. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/config-temp-substitute.png +0 -0
  21. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-approve-button.png +0 -0
  22. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-approved.png +0 -0
  23. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-collecting-diff-button.png +0 -0
  24. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-completed.png +0 -0
  25. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-created.png +0 -0
  26. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-diffs-tab.png +0 -0
  27. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-job-log.png +0 -0
  28. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-schedule-button.png +0 -0
  29. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-scheduled.png +0 -0
  30. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-unapprove-button.png +0 -0
  31. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/cr-unschedule-button.png +0 -0
  32. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/navbar.png +0 -0
  33. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/platformsetting.png +0 -0
  34. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/render-temp-substitute.png +0 -0
  35. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/script-list.png +0 -0
  36. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/script.png +0 -0
  37. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/docs/media/screenshots/substitute.png +0 -0
  38. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/api/__init__.py +0 -0
  39. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/api/urls.py +0 -0
  40. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/api/views.py +0 -0
  41. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/choices.py +0 -0
  42. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/compliance/__init__.py +0 -0
  43. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/compliance/base.py +0 -0
  44. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/compliance/secrets.py +0 -0
  45. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/compliance/utils.py +0 -0
  46. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/__init__.py +0 -0
  47. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/base.py +0 -0
  48. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/exceptions.py +0 -0
  49. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/factory.py +0 -0
  50. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/platforms.py +0 -0
  51. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/configurator/utils.py +0 -0
  52. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/constants.py +0 -0
  53. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/filtersets.py +0 -0
  54. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/forms/__init__.py +0 -0
  55. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/forms/general.py +0 -0
  56. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/graphql/__init__.py +0 -0
  57. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/graphql/enums.py +0 -0
  58. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/graphql/schema.py +0 -0
  59. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/graphql/types.py +0 -0
  60. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/jobs.py +0 -0
  61. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0001_initial.py +0 -0
  62. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0002_add_script.py +0 -0
  63. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0003_configcompliance_actual_config_and_more.py +0 -0
  64. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0004_update_script.py +0 -0
  65. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0005_configcompliance_extra_missing.py +0 -0
  66. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0006_substitute.py +0 -0
  67. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0007_configurationrequest.py +0 -0
  68. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0008_alter_configcompliance_device.py +0 -0
  69. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0009_configcompliance_patch.py +0 -0
  70. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0010_create_script.py +0 -0
  71. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/0011_configurationrequest_owner.py +0 -0
  72. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/migrations/__init__.py +0 -0
  73. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/models/__init__.py +0 -0
  74. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/models/base.py +0 -0
  75. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/models/data_models.py +0 -0
  76. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/models/models.py +0 -0
  77. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/navigation.py +0 -0
  78. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/scripts/config_diff.py +0 -0
  79. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/search.py +0 -0
  80. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/static/netbox_config_diff/diff2html-ui.min.js +0 -0
  81. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/static/netbox_config_diff/diff2html.dark.min.css +0 -0
  82. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/static/netbox_config_diff/diff2html.min.css +0 -0
  83. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/tables.py +0 -0
  84. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configcompliance/config.html +0 -0
  85. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configcompliance/data.html +0 -0
  86. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configcompliance/missing_extra.html +0 -0
  87. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configcompliance.html +0 -0
  88. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configurationrequest/base.html +0 -0
  89. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configurationrequest/diffs.html +0 -0
  90. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/configurationrequest.html +0 -0
  91. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/inc/commands_card.html +0 -0
  92. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/inc/diff.html +0 -0
  93. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/inc/job_log.html +0 -0
  94. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/platformsetting.html +0 -0
  95. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templates/netbox_config_diff/substitute.html +0 -0
  96. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templatetags/__init__.py +0 -0
  97. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/templatetags/string_filters.py +0 -0
  98. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/urls.py +0 -0
  99. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/views/__init__.py +0 -0
  100. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/views/base.py +0 -0
  101. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/views/compliance.py +0 -0
  102. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff/views/configuration.py +0 -0
  103. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff.egg-info/dependency_links.txt +0 -0
  104. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff.egg-info/requires.txt +0 -0
  105. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/netbox_config_diff.egg-info/top_level.txt +0 -0
  106. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/pyproject.toml +0 -0
  107. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/requirements/base.txt +0 -0
  108. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/requirements/dev.txt +0 -0
  109. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/requirements/test.txt +0 -0
  110. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/setup.cfg +0 -0
  111. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/tests/test_compliance.py +0 -0
  112. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/tests/test_compliance_utils.py +0 -0
  113. {netbox_config_diff-2.14.1 → netbox_config_diff-2.14.3}/tests/test_urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-config-diff
3
- Version: 2.14.1
3
+ Version: 2.14.3
4
4
  Summary: Push rendered device configurations from NetBox to devices and apply them.
5
5
  Author: Artem Kotik
6
6
  Author-email: miaow2@yandex.ru
@@ -2,7 +2,7 @@ from netbox.plugins import PluginConfig
2
2
 
3
3
  __author__ = "Artem Kotik"
4
4
  __email__ = "miaow2@yandex.ru"
5
- __version__ = "2.14.1"
5
+ __version__ = "2.14.3"
6
6
 
7
7
 
8
8
  class ConfigDiffConfig(PluginConfig):
@@ -101,21 +101,24 @@ class ConfigurationRequestSerializer(NetBoxModelSerializer):
101
101
 
102
102
  def validate(self, data):
103
103
  if data.get("devices"):
104
- if devices := data["devices"].filter(platform__platform_setting__isnull=True):
104
+ if devices := Device.objects.filter(id__in=[d.id for d in data["devices"]]).filter(
105
+ platform__platform_setting__isnull=True
106
+ ):
105
107
  platforms = {d.platform.name for d in devices}
106
108
  raise ValidationError({"devices": f"Assign PlatformSetting for platform(s): {', '.join(platforms)}"})
107
109
 
108
- if drivers := {
109
- device.platform.platform_setting.driver
110
- for device in data["devices"]
111
- if device.platform.platform_setting.driver not in ACCEPTABLE_DRIVERS
112
- }:
113
- raise ValidationError({"devices": f"Driver(s) not supported: {', '.join(drivers)}"})
114
-
115
- if devices := list(filter(lambda x: x.get_config_template() is None, data["devices"])):
116
- raise ValidationError(
117
- {"devices": f"Define config template for device(s): {', '.join(d.name for d in devices)}"}
118
- )
110
+ unsupported_drivers = {
111
+ d.platform.platform_setting.driver
112
+ for d in data["devices"]
113
+ if d.platform.platform_setting.driver not in ACCEPTABLE_DRIVERS
114
+ }
115
+ if unsupported_drivers:
116
+ raise ValidationError({"devices": f"Driver(s) not supported: {', '.join(unsupported_drivers)}"})
117
+
118
+ devices_without_template = [d for d in data["devices"] if d.get_config_template() is None]
119
+ if devices_without_template:
120
+ names = ", ".join(d.name for d in devices_without_template)
121
+ raise ValidationError({"devices": f"Define config template for device(s): {names}"})
119
122
 
120
123
  return super().validate(data)
121
124
 
@@ -5,7 +5,7 @@ import strawberry
5
5
  import strawberry_django
6
6
  from netbox.graphql.filters import ChangeLoggedModelFilter, NetBoxModelFilter, PrimaryModelFilter
7
7
  from strawberry.scalars import ID
8
- from strawberry_django import DatetimeFilterLookup
8
+ from strawberry_django import DatetimeFilterLookup, StrFilterLookup
9
9
 
10
10
  from netbox_config_diff.models import ConfigCompliance, ConfigurationRequest, PlatformSetting, Substitute
11
11
 
@@ -23,18 +23,18 @@ class ConfigComplianceFilter(ChangeLoggedModelFilter):
23
23
  status: Annotated["ConfigComplianceStatusEnum", strawberry.lazy("netbox_config_diff.graphql.enums")] | None = (
24
24
  strawberry_django.filter_field()
25
25
  )
26
- diff: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
27
- error: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
28
- actual_config: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
29
- rendered_config: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
30
- missing: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
31
- extra: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
32
- patch: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
26
+ diff: StrFilterLookup[str] | None = strawberry_django.filter_field()
27
+ error: StrFilterLookup[str] | None = strawberry_django.filter_field()
28
+ actual_config: StrFilterLookup[str] | None = strawberry_django.filter_field()
29
+ rendered_config: StrFilterLookup[str] | None = strawberry_django.filter_field()
30
+ missing: StrFilterLookup[str] | None = strawberry_django.filter_field()
31
+ extra: StrFilterLookup[str] | None = strawberry_django.filter_field()
32
+ patch: StrFilterLookup[str] | None = strawberry_django.filter_field()
33
33
 
34
34
 
35
35
  @strawberry_django.filter(ConfigurationRequest, lookups=True)
36
36
  class ConfigurationRequestFilter(PrimaryModelFilter):
37
- description: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
37
+ description: StrFilterLookup[str] | None = strawberry_django.filter_field()
38
38
  status: Annotated["ConfigurationRequestStatusEnum", strawberry.lazy("netbox_config_diff.graphql.enums")] | None = (
39
39
  strawberry_django.filter_field()
40
40
  )
@@ -64,10 +64,10 @@ class PlatformSettingFilter(NetBoxModelFilter):
64
64
  strawberry_django.filter_field()
65
65
  )
66
66
  platform_id: ID | None = strawberry_django.filter_field()
67
- driver: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
68
- description: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
69
- command: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
70
- exclude_regex: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
67
+ driver: StrFilterLookup[str] | None = strawberry_django.filter_field()
68
+ description: StrFilterLookup[str] | None = strawberry_django.filter_field()
69
+ command: StrFilterLookup[str] | None = strawberry_django.filter_field()
70
+ exclude_regex: StrFilterLookup[str] | None = strawberry_django.filter_field()
71
71
 
72
72
 
73
73
  @strawberry_django.filter(Substitute, lookups=True)
@@ -76,6 +76,6 @@ class SubstituteFilter(NetBoxModelFilter):
76
76
  Annotated["PlatformSettingFilter", strawberry.lazy("netbox_config_diff.graphql.filters")] | None
77
77
  ) = strawberry_django.filter_field()
78
78
  platform_setting_id: ID | None = strawberry_django.filter_field()
79
- name: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
80
- description: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
81
- regexp: strawberry_django.FilterLookup[str] | None = strawberry_django.filter_field()
79
+ name: StrFilterLookup[str] | None = strawberry_django.filter_field()
80
+ description: StrFilterLookup[str] | None = strawberry_django.filter_field()
81
+ regexp: StrFilterLookup[str] | None = strawberry_django.filter_field()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: netbox-config-diff
3
- Version: 2.14.1
3
+ Version: 2.14.3
4
4
  Summary: Push rendered device configurations from NetBox to devices and apply them.
5
5
  Author: Artem Kotik
6
6
  Author-email: miaow2@yandex.ru
@@ -107,4 +107,5 @@ tests/conftest.py
107
107
  tests/factories.py
108
108
  tests/test_compliance.py
109
109
  tests/test_compliance_utils.py
110
+ tests/test_configurtion_request.py
110
111
  tests/test_urls.py
@@ -5,6 +5,7 @@ import pytest
5
5
  from django.db import connection
6
6
  from django.test.utils import setup_databases
7
7
  from faker import Faker
8
+ from rest_framework.test import APIClient
8
9
  from typing_extensions import Unpack
9
10
 
10
11
  from netbox_config_diff.compliance.base import ConfigDiffBase
@@ -125,3 +126,16 @@ def devicedataclass_factory() -> "DeviceDataClassDataFactory":
125
126
  def devicedataclass_data(devicedataclass_factory: "DeviceDataClassDataFactory") -> ConplianceDeviceDataClass:
126
127
  data = devicedataclass_factory()
127
128
  return ConplianceDeviceDataClass(**data)
129
+
130
+
131
+ @pytest.fixture
132
+ def api_client():
133
+ """Basic API client fixture."""
134
+ return APIClient()
135
+
136
+
137
+ @pytest.fixture
138
+ def authenticated_api_client(api_client, admin_user):
139
+ """Authenticated API client fixture using admin user."""
140
+ api_client.force_authenticate(user=admin_user)
141
+ return api_client
@@ -94,6 +94,19 @@ class ConfigComplianceFactory(DjangoModelFactory):
94
94
  model = ConfigCompliance
95
95
 
96
96
 
97
+ class ConfigurationRequestFactory(DjangoModelFactory):
98
+ class Meta:
99
+ model = "netbox_config_diff.ConfigurationRequest"
100
+
101
+ @factory.post_generation
102
+ def devices(obj, create, extracted, **kwargs):
103
+ if not create:
104
+ return
105
+ if extracted:
106
+ for device in extracted:
107
+ obj.devices.add(device)
108
+
109
+
97
110
  class DataSourceFactory(DjangoModelFactory):
98
111
  name = factory.Sequence(lambda n: f"datasource-{n}")
99
112
  source_url = factory.Sequence(lambda n: f"/tmp/{n}")
@@ -0,0 +1,229 @@
1
+ from http import HTTPStatus
2
+ from typing import TYPE_CHECKING
3
+
4
+ import pytest
5
+ from django.urls import reverse
6
+ from users.models import User
7
+
8
+ from netbox_config_diff.choices import ConfigurationRequestStatusChoices
9
+ from tests.factories import (
10
+ ConfigurationRequestFactory,
11
+ DeviceFactory,
12
+ PlatformSettingFactory,
13
+ )
14
+
15
+ if TYPE_CHECKING:
16
+ pass
17
+
18
+
19
+ @pytest.mark.django_db()
20
+ def test_configuration_request_list(authenticated_api_client):
21
+ device = DeviceFactory.create()
22
+ PlatformSettingFactory.create(platform=device.platform)
23
+ ConfigurationRequestFactory.create(devices=[device])
24
+ ConfigurationRequestFactory.create(devices=[device])
25
+
26
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
27
+ response = authenticated_api_client.get(url)
28
+
29
+ assert response.status_code == HTTPStatus.OK
30
+ assert response.json()["count"] == 2
31
+ assert len(response.json()["results"]) == 2
32
+
33
+
34
+ @pytest.mark.django_db()
35
+ def test_configuration_request_retrieve(authenticated_api_client):
36
+ device = DeviceFactory.create()
37
+ PlatformSettingFactory.create(platform=device.platform)
38
+ cr = ConfigurationRequestFactory.create(devices=[device])
39
+
40
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-detail", args=[cr.pk])
41
+ response = authenticated_api_client.get(url)
42
+
43
+ assert response.status_code == HTTPStatus.OK
44
+ data = response.json()
45
+ assert data["id"] == cr.pk
46
+ assert data["status"]["value"] == ConfigurationRequestStatusChoices.CREATED
47
+ assert len(data["devices"]) == 1
48
+ assert data["devices"][0]["id"] == device.pk
49
+
50
+
51
+ @pytest.mark.django_db()
52
+ def test_configuration_request_create_success(authenticated_api_client, admin_user):
53
+ device = DeviceFactory.create()
54
+ PlatformSettingFactory.create(platform=device.platform)
55
+
56
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
57
+ payload = {
58
+ "devices": [device.pk],
59
+ "description": "Test configuration request",
60
+ "comments": "Test comments",
61
+ }
62
+ response = authenticated_api_client.post(url, payload, format="json")
63
+
64
+ assert response.status_code == HTTPStatus.CREATED
65
+ data = response.json()
66
+ assert data["status"]["value"] == ConfigurationRequestStatusChoices.CREATED
67
+ assert data["description"] == "Test configuration request"
68
+ assert data["comments"] == "Test comments"
69
+ assert len(data["devices"]) == 1
70
+ assert data["created_by"]["id"] == admin_user.pk
71
+
72
+
73
+ @pytest.mark.django_db()
74
+ def test_configuration_request_create_multiple_devices(authenticated_api_client):
75
+ device1 = DeviceFactory.create()
76
+ device2 = DeviceFactory.create()
77
+ PlatformSettingFactory.create(platform=device1.platform)
78
+ PlatformSettingFactory.create(platform=device2.platform)
79
+
80
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
81
+ payload = {
82
+ "devices": [device1.pk, device2.pk],
83
+ "description": "Multi-device request",
84
+ }
85
+ response = authenticated_api_client.post(url, payload, format="json")
86
+
87
+ assert response.status_code == HTTPStatus.CREATED
88
+ data = response.json()
89
+ assert len(data["devices"]) == 2
90
+ device_ids = {d["id"] for d in data["devices"]}
91
+ assert device1.pk in device_ids
92
+ assert device2.pk in device_ids
93
+
94
+
95
+ @pytest.mark.django_db()
96
+ def test_configuration_request_create_no_platform_setting(authenticated_api_client):
97
+ device = DeviceFactory.create()
98
+
99
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
100
+ payload = {
101
+ "devices": [device.pk],
102
+ }
103
+ response = authenticated_api_client.post(url, payload, format="json")
104
+
105
+ assert response.status_code == HTTPStatus.BAD_REQUEST
106
+ data = response.json()
107
+ assert "devices" in data
108
+ assert "Assign PlatformSetting" in str(data["devices"])
109
+
110
+
111
+ @pytest.mark.django_db()
112
+ def test_configuration_request_create_unsupported_driver(authenticated_api_client):
113
+ device = DeviceFactory.create()
114
+ PlatformSettingFactory.create(platform=device.platform, driver="unsupported_driver")
115
+
116
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
117
+ payload = {
118
+ "devices": [device.pk],
119
+ }
120
+ response = authenticated_api_client.post(url, payload, format="json")
121
+
122
+ assert response.status_code == HTTPStatus.BAD_REQUEST
123
+ data = response.json()
124
+ assert "devices" in data
125
+ assert "Driver(s) not supported" in str(data["devices"])
126
+
127
+
128
+ @pytest.mark.django_db()
129
+ def test_configuration_request_create_no_config_template(authenticated_api_client):
130
+ device = DeviceFactory.create(config_template=None)
131
+ PlatformSettingFactory.create(platform=device.platform)
132
+
133
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
134
+ payload = {
135
+ "devices": [device.pk],
136
+ }
137
+ response = authenticated_api_client.post(url, payload, format="json")
138
+
139
+ assert response.status_code == HTTPStatus.BAD_REQUEST
140
+ data = response.json()
141
+ assert "devices" in data
142
+ assert "Define config template" in str(data["devices"])
143
+
144
+
145
+ @pytest.mark.django_db()
146
+ def test_configuration_request_create_mixed_valid_invalid_devices(authenticated_api_client):
147
+ valid_device = DeviceFactory.create()
148
+ PlatformSettingFactory.create(platform=valid_device.platform)
149
+ invalid_device = DeviceFactory.create()
150
+
151
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
152
+ payload = {
153
+ "devices": [valid_device.pk, invalid_device.pk],
154
+ }
155
+ response = authenticated_api_client.post(url, payload, format="json")
156
+
157
+ assert response.status_code == HTTPStatus.BAD_REQUEST
158
+ data = response.json()
159
+ assert "devices" in data
160
+
161
+
162
+ @pytest.mark.django_db()
163
+ def test_configuration_request_create_empty_devices(authenticated_api_client):
164
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
165
+ payload = {
166
+ "devices": [],
167
+ }
168
+ response = authenticated_api_client.post(url, payload, format="json")
169
+
170
+ assert response.status_code == HTTPStatus.CREATED
171
+ data = response.json()
172
+ assert len(data["devices"]) == 0
173
+
174
+
175
+ @pytest.mark.django_db()
176
+ def test_configuration_request_create_without_authentication(api_client):
177
+ device = DeviceFactory.create()
178
+ PlatformSettingFactory.create(platform=device.platform)
179
+
180
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
181
+ payload = {
182
+ "devices": [device.pk],
183
+ }
184
+ response = api_client.post(url, payload, format="json")
185
+ assert response.status_code == HTTPStatus.FORBIDDEN
186
+
187
+
188
+ @pytest.mark.django_db()
189
+ def test_configuration_request_list_filtering(authenticated_api_client):
190
+ device = DeviceFactory.create()
191
+ PlatformSettingFactory.create(platform=device.platform)
192
+
193
+ cr = ConfigurationRequestFactory.create(devices=[device], status=ConfigurationRequestStatusChoices.CREATED)
194
+ ConfigurationRequestFactory.create(devices=[device], status=ConfigurationRequestStatusChoices.APPROVED)
195
+
196
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
197
+
198
+ response = authenticated_api_client.get(url, {"status": ConfigurationRequestStatusChoices.CREATED})
199
+
200
+ assert response.status_code == HTTPStatus.OK
201
+ data = response.json()
202
+
203
+ assert any(r["id"] == cr.pk for r in data["results"])
204
+
205
+
206
+ @pytest.mark.django_db()
207
+ def test_configuration_request_retrieve_nonexistent(authenticated_api_client):
208
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-detail", args=[9999])
209
+ response = authenticated_api_client.get(url)
210
+
211
+ assert response.status_code == HTTPStatus.NOT_FOUND
212
+
213
+
214
+ @pytest.mark.django_db()
215
+ def test_configuration_request_create_sets_created_by(authenticated_api_client, admin_user):
216
+ user2 = User.objects.create_user(username="user2", password="pass")
217
+
218
+ device = DeviceFactory.create()
219
+ PlatformSettingFactory.create(platform=device.platform)
220
+
221
+ url = reverse("plugins-api:netbox_config_diff-api:configurationrequest-list")
222
+ payload = {"devices": [device.pk]}
223
+ response = authenticated_api_client.post(url, payload, format="json")
224
+
225
+ assert response.status_code == HTTPStatus.CREATED
226
+ data = response.json()
227
+
228
+ assert data["created_by"]["id"] == admin_user.pk
229
+ assert data["created_by"]["id"] != user2.pk