mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 12:06:44 +00:00
More plugin testing (#3052)
* Add a check of a child panel too * do not cover error catching * test for implementation error * Add warning to test for * Add test for event_sample * ignore safety switches * Add a settings flag to enable event testing * test if not implemented is raises * raise plugin specific errors * use plugin specific error * fix assertation * add test for mixin * this point can't be reached * add tests for locate plugin * fix assertations * fix function call * refert switch * this is already caught by the internal API * also cover mixin redirect
This commit is contained in:
parent
a7ef560ee3
commit
1c6e5f0f20
@ -910,6 +910,7 @@ if DEBUG or TESTING:
|
|||||||
# Plugin test settings
|
# Plugin test settings
|
||||||
PLUGIN_TESTING = get_setting('PLUGIN_TESTING', TESTING) # are plugins beeing tested?
|
PLUGIN_TESTING = get_setting('PLUGIN_TESTING', TESTING) # are plugins beeing tested?
|
||||||
PLUGIN_TESTING_SETUP = get_setting('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing?
|
PLUGIN_TESTING_SETUP = get_setting('PLUGIN_TESTING_SETUP', False) # load plugins from setup hooks in testing?
|
||||||
|
PLUGIN_TESTING_EVENTS = False # Flag if events are tested right now
|
||||||
PLUGIN_RETRY = get_setting('PLUGIN_RETRY', 5) # how often should plugin loading be tried?
|
PLUGIN_RETRY = get_setting('PLUGIN_RETRY', 5) # how often should plugin loading be tried?
|
||||||
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
PLUGIN_FILE_CHECKED = False # Was the plugin file checked?
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class BulkNotificationMethodTests(BaseNotificationIntegrationTest):
|
|||||||
def test_BulkNotificationMethod(self):
|
def test_BulkNotificationMethod(self):
|
||||||
"""
|
"""
|
||||||
Ensure the implementation requirements are tested.
|
Ensure the implementation requirements are tested.
|
||||||
NotImplementedError needs to raise if the send_bulk() method is not set.
|
MixinNotImplementedError needs to raise if the send_bulk() method is not set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class WrongImplementation(BulkNotificationMethod):
|
class WrongImplementation(BulkNotificationMethod):
|
||||||
@ -94,7 +94,7 @@ class SingleNotificationMethodTests(BaseNotificationIntegrationTest):
|
|||||||
def test_SingleNotificationMethod(self):
|
def test_SingleNotificationMethod(self):
|
||||||
"""
|
"""
|
||||||
Ensure the implementation requirements are tested.
|
Ensure the implementation requirements are tested.
|
||||||
NotImplementedError needs to raise if the send() method is not set.
|
MixinNotImplementedError needs to raise if the send() method is not set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class WrongImplementation(SingleNotificationMethod):
|
class WrongImplementation(SingleNotificationMethod):
|
||||||
|
@ -26,9 +26,10 @@ def trigger_event(event, *args, **kwargs):
|
|||||||
|
|
||||||
if not settings.PLUGINS_ENABLED:
|
if not settings.PLUGINS_ENABLED:
|
||||||
# Do nothing if plugins are not enabled
|
# Do nothing if plugins are not enabled
|
||||||
return
|
return # pragma: no cover
|
||||||
|
|
||||||
if not canAppAccessDatabase():
|
# Make sure the database can be accessed and is not beeing tested rn
|
||||||
|
if not canAppAccessDatabase() and not settings.PLUGIN_TESTING_EVENTS:
|
||||||
logger.debug(f"Ignoring triggered event '{event}' - database not ready")
|
logger.debug(f"Ignoring triggered event '{event}' - database not ready")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ def process_event(plugin_slug, event, *args, **kwargs):
|
|||||||
|
|
||||||
plugin = registry.plugins.get(plugin_slug, None)
|
plugin = registry.plugins.get(plugin_slug, None)
|
||||||
|
|
||||||
if plugin is None:
|
if plugin is None: # pragma: no cover
|
||||||
logger.error(f"Could not find matching plugin for '{plugin_slug}'")
|
logger.error(f"Could not find matching plugin for '{plugin_slug}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ def allow_table_event(table_name):
|
|||||||
|
|
||||||
if isImportingData():
|
if isImportingData():
|
||||||
# Prevent table events during the data import process
|
# Prevent table events during the data import process
|
||||||
return False
|
return False # pragma: no cover
|
||||||
|
|
||||||
table_name = table_name.lower().strip()
|
table_name = table_name.lower().strip()
|
||||||
|
|
||||||
|
@ -541,7 +541,7 @@ class PanelMixin:
|
|||||||
|
|
||||||
def get_custom_panels(self, view, request):
|
def get_custom_panels(self, view, request):
|
||||||
""" This method *must* be implemented by the plugin class """
|
""" This method *must* be implemented by the plugin class """
|
||||||
raise NotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
|
raise MixinNotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
|
||||||
|
|
||||||
def get_panel_context(self, view, request, context):
|
def get_panel_context(self, view, request, context):
|
||||||
"""
|
"""
|
||||||
@ -559,7 +559,7 @@ class PanelMixin:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
context['object'] = view.get_object()
|
context['object'] = view.get_object()
|
||||||
except AttributeError:
|
except AttributeError: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -8,6 +8,7 @@ from error_report.models import Error
|
|||||||
|
|
||||||
from InvenTree.helpers import InvenTreeTestCase
|
from InvenTree.helpers import InvenTreeTestCase
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
|
from plugin.base.integration.mixins import PanelMixin
|
||||||
from plugin.helpers import MixinNotImplementedError
|
from plugin.helpers import MixinNotImplementedError
|
||||||
from plugin.mixins import (APICallMixin, AppMixin, NavigationMixin,
|
from plugin.mixins import (APICallMixin, AppMixin, NavigationMixin,
|
||||||
SettingsMixin, UrlsMixin)
|
SettingsMixin, UrlsMixin)
|
||||||
@ -324,7 +325,7 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
urls = [
|
urls = [
|
||||||
reverse('part-detail', kwargs={'pk': 1}),
|
reverse('part-detail', kwargs={'pk': 1}),
|
||||||
reverse('stock-item-detail', kwargs={'pk': 2}),
|
reverse('stock-item-detail', kwargs={'pk': 2}),
|
||||||
reverse('stock-location-detail', kwargs={'pk': 1}),
|
reverse('stock-location-detail', kwargs={'pk': 2}),
|
||||||
]
|
]
|
||||||
|
|
||||||
plugin.set_setting('ENABLE_HELLO_WORLD', False)
|
plugin.set_setting('ENABLE_HELLO_WORLD', False)
|
||||||
@ -379,3 +380,13 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
|
|
||||||
# Assert that each request threw an error
|
# Assert that each request threw an error
|
||||||
self.assertEqual(Error.objects.count(), n_errors + len(urls))
|
self.assertEqual(Error.objects.count(), n_errors + len(urls))
|
||||||
|
|
||||||
|
def test_mixin(self):
|
||||||
|
"""Test that ImplementationError is raised"""
|
||||||
|
|
||||||
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
|
class Wrong(PanelMixin, InvenTreePlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
plugin = Wrong()
|
||||||
|
plugin.get_custom_panels('abc', 'abc')
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from plugin.helpers import MixinImplementationError
|
from plugin.helpers import MixinNotImplementedError
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class LocateMixin:
|
|||||||
if item.in_stock and item.location is not None:
|
if item.in_stock and item.location is not None:
|
||||||
self.locate_stock_location(item.location.pk)
|
self.locate_stock_location(item.location.pk)
|
||||||
|
|
||||||
except StockItem.DoesNotExist:
|
except StockItem.DoesNotExist: # pragma: no cover
|
||||||
logger.warning("LocateMixin: StockItem pk={item_pk} not found")
|
logger.warning("LocateMixin: StockItem pk={item_pk} not found")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -71,4 +71,4 @@ class LocateMixin:
|
|||||||
|
|
||||||
Note: The default implementation here does nothing!
|
Note: The default implementation here does nothing!
|
||||||
"""
|
"""
|
||||||
raise MixinImplementationError
|
raise MixinNotImplementedError
|
||||||
|
@ -5,7 +5,8 @@ Unit tests for the 'locate' plugin mixin class
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
from plugin.registry import registry
|
from plugin import InvenTreePlugin, MixinNotImplementedError, registry
|
||||||
|
from plugin.base.locate.mixins import LocateMixin
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
|
|
||||||
|
|
||||||
@ -145,3 +146,17 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
# Item metadata should have been altered!
|
# Item metadata should have been altered!
|
||||||
self.assertTrue(location.metadata['located'])
|
self.assertTrue(location.metadata['located'])
|
||||||
|
|
||||||
|
def test_mixin_locate(self):
|
||||||
|
"""Test the sample mixin redirection"""
|
||||||
|
class SamplePlugin(LocateMixin, InvenTreePlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
plugin = SamplePlugin()
|
||||||
|
|
||||||
|
# Test that the request is patched through to location
|
||||||
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
|
plugin.locate_stock_item(1)
|
||||||
|
|
||||||
|
# Test that it runs through
|
||||||
|
plugin.locate_stock_item(999)
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
Sample plugin which responds to events
|
Sample plugin which responds to events
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import EventMixin
|
from plugin.mixins import EventMixin
|
||||||
|
|
||||||
@ -21,3 +25,7 @@ class EventPluginSample(EventMixin, InvenTreePlugin):
|
|||||||
print(f"Processing triggered event: '{event}'")
|
print(f"Processing triggered event: '{event}'")
|
||||||
print("args:", str(args))
|
print("args:", str(args))
|
||||||
print("kwargs:", str(kwargs))
|
print("kwargs:", str(kwargs))
|
||||||
|
|
||||||
|
# Issue warning that we can test for
|
||||||
|
if settings.PLUGIN_TESTING:
|
||||||
|
warnings.warn(f'Event `{event}` triggered')
|
||||||
|
40
InvenTree/plugin/samples/event/test_event_sample.py
Normal file
40
InvenTree/plugin/samples/event/test_event_sample.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""Unit tests for event_sample sample plugins"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from plugin import InvenTreePlugin, registry
|
||||||
|
from plugin.base.event.events import trigger_event
|
||||||
|
from plugin.helpers import MixinNotImplementedError
|
||||||
|
from plugin.mixins import EventMixin
|
||||||
|
|
||||||
|
|
||||||
|
class EventPluginSampleTests(TestCase):
|
||||||
|
"""Tests for EventPluginSample"""
|
||||||
|
|
||||||
|
def test_run_event(self):
|
||||||
|
"""Check if the event is issued"""
|
||||||
|
# Activate plugin
|
||||||
|
config = registry.get_plugin('sampleevent').plugin_config()
|
||||||
|
config.active = True
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
# Enable event testing
|
||||||
|
settings.PLUGIN_TESTING_EVENTS = True
|
||||||
|
# Check that an event is issued
|
||||||
|
with self.assertWarns(Warning) as cm:
|
||||||
|
trigger_event('test.event')
|
||||||
|
self.assertEqual(cm.warning.args[0], 'Event `test.event` triggered')
|
||||||
|
|
||||||
|
# Disable again
|
||||||
|
settings.PLUGIN_TESTING_EVENTS = False
|
||||||
|
|
||||||
|
def test_mixin(self):
|
||||||
|
"""Test that MixinNotImplementedError is raised"""
|
||||||
|
|
||||||
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
|
class Wrong(EventMixin, InvenTreePlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
plugin = Wrong()
|
||||||
|
plugin.process_event('abc')
|
@ -37,7 +37,7 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
|
|||||||
# Tag metadata
|
# Tag metadata
|
||||||
item.set_metadata('located', True)
|
item.set_metadata('located', True)
|
||||||
|
|
||||||
except (ValueError, StockItem.DoesNotExist):
|
except (ValueError, StockItem.DoesNotExist): # pragma: no cover
|
||||||
logger.error(f"StockItem ID {item_pk} does not exist!")
|
logger.error(f"StockItem ID {item_pk} does not exist!")
|
||||||
|
|
||||||
def locate_stock_location(self, location_pk):
|
def locate_stock_location(self, location_pk):
|
||||||
@ -53,5 +53,5 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
|
|||||||
# Tag metadata
|
# Tag metadata
|
||||||
location.set_metadata('located', True)
|
location.set_metadata('located', True)
|
||||||
|
|
||||||
except (ValueError, StockLocation.DoesNotExist):
|
except (ValueError, StockLocation.DoesNotExist): # pragma: no cover
|
||||||
logger.error(f"Location ID {location_pk} does not exist!")
|
logger.error(f"Location ID {location_pk} does not exist!")
|
||||||
|
60
InvenTree/plugin/samples/locate/test_locate_sample.py
Normal file
60
InvenTree/plugin/samples/locate/test_locate_sample.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Unit tests for locate_sample sample plugins"""
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||||
|
from plugin import InvenTreePlugin, registry
|
||||||
|
from plugin.helpers import MixinNotImplementedError
|
||||||
|
from plugin.mixins import LocateMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SampleLocatePlugintests(InvenTreeAPITestCase):
|
||||||
|
"""Tests for SampleLocatePlugin"""
|
||||||
|
|
||||||
|
fixtures = [
|
||||||
|
'location',
|
||||||
|
'category',
|
||||||
|
'part',
|
||||||
|
'stock'
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_run_locator(self):
|
||||||
|
"""Check if the event is issued"""
|
||||||
|
# Activate plugin
|
||||||
|
config = registry.get_plugin('samplelocate').plugin_config()
|
||||||
|
config.active = True
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
# Test APIs
|
||||||
|
url = reverse('api-locate-plugin')
|
||||||
|
|
||||||
|
# No plugin
|
||||||
|
self.post(url, {}, expected_code=400)
|
||||||
|
|
||||||
|
# Wrong plugin
|
||||||
|
self.post(url, {'plugin': 'sampleevent'}, expected_code=400)
|
||||||
|
|
||||||
|
# Right plugin - no search item
|
||||||
|
self.post(url, {'plugin': 'samplelocate'}, expected_code=400)
|
||||||
|
|
||||||
|
# Right plugin - wrong reference
|
||||||
|
self.post(url, {'plugin': 'samplelocate', 'item': 999}, expected_code=404)
|
||||||
|
|
||||||
|
# Right plugin - right reference
|
||||||
|
self.post(url, {'plugin': 'samplelocate', 'item': 1}, expected_code=200)
|
||||||
|
|
||||||
|
# Right plugin - wrong reference
|
||||||
|
self.post(url, {'plugin': 'samplelocate', 'location': 999}, expected_code=404)
|
||||||
|
|
||||||
|
# Right plugin - right reference
|
||||||
|
self.post(url, {'plugin': 'samplelocate', 'location': 1}, expected_code=200)
|
||||||
|
|
||||||
|
def test_mixin(self):
|
||||||
|
"""Test that MixinNotImplementedError is raised"""
|
||||||
|
|
||||||
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
|
class Wrong(LocateMixin, InvenTreePlugin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
plugin = Wrong()
|
||||||
|
plugin.locate_stock_location(1)
|
Loading…
x
Reference in New Issue
Block a user