1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Add tests for plugin configuration, min/max version

This commit is contained in:
Jeremy Stretch
2020-04-06 11:44:38 -04:00
parent 58442b1af6
commit 9ffc404027
5 changed files with 96 additions and 26 deletions

View File

@ -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

View File

@ -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'
] ]

View File

@ -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)

View File

@ -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',

View File

@ -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 # Validate user-provided configuration settings and assign defaults
parsed_min_version = parse_version(plugin_config.min_version or VERSION) if plugin_name not in PLUGINS_CONFIG:
parsed_max_version = parse_version(plugin_config.max_version or VERSION) PLUGINS_CONFIG[plugin_name] = {}
if plugin_config.min_version and plugin_config.max_version and parsed_min_version > parsed_max_version: plugin_config.validate(PLUGINS_CONFIG[plugin_name])
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}!")
# 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.")