mirror of
https://github.com/inventree/InvenTree.git
synced 2025-08-07 12:22:11 +00:00
chore(backend): increase coverage (#10121)
* chore(backend): increase coverage * add a full api based install / uninstall test * fix asserted code * delete unreachable code * clean up unused code * add more notification tests * fix test * order currencies
This commit is contained in:
@@ -5,11 +5,7 @@ from django.apps import AppConfig
|
|||||||
import structlog
|
import structlog
|
||||||
|
|
||||||
import InvenTree.ready
|
import InvenTree.ready
|
||||||
from common.settings import (
|
from common.settings import get_global_setting, set_global_setting
|
||||||
get_global_setting,
|
|
||||||
global_setting_overrides,
|
|
||||||
set_global_setting,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = structlog.get_logger('inventree')
|
logger = structlog.get_logger('inventree')
|
||||||
|
|
||||||
@@ -24,11 +20,10 @@ class CommonConfig(AppConfig):
|
|||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
"""Initialize restart flag clearance on startup."""
|
"""Initialize restart flag clearance on startup."""
|
||||||
if InvenTree.ready.isRunningMigrations():
|
if InvenTree.ready.isRunningMigrations(): # pragma: no cover
|
||||||
return
|
return
|
||||||
|
|
||||||
self.clear_restart_flag()
|
self.clear_restart_flag()
|
||||||
self.override_global_settings()
|
|
||||||
|
|
||||||
def clear_restart_flag(self):
|
def clear_restart_flag(self):
|
||||||
"""Clear the SERVER_RESTART_REQUIRED setting."""
|
"""Clear the SERVER_RESTART_REQUIRED setting."""
|
||||||
@@ -40,28 +35,5 @@ class CommonConfig(AppConfig):
|
|||||||
|
|
||||||
if not InvenTree.ready.isImportingData():
|
if not InvenTree.ready.isImportingData():
|
||||||
set_global_setting('SERVER_RESTART_REQUIRED', False, None)
|
set_global_setting('SERVER_RESTART_REQUIRED', False, None)
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def override_global_settings(self):
|
|
||||||
"""Update global settings based on environment variables."""
|
|
||||||
overrides = global_setting_overrides()
|
|
||||||
|
|
||||||
if not overrides:
|
|
||||||
return
|
|
||||||
|
|
||||||
for key, value in overrides.items():
|
|
||||||
try:
|
|
||||||
current_value = get_global_setting(key, create=False)
|
|
||||||
|
|
||||||
if current_value != value:
|
|
||||||
logger.info(
|
|
||||||
'INVE-I1: Overriding global setting: %s = %s',
|
|
||||||
value,
|
|
||||||
current_value,
|
|
||||||
)
|
|
||||||
set_global_setting(key, value, None, create=True)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
logger.warning('Failed to override global setting %s -> %s', key, value)
|
|
||||||
continue
|
|
||||||
|
@@ -18,13 +18,13 @@ class CreateModelOrSkip(migrations.CreateModel):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def state_forwards(self, app_label, state) -> None:
|
def state_forwards(self, app_label, state) -> None:
|
||||||
try:
|
try:
|
||||||
super().state_forwards(app_label, state)
|
super().state_forwards(app_label, state)
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@@ -49,18 +49,18 @@ def set_currencies(apps, schema_editor):
|
|||||||
|
|
||||||
value = ','.join(valid_codes)
|
value = ','.join(valid_codes)
|
||||||
|
|
||||||
if not settings.TESTING:
|
if not settings.TESTING: # pragma: no cover
|
||||||
print(f"Found existing currency codes:", value)
|
print(f"Found existing currency codes:", value)
|
||||||
|
|
||||||
setting = InvenTreeSetting.objects.filter(key=key).first()
|
setting = InvenTreeSetting.objects.filter(key=key).first()
|
||||||
|
|
||||||
if setting:
|
if setting:
|
||||||
if not settings.TESTING:
|
if not settings.TESTING: # pragma: no cover
|
||||||
print(f"- Updating existing setting for currency codes")
|
print(f"- Updating existing setting for currency codes")
|
||||||
setting.value = value
|
setting.value = value
|
||||||
setting.save()
|
setting.save()
|
||||||
else:
|
else:
|
||||||
if not settings.TESTING:
|
if not settings.TESTING: # pragma: no cover
|
||||||
print(f"- Creating new setting for currency codes")
|
print(f"- Creating new setting for currency codes")
|
||||||
setting = InvenTreeSetting(key=key, value=value)
|
setting = InvenTreeSetting(key=key, value=value)
|
||||||
setting.save()
|
setting.save()
|
||||||
|
@@ -288,7 +288,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
cache.set(key, self, timeout=3600)
|
cache.set(key, self, timeout=3600)
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -558,7 +558,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
or InvenTree.ready.isRunningMigrations()
|
or InvenTree.ready.isRunningMigrations()
|
||||||
or InvenTree.ready.isRebuildingData()
|
or InvenTree.ready.isRebuildingData()
|
||||||
or InvenTree.ready.isRunningBackup()
|
or InvenTree.ready.isRunningBackup()
|
||||||
):
|
): # pragma: no cover
|
||||||
create = False
|
create = False
|
||||||
access_global_cache = False
|
access_global_cache = False
|
||||||
|
|
||||||
@@ -706,7 +706,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
or InvenTree.ready.isRunningMigrations()
|
or InvenTree.ready.isRunningMigrations()
|
||||||
or InvenTree.ready.isRebuildingData()
|
or InvenTree.ready.isRebuildingData()
|
||||||
or InvenTree.ready.isRunningBackup()
|
or InvenTree.ready.isRunningBackup()
|
||||||
):
|
): # pragma: no cover
|
||||||
return
|
return
|
||||||
|
|
||||||
attempts = int(kwargs.get('attempts', 3))
|
attempts = int(kwargs.get('attempts', 3))
|
||||||
@@ -731,7 +731,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
logger.warning("Database is locked, cannot set setting '%s'", key)
|
logger.warning("Database is locked, cannot set setting '%s'", key)
|
||||||
# Likely the DB is locked - not much we can do here
|
# Likely the DB is locked - not much we can do here
|
||||||
return
|
return
|
||||||
except Exception as exc:
|
except Exception as exc: # pragma: no cover
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
||||||
)
|
)
|
||||||
@@ -766,7 +766,7 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError):
|
||||||
logger.warning("Database is locked, cannot set setting '%s'", key)
|
logger.warning("Database is locked, cannot set setting '%s'", key)
|
||||||
# Likely the DB is locked - not much we can do here
|
# Likely the DB is locked - not much we can do here
|
||||||
except Exception as exc:
|
except Exception as exc: # pragma: no cover
|
||||||
# Some other error
|
# Some other error
|
||||||
logger.exception(
|
logger.exception(
|
||||||
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
"Error setting setting '%s' for %s: %s", key, str(cls), str(type(exc))
|
||||||
|
@@ -22,92 +22,6 @@ logger = structlog.get_logger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
# region methods
|
# region methods
|
||||||
class NotificationMethod:
|
|
||||||
"""Base class for notification methods."""
|
|
||||||
|
|
||||||
METHOD_NAME = ''
|
|
||||||
METHOD_ICON = None
|
|
||||||
CONTEXT_BUILTIN = ['name', 'message']
|
|
||||||
CONTEXT_EXTRA = []
|
|
||||||
GLOBAL_SETTING = None
|
|
||||||
USER_SETTING = None
|
|
||||||
|
|
||||||
def __init__(self, obj: Model, category: str, targets: list, context) -> None:
|
|
||||||
"""Check that the method is read.
|
|
||||||
|
|
||||||
This checks that:
|
|
||||||
- All needed functions are implemented
|
|
||||||
- The method is not disabled via plugin
|
|
||||||
- All needed context values were provided
|
|
||||||
"""
|
|
||||||
# Check if a sending fnc is defined
|
|
||||||
if (not hasattr(self, 'send')) and (not hasattr(self, 'send_bulk')):
|
|
||||||
raise NotImplementedError(
|
|
||||||
'A NotificationMethod must either define a `send` or a `send_bulk` method'
|
|
||||||
)
|
|
||||||
|
|
||||||
# No method name is no good
|
|
||||||
if self.METHOD_NAME in ('', None):
|
|
||||||
raise NotImplementedError(
|
|
||||||
f'The NotificationMethod {self.__class__} did not provide a METHOD_NAME'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if plugin is disabled - if so do not gather targets etc.
|
|
||||||
if self.global_setting_disable():
|
|
||||||
self.targets = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Define arguments
|
|
||||||
self.obj = obj
|
|
||||||
self.category = category
|
|
||||||
self.targets = targets
|
|
||||||
self.context = self.check_context(context)
|
|
||||||
|
|
||||||
# Gather targets
|
|
||||||
self.targets = self.get_targets()
|
|
||||||
|
|
||||||
def check_context(self, context):
|
|
||||||
"""Check that all values defined in the methods CONTEXT were provided in the current context."""
|
|
||||||
|
|
||||||
def check(ref, obj):
|
|
||||||
# the obj is not accessible so we are on the end
|
|
||||||
if not isinstance(obj, (list, dict, tuple)):
|
|
||||||
return ref
|
|
||||||
|
|
||||||
# check if the ref exists
|
|
||||||
if isinstance(ref, str):
|
|
||||||
if not obj.get(ref):
|
|
||||||
return ref
|
|
||||||
return False
|
|
||||||
|
|
||||||
# nested
|
|
||||||
elif isinstance(ref, (tuple, list)):
|
|
||||||
if len(ref) == 1:
|
|
||||||
return check(ref[0], obj)
|
|
||||||
ret = check(ref[0], obj)
|
|
||||||
if ret:
|
|
||||||
return ret
|
|
||||||
return check(ref[1:], obj[ref[0]])
|
|
||||||
|
|
||||||
# other cases -> raise
|
|
||||||
raise NotImplementedError(
|
|
||||||
'This type can not be used as a context reference'
|
|
||||||
)
|
|
||||||
|
|
||||||
missing = []
|
|
||||||
for item in (*self.CONTEXT_BUILTIN, *self.CONTEXT_EXTRA):
|
|
||||||
ret = check(item, context)
|
|
||||||
if ret:
|
|
||||||
missing.append(ret)
|
|
||||||
|
|
||||||
if missing:
|
|
||||||
raise NotImplementedError(
|
|
||||||
f'The `context` is missing the following items:\n{missing}'
|
|
||||||
)
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class NotificationBody:
|
class NotificationBody:
|
||||||
"""Information needed to create a notification.
|
"""Information needed to create a notification.
|
||||||
@@ -182,7 +96,7 @@ def trigger_notification(
|
|||||||
kwargs: Additional arguments to pass to the notification method
|
kwargs: Additional arguments to pass to the notification method
|
||||||
"""
|
"""
|
||||||
# Check if data is importing currently
|
# Check if data is importing currently
|
||||||
if isImportingData() or isRebuildingData():
|
if isImportingData() or isRebuildingData(): # pragma: no cover
|
||||||
return
|
return
|
||||||
|
|
||||||
targets = kwargs.get('targets')
|
targets = kwargs.get('targets')
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
"""Data migration unit tests for the 'common' app."""
|
"""Data migration unit tests for the 'common' app."""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
|
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
@@ -208,3 +209,49 @@ class TestForwardMigrations(MigratorTestCase):
|
|||||||
'stockitem',
|
'stockitem',
|
||||||
]:
|
]:
|
||||||
self.assertEqual(Attachment.objects.filter(model_type=model).count(), 2)
|
self.assertEqual(Attachment.objects.filter(model_type=model).count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def prep_currency_migration(self, vals: str):
|
||||||
|
"""Prepare the environment for the currency migration tests."""
|
||||||
|
# Set keys
|
||||||
|
os.environ['INVENTREE_CURRENCIES'] = vals
|
||||||
|
|
||||||
|
# And setting
|
||||||
|
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||||
|
|
||||||
|
setting = InvenTreeSetting(key='CURRENCY_CODES', value='123')
|
||||||
|
setting.save()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCurrencyMigration(MigratorTestCase):
|
||||||
|
"""Test currency migration."""
|
||||||
|
|
||||||
|
migrate_from = ('common', '0022_projectcode_responsible')
|
||||||
|
migrate_to = ('common', '0023_auto_20240602_1332')
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
"""Prepare the environment for the migration test."""
|
||||||
|
prep_currency_migration(self, 'USD,EUR,GBP')
|
||||||
|
|
||||||
|
def test_currency_migration(self):
|
||||||
|
"""Test that the currency migration works."""
|
||||||
|
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||||
|
setting = InvenTreeSetting.objects.filter(key='CURRENCY_CODES').first()
|
||||||
|
self.assertEqual(setting.value, 'EUR,GBP,USD')
|
||||||
|
|
||||||
|
|
||||||
|
class TestCurrencyMigrationNo(MigratorTestCase):
|
||||||
|
"""Test currency migration."""
|
||||||
|
|
||||||
|
migrate_from = ('common', '0022_projectcode_responsible')
|
||||||
|
migrate_to = ('common', '0023_auto_20240602_1332')
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
"""Prepare the environment for the migration test."""
|
||||||
|
prep_currency_migration(self, 'YYY,ZZZ')
|
||||||
|
|
||||||
|
def test_currency_migration(self):
|
||||||
|
"""Test that no currency migration occurs if wrong currencies are set."""
|
||||||
|
InvenTreeSetting = self.old_state.apps.get_model('common', 'InvenTreeSetting')
|
||||||
|
setting = InvenTreeSetting.objects.filter(key='CURRENCY_CODES').first()
|
||||||
|
self.assertEqual(setting.value, '123')
|
||||||
|
@@ -8,6 +8,7 @@ from http import HTTPStatus
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@@ -21,6 +22,7 @@ from django.urls import reverse
|
|||||||
import PIL
|
import PIL
|
||||||
|
|
||||||
import common.validators
|
import common.validators
|
||||||
|
from common.notifications import trigger_notification
|
||||||
from common.settings import get_global_setting, set_global_setting
|
from common.settings import get_global_setting, set_global_setting
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
from InvenTree.unit_test import (
|
from InvenTree.unit_test import (
|
||||||
@@ -1073,6 +1075,7 @@ class NotificationTest(InvenTreeAPITestCase):
|
|||||||
"""Tests for NotificationEntry."""
|
"""Tests for NotificationEntry."""
|
||||||
|
|
||||||
fixtures = ['users']
|
fixtures = ['users']
|
||||||
|
roles = ['admin.view']
|
||||||
|
|
||||||
def test_check_notification_entries(self):
|
def test_check_notification_entries(self):
|
||||||
"""Test that notification entries can be created."""
|
"""Test that notification entries can be created."""
|
||||||
@@ -1162,6 +1165,72 @@ class NotificationTest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(NotificationMessage.objects.count(), 13)
|
self.assertEqual(NotificationMessage.objects.count(), 13)
|
||||||
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 3)
|
self.assertEqual(NotificationMessage.objects.filter(user=self.user).count(), 3)
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
"""Test that a simple notification can be created."""
|
||||||
|
trigger_notification(
|
||||||
|
Group.objects.get(name='Sales'),
|
||||||
|
user=self.user,
|
||||||
|
data={'message': 'This is a test notification'},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_with_group(self):
|
||||||
|
"""Test that a notification can be created with a group."""
|
||||||
|
grp = Group.objects.get(name='Sales')
|
||||||
|
trigger_notification(
|
||||||
|
grp,
|
||||||
|
user=self.user,
|
||||||
|
data={'message': 'This is a test notification with group'},
|
||||||
|
targets=[grp],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_wrong_target(self):
|
||||||
|
"""Test that a notification with an invalid target raises an error."""
|
||||||
|
with self.assertLogs() as cm:
|
||||||
|
trigger_notification(
|
||||||
|
Group.objects.get(name='Sales'),
|
||||||
|
user=self.user,
|
||||||
|
data={'message': 'This is a test notification'},
|
||||||
|
targets=['invalid_target'],
|
||||||
|
)
|
||||||
|
self.assertIn('Unknown target passed to t', str(cm[1]))
|
||||||
|
|
||||||
|
def test_wrong_obj(self):
|
||||||
|
"""Test that a object without a reference is raising an issue."""
|
||||||
|
|
||||||
|
class SampleObj:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(KeyError) as cm:
|
||||||
|
trigger_notification(
|
||||||
|
SampleObj(),
|
||||||
|
user=self.user,
|
||||||
|
data={'message': 'This is a test notification'},
|
||||||
|
)
|
||||||
|
self.assertIn('Could not resolve an object reference for', str(cm.exception))
|
||||||
|
|
||||||
|
# Without reference, it should not raise an error
|
||||||
|
trigger_notification(
|
||||||
|
Group.objects.get(name='Sales'),
|
||||||
|
user=self.user,
|
||||||
|
data={'message': 'This is a test notification'},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_recent(self):
|
||||||
|
"""Test that a notification is not created if it was already sent recently."""
|
||||||
|
grp = Group.objects.get(name='Sales')
|
||||||
|
trigger_notification( #
|
||||||
|
grp, category='core', context={'name': 'test'}, targets=[self.user]
|
||||||
|
)
|
||||||
|
self.assertEqual(NotificationMessage.objects.count(), 1)
|
||||||
|
|
||||||
|
# Should not create a new notification
|
||||||
|
with self.assertLogs(logger='inventree') as cm:
|
||||||
|
trigger_notification(
|
||||||
|
grp, category='core', context={'name': 'test'}, targets=[self.user]
|
||||||
|
)
|
||||||
|
self.assertEqual(NotificationMessage.objects.count(), 1)
|
||||||
|
self.assertIn('as recently been sent for', str(cm[1]))
|
||||||
|
|
||||||
|
|
||||||
class CommonTest(InvenTreeAPITestCase):
|
class CommonTest(InvenTreeAPITestCase):
|
||||||
"""Tests for the common config."""
|
"""Tests for the common config."""
|
||||||
|
@@ -32,6 +32,7 @@ class InvenTreeUINotifications(NotificationMixin, InvenTreePlugin):
|
|||||||
"""Create a UI notification entry for specified users."""
|
"""Create a UI notification entry for specified users."""
|
||||||
from common.models import NotificationMessage
|
from common.models import NotificationMessage
|
||||||
|
|
||||||
|
ctx = context if context else {}
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
if not users:
|
if not users:
|
||||||
@@ -45,8 +46,8 @@ class InvenTreeUINotifications(NotificationMixin, InvenTreePlugin):
|
|||||||
source_object=user,
|
source_object=user,
|
||||||
user=user,
|
user=user,
|
||||||
category=category,
|
category=category,
|
||||||
name=context['name'],
|
name=ctx.get('name'),
|
||||||
message=context['message'],
|
message=ctx.get('message'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -594,3 +594,55 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
|||||||
|
|
||||||
self.assertEqual(Y_MANDATORY_2, Y_MANDATORY + 1)
|
self.assertEqual(Y_MANDATORY_2, Y_MANDATORY + 1)
|
||||||
self.assertEqual(N_MANDATORY_2, N_MANDATORY - 1)
|
self.assertEqual(N_MANDATORY_2, N_MANDATORY - 1)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginFullAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||||
|
"""Tests the plugin API endpoints."""
|
||||||
|
|
||||||
|
superuser = True
|
||||||
|
|
||||||
|
@override_settings(PLUGIN_TESTING_SETUP=True)
|
||||||
|
def test_full_process(self):
|
||||||
|
"""Test the full plugin install/uninstall process via API."""
|
||||||
|
install_slug = 'inventree-brother-plugin'
|
||||||
|
slug = 'brother'
|
||||||
|
|
||||||
|
# Install a plugin
|
||||||
|
data = self.post(
|
||||||
|
reverse('api-plugin-install'),
|
||||||
|
{'confirm': True, 'packagename': install_slug},
|
||||||
|
expected_code=201,
|
||||||
|
max_query_time=30,
|
||||||
|
max_query_count=370,
|
||||||
|
).data
|
||||||
|
self.assertEqual(data['success'], 'Installed plugin successfully')
|
||||||
|
|
||||||
|
# Activate the plugin
|
||||||
|
data = self.patch(
|
||||||
|
reverse('api-plugin-detail-activate', kwargs={'plugin': slug}),
|
||||||
|
data={'active': True},
|
||||||
|
max_query_count=320,
|
||||||
|
).data
|
||||||
|
self.assertEqual(data['active'], True)
|
||||||
|
|
||||||
|
# Check if the plugin is installed
|
||||||
|
test_plg = PluginConfig.objects.get(key=slug)
|
||||||
|
self.assertIsNotNone(test_plg, 'Test plugin not found')
|
||||||
|
self.assertTrue(test_plg.is_active())
|
||||||
|
|
||||||
|
# De-activate and uninstall the plugin
|
||||||
|
data = self.patch(
|
||||||
|
reverse('api-plugin-detail-activate', kwargs={'plugin': slug}),
|
||||||
|
data={'active': False},
|
||||||
|
max_query_count=380,
|
||||||
|
).data
|
||||||
|
self.assertEqual(data['active'], False)
|
||||||
|
response = self.patch(
|
||||||
|
reverse('api-plugin-uninstall', kwargs={'plugin': slug}),
|
||||||
|
max_query_count=305,
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Successful uninstallation
|
||||||
|
with self.assertRaises(PluginConfig.DoesNotExist):
|
||||||
|
PluginConfig.objects.get(key=slug)
|
||||||
|
Reference in New Issue
Block a user