فهرست منبع

Add tests for plugin configuration, min/max version

Jeremy Stretch 5 سال پیش
والد
کامیت
9ffc404027

+ 34 - 0
netbox/extras/plugins/__init__.py

@@ -1,7 +1,10 @@
 import collections
 import collections
 import inspect
 import inspect
+from pkg_resources import parse_version
 
 
 from django.apps import AppConfig
 from django.apps import AppConfig
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
 from django.template.loader import get_template
 from django.template.loader import get_template
 from django.utils.module_loading import import_string
 from django.utils.module_loading import import_string
 
 
@@ -70,6 +73,37 @@ class PluginConfig(AppConfig):
         except ImportError:
         except ImportError:
             pass
             pass
 
 
+    @classmethod
+    def validate(cls, user_config):
+
+        # Enforce version constraints
+        current_version = parse_version(settings.VERSION)
+        if cls.min_version is not None:
+            min_version = parse_version(cls.min_version)
+            if current_version < min_version:
+                raise ImproperlyConfigured(
+                    f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}."
+                )
+        if cls.max_version is not None:
+            max_version = parse_version(cls.max_version)
+            if current_version > max_version:
+                raise ImproperlyConfigured(
+                    f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}."
+                )
+
+        # Verify required configuration settings
+        for setting in cls.required_settings:
+            if setting not in user_config:
+                raise ImproperlyConfigured(
+                    f"Plugin {cls.__module__} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
+                    f"configuration.py."
+                )
+
+        # Apply default configuration values
+        for setting, value in cls.default_settings.items():
+            if setting not in user_config:
+                user_config[setting] = value
+
 
 
 #
 #
 # Template content injection
 # Template content injection

+ 2 - 0
netbox/extras/tests/dummy_plugin/__init__.py

@@ -7,6 +7,8 @@ class DummyPluginConfig(PluginConfig):
     version = '0.0'
     version = '0.0'
     description = 'For testing purposes only'
     description = 'For testing purposes only'
     base_url = 'dummy-plugin'
     base_url = 'dummy-plugin'
+    min_version = '1.0'
+    max_version = '9.0'
     middleware = [
     middleware = [
         'extras.tests.dummy_plugin.middleware.DummyMiddleware'
         'extras.tests.dummy_plugin.middleware.DummyMiddleware'
     ]
     ]

+ 52 - 1
netbox/extras/tests/test_plugins.py

@@ -1,13 +1,15 @@
 from unittest import skipIf
 from unittest import skipIf
 
 
 from django.conf import settings
 from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
 from django.test import Client, TestCase, override_settings
 from django.test import Client, TestCase, override_settings
 from django.urls import reverse
 from django.urls import reverse
 
 
 from extras.registry import registry
 from extras.registry import registry
+from extras.tests.dummy_plugin import config as dummy_config
 
 
 
 
-@skipIf('extras.tests.dummy_plugin.DummyPluginConfig' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
+@skipIf('extras.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
 class PluginTest(TestCase):
 class PluginTest(TestCase):
 
 
     def test_config(self):
     def test_config(self):
@@ -77,3 +79,52 @@ class PluginTest(TestCase):
         Check that plugin middleware is registered.
         Check that plugin middleware is registered.
         """
         """
         self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE)
         self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE)
+
+    @override_settings(VERSION='0.9')
+    def test_min_version(self):
+        """
+        Check enforcement of minimum NetBox version.
+        """
+        with self.assertRaises(ImproperlyConfigured):
+            dummy_config.validate({})
+
+    @override_settings(VERSION='10.0')
+    def test_max_version(self):
+        """
+        Check enforcement of maximum NetBox version.
+        """
+        with self.assertRaises(ImproperlyConfigured):
+            dummy_config.validate({})
+
+    def test_required_settings(self):
+        """
+        Validate enforcement of required settings.
+        """
+        class DummyConfigWithRequiredSettings(dummy_config):
+            required_settings = ['foo']
+
+        # Validation should pass when all required settings are present
+        DummyConfigWithRequiredSettings.validate({'foo': True})
+
+        # Validation should fail when a required setting is missing
+        with self.assertRaises(ImproperlyConfigured):
+            DummyConfigWithRequiredSettings.validate({})
+
+    def test_default_settings(self):
+        """
+        Validate population of default config settings.
+        """
+        class DummyConfigWithDefaultSettings(dummy_config):
+            default_settings = {
+                'bar': 123,
+            }
+
+        # Populate the default value if setting has not been specified
+        user_config = {}
+        DummyConfigWithDefaultSettings.validate(user_config)
+        self.assertEqual(user_config['bar'], 123)
+
+        # Don't overwrite specified values
+        user_config = {'bar': 456}
+        DummyConfigWithDefaultSettings.validate(user_config)
+        self.assertEqual(user_config['bar'], 456)

+ 4 - 0
netbox/netbox/configuration.testing.py

@@ -18,6 +18,10 @@ PLUGINS = [
     'extras.tests.dummy_plugin',
     'extras.tests.dummy_plugin',
 ]
 ]
 
 
+PLUGINS_CONFIG = {
+    'foo': True,
+}
+
 REDIS = {
 REDIS = {
     'tasks': {
     'tasks': {
         'HOST': 'localhost',
         'HOST': 'localhost',

+ 4 - 25
netbox/netbox/settings.py

@@ -10,7 +10,6 @@ from urllib.parse import urlsplit
 from django.contrib.messages import constants as messages
 from django.contrib.messages import constants as messages
 from django.core.exceptions import ImproperlyConfigured, ValidationError
 from django.core.exceptions import ImproperlyConfigured, ValidationError
 from django.core.validators import URLValidator
 from django.core.validators import URLValidator
-from pkg_resources import parse_version
 
 
 
 
 #
 #
@@ -658,36 +657,16 @@ for plugin_name in PLUGINS:
             f"__init__.py file and point to the PluginConfig subclass."
             f"__init__.py file and point to the PluginConfig subclass."
         )
         )
 
 
-    # Check version constraints
-    parsed_min_version = parse_version(plugin_config.min_version or VERSION)
-    parsed_max_version = parse_version(plugin_config.max_version or VERSION)
-    if plugin_config.min_version and plugin_config.max_version and parsed_min_version > parsed_max_version:
-        raise ImproperlyConfigured(f"Plugin {plugin_name} specifies invalid version constraints!")
-    if plugin_config.min_version and parsed_min_version > parse_version(VERSION):
-        raise ImproperlyConfigured(f"Plugin {plugin_name} requires NetBox minimum version {plugin_config.min_version}!")
-    if plugin_config.max_version and parsed_max_version < parse_version(VERSION):
-        raise ImproperlyConfigured(f"Plugin {plugin_name} requires NetBox maximum version {plugin_config.max_version}!")
+    # Validate user-provided configuration settings and assign defaults
+    if plugin_name not in PLUGINS_CONFIG:
+        PLUGINS_CONFIG[plugin_name] = {}
+    plugin_config.validate(PLUGINS_CONFIG[plugin_name])
 
 
     # Add middleware
     # Add middleware
     plugin_middleware = plugin_config.middleware
     plugin_middleware = plugin_config.middleware
     if plugin_middleware and type(plugin_middleware) in (list, tuple):
     if plugin_middleware and type(plugin_middleware) in (list, tuple):
         MIDDLEWARE.extend(plugin_middleware)
         MIDDLEWARE.extend(plugin_middleware)
 
 
-    # Verify required configuration settings
-    if plugin_name not in PLUGINS_CONFIG:
-        PLUGINS_CONFIG[plugin_name] = {}
-    for setting in plugin_config.required_settings:
-        if setting not in PLUGINS_CONFIG[plugin_name]:
-            raise ImproperlyConfigured(
-                f"Plugin {plugin_name} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
-                f"configuration.py."
-            )
-
-    # Apply default configuration values
-    for setting, value in plugin_config.default_settings.items():
-        if setting not in PLUGINS_CONFIG[plugin_name]:
-            PLUGINS_CONFIG[plugin_name][setting] = value
-
     # Apply cacheops config
     # Apply cacheops config
     if type(plugin_config.caching_config) is not dict:
     if type(plugin_config.caching_config) is not dict:
         raise ImproperlyConfigured(f"Plugin {plugin_name} caching_config must be a dictionary.")
         raise ImproperlyConfigured(f"Plugin {plugin_name} caching_config must be a dictionary.")