Просмотр исходного кода

Merge pull request #4446 from netbox-community/plugin-testing

Plugin testing
Jeremy Stretch 5 лет назад
Родитель
Сommit
6cfd68d9fb

+ 5 - 4
netbox/extras/plugins/urls.py

@@ -18,13 +18,14 @@ plugin_admin_patterns = [
 ]
 ]
 
 
 # Register base/API URL patterns for each plugin
 # Register base/API URL patterns for each plugin
-for plugin in settings.PLUGINS:
-    app = apps.get_app_config(plugin)
+for plugin_path in settings.PLUGINS:
+    plugin_name = plugin_path.split('.')[-1]
+    app = apps.get_app_config(plugin_name)
     base_url = getattr(app, 'base_url') or app.label
     base_url = getattr(app, 'base_url') or app.label
 
 
     # Check if the plugin specifies any base URLs
     # Check if the plugin specifies any base URLs
     try:
     try:
-        urlpatterns = import_string(f"{plugin}.urls.urlpatterns")
+        urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
         plugin_patterns.append(
         plugin_patterns.append(
             path(f"{base_url}/", include((urlpatterns, app.label)))
             path(f"{base_url}/", include((urlpatterns, app.label)))
         )
         )
@@ -33,7 +34,7 @@ for plugin in settings.PLUGINS:
 
 
     # Check if the plugin specifies any API URLs
     # Check if the plugin specifies any API URLs
     try:
     try:
-        urlpatterns = import_string(f"{plugin}.api.urls.urlpatterns")
+        urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
         plugin_api_patterns.append(
         plugin_api_patterns.append(
             path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
             path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
         )
         )

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

@@ -0,0 +1,15 @@
+from extras.plugins import PluginConfig
+
+
+class DummyPluginConfig(PluginConfig):
+    name = 'extras.tests.dummy_plugin'
+    verbose_name = 'Dummy plugin'
+    version = '0.0'
+    description = 'For testing purposes only'
+    base_url = 'dummy-plugin'
+    middleware = [
+        'extras.tests.dummy_plugin.middleware.DummyMiddleware'
+    ]
+
+
+config = DummyPluginConfig

+ 9 - 0
netbox/extras/tests/dummy_plugin/admin.py

@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+from netbox.admin import admin_site
+from .models import DummyModel
+
+
+@admin.register(DummyModel, site=admin_site)
+class DummyModelAdmin(admin.ModelAdmin):
+    list_display = ('name', 'number')

+ 9 - 0
netbox/extras/tests/dummy_plugin/api/serializers.py

@@ -0,0 +1,9 @@
+from rest_framework.serializers import ModelSerializer
+from extras.tests.dummy_plugin.models import DummyModel
+
+
+class DummySerializer(ModelSerializer):
+
+    class Meta:
+        model = DummyModel
+        fields = ('id', 'name', 'number')

+ 6 - 0
netbox/extras/tests/dummy_plugin/api/urls.py

@@ -0,0 +1,6 @@
+from rest_framework import routers
+from .views import DummyViewSet
+
+router = routers.DefaultRouter()
+router.register('dummy-models', DummyViewSet)
+urlpatterns = router.urls

+ 8 - 0
netbox/extras/tests/dummy_plugin/api/views.py

@@ -0,0 +1,8 @@
+from rest_framework.viewsets import ModelViewSet
+from extras.tests.dummy_plugin.models import DummyModel
+from .serializers import DummySerializer
+
+
+class DummyViewSet(ModelViewSet):
+    queryset = DummyModel.objects.all()
+    serializer_class = DummySerializer

+ 7 - 0
netbox/extras/tests/dummy_plugin/middleware.py

@@ -0,0 +1,7 @@
+class DummyMiddleware:
+
+    def __init__(self, get_response):
+        self.get_response = get_response
+
+    def __call__(self, request):
+        return self.get_response(request)

+ 23 - 0
netbox/extras/tests/dummy_plugin/migrations/0001_initial.py

@@ -0,0 +1,23 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DummyModel',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
+                ('name', models.CharField(max_length=20)),
+                ('number', models.IntegerField(default=100)),
+            ],
+            options={
+                'ordering': ['name'],
+            },
+        ),
+    ]

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


+ 13 - 0
netbox/extras/tests/dummy_plugin/models.py

@@ -0,0 +1,13 @@
+from django.db import models
+
+
+class DummyModel(models.Model):
+    name = models.CharField(
+        max_length=20
+    )
+    number = models.IntegerField(
+        default=100
+    )
+
+    class Meta:
+        ordering = ['name']

+ 25 - 0
netbox/extras/tests/dummy_plugin/navigation.py

@@ -0,0 +1,25 @@
+from extras.plugins import PluginMenuButton, PluginMenuItem
+
+
+menu_items = (
+    PluginMenuItem(
+        link='plugins:dummy_plugin:dummy_models',
+        link_text='Item 1',
+        buttons=(
+            PluginMenuButton(
+                link='admin:dummy_plugin_dummymodel_add',
+                title='Add a new dummy model',
+                icon_class='fa-plus',
+            ),
+            PluginMenuButton(
+                link='admin:dummy_plugin_dummymodel_add',
+                title='Add a new dummy model',
+                icon_class='fa-plus',
+            ),
+        )
+    ),
+    PluginMenuItem(
+        link='plugins:dummy_plugin:dummy_models',
+        link_text='Item 2',
+    ),
+)

+ 20 - 0
netbox/extras/tests/dummy_plugin/template_content.py

@@ -0,0 +1,20 @@
+from extras.plugins import PluginTemplateExtension
+
+
+class SiteContent(PluginTemplateExtension):
+    model = 'dcim.site'
+
+    def left_page(self):
+        return "SITE CONTENT - LEFT PAGE"
+
+    def right_page(self):
+        return "SITE CONTENT - RIGHT PAGE"
+
+    def full_width_page(self):
+        return "SITE CONTENT - FULL WIDTH PAGE"
+
+    def full_buttons(self):
+        return "SITE CONTENT - BUTTONS"
+
+
+template_extensions = [SiteContent]

+ 8 - 0
netbox/extras/tests/dummy_plugin/urls.py

@@ -0,0 +1,8 @@
+from django.urls import path
+
+from . import views
+
+
+urlpatterns = (
+    path('models/', views.DummyModelsView.as_view(), name='dummy_models'),
+)

+ 11 - 0
netbox/extras/tests/dummy_plugin/views.py

@@ -0,0 +1,11 @@
+from django.http import HttpResponse
+from django.views.generic import View
+
+from .models import DummyModel
+
+
+class DummyModelsView(View):
+
+    def get(self, request):
+        instance_count = DummyModel.objects.count()
+        return HttpResponse(f"Instances: {instance_count}")

+ 79 - 0
netbox/extras/tests/test_plugins.py

@@ -0,0 +1,79 @@
+from unittest import skipIf
+
+from django.conf import settings
+from django.test import Client, TestCase, override_settings
+from django.urls import reverse
+
+from extras.registry import registry
+
+
+@skipIf('extras.tests.dummy_plugin.DummyPluginConfig' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
+class PluginTest(TestCase):
+
+    def test_config(self):
+
+        self.assertIn('extras.tests.dummy_plugin.DummyPluginConfig', settings.INSTALLED_APPS)
+
+    def test_models(self):
+        from extras.tests.dummy_plugin.models import DummyModel
+
+        # Test saving an instance
+        instance = DummyModel(name='Instance 1', number=100)
+        instance.save()
+        self.assertIsNotNone(instance.pk)
+
+        # Test deleting an instance
+        instance.delete()
+        self.assertIsNone(instance.pk)
+
+    def test_admin(self):
+
+        # Test admin view URL resolution
+        url = reverse('admin:dummy_plugin_dummymodel_add')
+        self.assertEqual(url, '/admin/dummy_plugin/dummymodel/add/')
+
+    def test_views(self):
+
+        # Test URL resolution
+        url = reverse('plugins:dummy_plugin:dummy_models')
+        self.assertEqual(url, '/plugins/dummy-plugin/models/')
+
+        # Test GET request
+        client = Client()
+        response = client.get(url)
+        self.assertEqual(response.status_code, 200)
+
+    @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
+    def test_api_views(self):
+
+        # Test URL resolution
+        url = reverse('plugins-api:dummy_plugin-api:dummymodel-list')
+        self.assertEqual(url, '/api/plugins/dummy-plugin/dummy-models/')
+
+        # Test GET request
+        client = Client()
+        response = client.get(url)
+        self.assertEqual(response.status_code, 200)
+
+    def test_menu_items(self):
+        """
+        Check that plugin MenuItems and MenuButtons are registered.
+        """
+        self.assertIn('Dummy plugin', registry['plugin_menu_items'])
+        menu_items = registry['plugin_menu_items']['Dummy plugin']
+        self.assertEqual(len(menu_items), 2)
+        self.assertEqual(len(menu_items[0].buttons), 2)
+
+    def test_template_extensions(self):
+        """
+        Check that plugin TemplateExtensions are registered.
+        """
+        from extras.tests.dummy_plugin.template_content import SiteContent
+
+        self.assertIn(SiteContent, registry['plugin_template_extensions']['dcim.site'])
+
+    def test_middleware(self):
+        """
+        Check that plugin middleware is registered.
+        """
+        self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE)

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

@@ -0,0 +1,40 @@
+###################################################################
+#  This file serves as a base configuration for testing purposes  #
+#  only. It is not intended for production use.                   #
+###################################################################
+
+ALLOWED_HOSTS = ['*']
+
+DATABASE = {
+    'NAME': 'netbox',
+    'USER': '',
+    'PASSWORD': '',
+    'HOST': 'localhost',
+    'PORT': '',
+    'CONN_MAX_AGE': 300,
+}
+
+PLUGINS = [
+    'extras.tests.dummy_plugin',
+]
+
+REDIS = {
+    'tasks': {
+        'HOST': 'localhost',
+        'PORT': 6379,
+        'PASSWORD': '',
+        'DATABASE': 0,
+        'DEFAULT_TIMEOUT': 300,
+        'SSL': False,
+    },
+    'caching': {
+        'HOST': 'localhost',
+        'PORT': 6379,
+        'PASSWORD': '',
+        'DATABASE': 1,
+        'DEFAULT_TIMEOUT': 300,
+        'SSL': False,
+    }
+}
+
+SECRET_KEY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

+ 2 - 5
scripts/cibuild.sh

@@ -34,11 +34,8 @@ if [[ $RC != 0 ]]; then
 	EXIT=$RC
 	EXIT=$RC
 fi
 fi
 
 
-# Prepare configuration file for use in CI
-CONFIG="netbox/netbox/configuration.py"
-cp netbox/netbox/configuration.example.py $CONFIG
-sed -i -e "s/ALLOWED_HOSTS = \[\]/ALLOWED_HOSTS = \['*'\]/g" $CONFIG
-sed -i -e "s/SECRET_KEY = ''/SECRET_KEY = 'netboxci'/g" $CONFIG
+# Point to the testing configuration file for use in CI
+ln -s configuration.testing.py netbox/netbox/configuration.py
 
 
 # Run NetBox tests
 # Run NetBox tests
 coverage run --source="netbox/" netbox/manage.py test netbox/
 coverage run --source="netbox/" netbox/manage.py test netbox/