Skip to content

Commit

Permalink
[tests] Fixed tests that polluted the test environment
Browse files Browse the repository at this point in the history
Mock NOTIFICATION_TYPE and NOTIFICATION_CHOICES to avoid polluting
the test environment from notification types registered in the test.
  • Loading branch information
pandafy committed May 14, 2024
1 parent fea1fa3 commit 8aec4de
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 75 deletions.
10 changes: 5 additions & 5 deletions openwisp_notifications/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from django.db.utils import OperationalError
from django.utils import timezone

from openwisp_notifications import types
from openwisp_notifications.swapper import load_model, swapper_load_model
from openwisp_notifications.types import NOTIFICATION_TYPES
from openwisp_utils.tasks import OpenwispCeleryTask

User = get_user_model()
Expand Down Expand Up @@ -105,7 +105,7 @@ def update_superuser_notification_settings(instance_id, is_superuser, is_created
create_notification_settings(
user=user,
organizations=Organization.objects.all(),
notification_types=NOTIFICATION_TYPES.keys(),
notification_types=types.NOTIFICATION_TYPES.keys(),
)


Expand All @@ -119,7 +119,7 @@ def ns_register_unregister_notification_type(
"""

notification_types = (
[notification_type] if notification_type else NOTIFICATION_TYPES.keys()
[notification_type] if notification_type else types.NOTIFICATION_TYPES.keys()
)

organizations = Organization.objects.all()
Expand Down Expand Up @@ -166,7 +166,7 @@ def update_org_user_notificationsetting(org_user_id, user_id, org_id, is_org_adm
create_notification_settings(
user=user,
organizations=[organization],
notification_types=NOTIFICATION_TYPES.keys(),
notification_types=types.NOTIFICATION_TYPES.keys(),
)


Expand All @@ -192,7 +192,7 @@ def ns_organization_created(instance_id):
create_notification_settings(
user=user,
organizations=[organization],
notification_types=NOTIFICATION_TYPES.keys(),
notification_types=types.NOTIFICATION_TYPES.keys(),
)


Expand Down
9 changes: 4 additions & 5 deletions openwisp_notifications/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from openwisp_notifications.swapper import load_model, swapper_load_model
from openwisp_notifications.tests.test_helpers import (
TEST_DATETIME,
mock_notification_types,
register_notification_type,
unregister_notification_type,
)
from openwisp_users.tests.test_api import AuthenticationMixin
from openwisp_users.tests.utils import TestOrganizationMixin
Expand Down Expand Up @@ -436,6 +436,7 @@ def test_list_view_notification_cache(self):
self.assertEqual(response.data['count'], number_of_notifications)

@capture_any_output()
@mock_notification_types
def test_malformed_notifications(self):
test_type = {
'verbose_name': 'Test Notification Type',
Expand Down Expand Up @@ -469,9 +470,8 @@ def test_malformed_notifications(self):
self.assertEqual(response.status_code, 404)
self.assertDictEqual(response.data, {'detail': NOT_FOUND_ERROR})

unregister_notification_type('test_type')

@capture_any_output()
@mock_notification_types
@patch('openwisp_notifications.tasks.delete_obsolete_objects.delay')
def test_obsolete_notifications_busy_worker(self, mocked_task):
"""
Expand Down Expand Up @@ -516,8 +516,6 @@ def test_obsolete_notifications_busy_worker(self, mocked_task):
self.assertEqual(response.status_code, 404)
self.assertDictEqual(response.data, {'detail': NOT_FOUND_ERROR})

unregister_notification_type('test_type')

def test_notification_setting_list_api(self):
self._create_org_user(is_admin=True)
number_of_settings = NotificationSetting.objects.filter(user=self.admin).count()
Expand Down Expand Up @@ -904,6 +902,7 @@ def test_list_ignore_obj_notification_api(self, mocked_task):
self.assertIn('object_content_type', ignore_obj_notification)
self.assertIsNone(ignore_obj_notification['valid_till'])

@capture_any_output()
@patch('openwisp_notifications.tasks.delete_notification.delay')
def test_deleted_notification_type(self, *args):
notify.send(sender=self.admin, type='default', target=self.admin)
Expand Down
40 changes: 40 additions & 0 deletions openwisp_notifications/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
from copy import deepcopy
from datetime import datetime
from unittest.mock import patch

from django.contrib.messages.storage.fallback import FallbackStorage
from django.http import HttpRequest
from django.utils import timezone

from openwisp_notifications.swapper import load_model
from openwisp_notifications.tasks import ns_register_unregister_notification_type
from openwisp_notifications.types import NOTIFICATION_CHOICES, NOTIFICATION_TYPES
from openwisp_notifications.types import (
register_notification_type as base_register_notification_type,
)
from openwisp_notifications.types import (
unregister_notification_type as base_unregister_notification_type,
)

Notification = load_model('Notification')
NotificationSetting = load_model('NotificationSetting')

TEST_DATETIME = datetime(2020, 5, 4, 0, 0, 0, 0, timezone.get_default_timezone())
Expand Down Expand Up @@ -47,3 +51,39 @@ def unregister_notification_type(type_name):
def notification_related_object_url(obj, field, *args, **kwargs):
related_obj = getattr(obj, field)
return f'https://{related_obj}.example.com'


def mock_notification_types(func):
"""
This decorator mocks the NOTIFICATION_CHOICES and NOTIFICATION_TYPES
to prevent polluting the test environment with any notification types
registered during the test.
"""

def wrapper(*args, **kwargs):
with patch.multiple(
'openwisp_notifications.types',
NOTIFICATION_CHOICES=deepcopy(NOTIFICATION_CHOICES),
NOTIFICATION_TYPES=deepcopy(NOTIFICATION_TYPES),
):
from openwisp_notifications import types

# Here we mock the choices for model fields directly. This is
# because the choices for model fields are defined during the
# model loading phase, and any changes to the mocked
# NOTIFICATION_CHOICES don't affect the model field choices.
# So, we mock the choices directly here to ensure that any
# changes to the mocked NOTIFICATION_CHOICES reflect in the
# model field choices.
with patch.object(
Notification._meta.get_field('type'),
'choices',
types.NOTIFICATION_CHOICES,
), patch.object(
NotificationSetting._meta.get_field('type'),
'choices',
types.NOTIFICATION_CHOICES,
):
return func(*args, **kwargs)

return wrapper
26 changes: 12 additions & 14 deletions openwisp_notifications/tests/test_ignore_object_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from openwisp_notifications.swapper import load_model
from openwisp_users.tests.utils import TestOrganizationMixin

from ..types import NOTIFICATION_TYPES
from .test_helpers import register_notification_type
from ..types import get_notification_configuration
from .test_helpers import mock_notification_types, register_notification_type

IgnoreObjectNotification = load_model('IgnoreObjectNotification')
Notification = load_model('Notification')
Expand Down Expand Up @@ -46,20 +46,18 @@ def test_notification_for_disabled_object(self, mocked_task):
notify.send(sender=self.admin, type='default', target=self.obj)
self.assertEqual(Notification.objects.count(), 0)

@mock_notification_types
@patch('openwisp_notifications.tasks.delete_ignore_object_notification.apply_async')
def test_related_object_deleted(self, *args):
type_config = NOTIFICATION_TYPES['default']
with patch('openwisp_notifications.types.NOTIFICATION_TYPES', {}):
register_notification_type(
'test', type_config, models=[self.obj._meta.model]
)
IgnoreObjectNotification.objects.create(
object=self.obj,
user=self.admin,
valid_till=(timezone.now() + timezone.timedelta(days=1)),
)
self.obj.delete()
self.assertEqual(on_queryset.count(), 0)
type_config = get_notification_configuration('default')
register_notification_type('test', type_config, models=[self.obj._meta.model])
IgnoreObjectNotification.objects.create(
object=self.obj,
user=self.admin,
valid_till=(timezone.now() + timezone.timedelta(days=1)),
)
self.obj.delete()
self.assertEqual(on_queryset.count(), 0)

@patch.object(
IgnoreObjectNotification.objects, 'filter', side_effect=OperationalError
Expand Down
41 changes: 16 additions & 25 deletions openwisp_notifications/tests/test_notification_setting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from django.core.exceptions import ImproperlyConfigured
from django.db.models.signals import post_save
from django.test import TransactionTestCase

Expand All @@ -9,10 +8,9 @@
from openwisp_notifications.tests.test_helpers import (
base_register_notification_type,
base_unregister_notification_type,
mock_notification_types,
register_notification_type,
unregister_notification_type,
)
from openwisp_notifications.types import get_notification_configuration
from openwisp_users.tests.utils import TestOrganizationMixin

test_notification_type = {
Expand All @@ -32,15 +30,7 @@

class TestNotificationSetting(TestOrganizationMixin, TransactionTestCase):
def setUp(self):
if not Organization.objects.first():
self._create_org(name='default', slug='default')

def tearDown(self):
super().tearDown()
try:
unregister_notification_type('test')
except ImproperlyConfigured:
pass
self.default_org = self._get_org('default')

def _create_staff_org_admin(self):
return self._create_org_user(user=self._create_operator(), is_admin=True)
Expand All @@ -56,6 +46,7 @@ def test_user_created(self):
self._get_user()
self.assertEqual(ns_queryset.count(), 0)

@mock_notification_types
def test_notification_type_registered(self):
register_notification_type('test', test_notification_type)
queryset = NotificationSetting.objects.filter(type='test')
Expand Down Expand Up @@ -95,6 +86,7 @@ def test_organization_user(self):
self.assertEqual(ns_queryset.filter(deleted=False).count(), 1)
self.assertEqual(ns_queryset.filter(deleted=True).count(), 1)

@mock_notification_types
def test_register_notification_org_user(self):
self._create_staff_org_admin()

Expand All @@ -103,13 +95,15 @@ def test_register_notification_org_user(self):
register_notification_type('test', test_notification_type)
self.assertEqual(queryset.count(), 1)

@mock_notification_types
def test_post_migration_handler(self):
from openwisp_notifications.types import NOTIFICATION_CHOICES

# Simulates loading of app when Django server starts
admin = self._get_admin()
org_user = self._create_staff_org_admin()
self.assertEqual(ns_queryset.count(), 3)

default_type_config = get_notification_configuration('default')
base_unregister_notification_type('default')
base_register_notification_type('test', test_notification_type)
notification_type_registered_unregistered_handler(sender=self)
Expand All @@ -119,16 +113,14 @@ def test_post_migration_handler(self):

# Notification Settings for "test" type are created
queryset = NotificationSetting.objects.filter(deleted=False)
if NotificationSetting._meta.app_label == 'sample_notifications':
self.assertEqual(queryset.count(), 6)
self.assertEqual(queryset.filter(user=admin).count(), 4)
self.assertEqual(queryset.filter(user=org_user.user).count(), 2)
else:
self.assertEqual(queryset.count(), 3)
self.assertEqual(queryset.filter(user=admin).count(), 2)
self.assertEqual(queryset.filter(user=org_user.user).count(), 1)

base_register_notification_type('default', default_type_config)
notification_types_count = len(NOTIFICATION_CHOICES)
self.assertEqual(queryset.count(), 3 * notification_types_count)
self.assertEqual(
queryset.filter(user=admin).count(), 2 * notification_types_count
)
self.assertEqual(
queryset.filter(user=org_user.user).count(), 1 * notification_types_count
)

def test_superuser_demoted_to_user(self):
admin = self._get_admin()
Expand Down Expand Up @@ -187,6 +179,7 @@ def test_multiple_org_membership(self):
OrganizationUser.objects.create(user=user, organization=test_org, is_admin=True)
self.assertEqual(ns_queryset.count(), 2)

@mock_notification_types
def test_notification_setting_full_clean(self):
test_type = {
'verbose_name': 'Test Notification Type',
Expand All @@ -207,8 +200,6 @@ def test_notification_setting_full_clean(self):
self.assertIsNone(notification_setting.email)
self.assertIsNone(notification_setting.web)

unregister_notification_type('test_type')

def test_organization_user_updated(self):
default_org = Organization.objects.first()
org_user = self._create_staff_org_admin()
Expand Down
Loading

0 comments on commit 8aec4de

Please sign in to comment.