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 inspect
from pkg_resources import parse_version
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.utils.module_loading import import_string
@ -70,6 +73,37 @@ class PluginConfig(AppConfig):
except ImportError:
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

View File

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

View File

@ -1,13 +1,15 @@
from unittest import skipIf
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.test import Client, TestCase, override_settings
from django.urls import reverse
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):
def test_config(self):
@ -77,3 +79,52 @@ class PluginTest(TestCase):
Check that plugin middleware is registered.
"""
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',
]
PLUGINS_CONFIG = {
'foo': True,
}
REDIS = {
'tasks': {
'HOST': 'localhost',

View File

@ -10,7 +10,6 @@ from urllib.parse import urlsplit
from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured, ValidationError
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."
)
# 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
plugin_middleware = plugin_config.middleware
if plugin_middleware and type(plugin_middleware) in (list, tuple):
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
if type(plugin_config.caching_config) is not dict:
raise ImproperlyConfigured(f"Plugin {plugin_name} caching_config must be a dictionary.")