netbox-community / netbox

The premier source of truth powering network automation. Open source under Apache 2. Try NetBox Cloud free: https://netboxlabs.com/free-netbox-cloud/
http://netboxlabs.com/oss/netbox/
Apache License 2.0
16.2k stars 2.59k forks source link

Using APIViewTestCases and ViewTestCases in plugins #10279

Open miaow2 opened 2 years ago

miaow2 commented 2 years ago

NetBox version

v3.3.0

Feature type

Change to existing functionality

Proposed functionality

Hi, NetBox team! I am writing a plugin for NetBox and want to use APIViewTestCases and ViewTestCases in tests. But they don't work because test cases don't know that plugins namespaces are extended by plugins: and plugins-api:. GraphQLTestCase does not work because function dynamic_import (from utilities/utils.py) can't import plugins graphql types.

I've made several changes and test cases started working in plugins. NetBox core tests also working. Check changes below.

In API and views test cases checks if app_label is in settings.PLUGINS

utilities/testing/api.py

class APITestCase(ModelTestCase):

    def _get_view_namespace(self):
-        return f'{self.view_namespace or self.model._meta.app_label}-api'
+        app_label = self.view_namespace or self.model._meta.app_label
+       if app_label in settings.PLUGINS:
+            app_label = f'plugins-api:{app_label}'
+        return f'{app_label}-api'

utilities/testing/api.py

+from django.conf import settings

class ModelViewTestCase(ModelTestCase):

    def _get_base_url(self):
-        return '{}:{}_{{}}'.format(
-            self.model._meta.app_label,
-            self.model._meta.model_name
-        )
+        app_label = self.model._meta.app_label
+        if app_label in settings.PLUGINS:
+            app_label = f'plugins:{app_label}'
+        return f'{app_label}:{self.model._meta.model_name}_{{}}'

In GraphQLTestCase I'm using import_object (from extras/plugins/utils.py) instead of dynamic_import for plugins. utilities/testing/api.py

+from extras.plugins.utils import import_object

def get_graphql_type_for_model(model):
    """
    Return the GraphQL type class for the given model.
    """
    app_name, model_name = model._meta.label.split('.')
    # Object types for Django's auth models are in the users app
    if app_name == 'auth':
        app_name = 'users'
-    class_name = f'{app_name}.graphql.types.{model_name}Type'
-    try:
-        return dynamic_import(class_name)
-    except AttributeError:
-        raise GraphQLTypeNotFound(f"Could not find GraphQL type for {app_name}.{model_name}")
+    if app_name in settings.PLUGINS:
+        class_name = f'{app_name}.graphql.{model_name}Type'
+        graphql_type = import_object(class_name)
+        if graphql_type is None:
+            raise GraphQLTypeNotFound(f"Could not find GraphQL type for {app_name}.{model_name}")
+    else:
+        class_name = f'{app_name}.graphql.types.{model_name}Type'
+        try:
+            graphql_type = dynamic_import(class_name)
+        except AttributeError:
+            raise GraphQLTypeNotFound(f"Could not find GraphQL type for {app_name}.{model_name}")

+    return graphql_type

Use case

It would be great to use NetBox abstractions in plugin testing, it really simplifies the work. I can create a pull request if this solution is right for you. If not, feel free to close this issue. Early I've opened discussion.

Database changes

There are no database changes.

External dependencies

There are no external dependencies.

peteeckel commented 1 year ago

As it's been a while - I wholeheartedly support this issue.

I've been (semi-legally :-)) using inherited versions of the classes in question since the beginning and found lots of errors that way - actually a long time ago I raised this a while ago, but lost track of it.