Reorganize reusable test code

Extract test views and user code into their own separate test case
mixins. Append TestCase to test case mixins with base test code
to differentiate them from test mixins with reusable view calls.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-01-03 14:04:16 -04:00
parent c6aab93f98
commit 65ccbd3b7b
9 changed files with 268 additions and 232 deletions

View File

@@ -1,47 +1,27 @@
from __future__ import unicode_literals
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import ImproperlyConfigured
from mayan.apps.permissions.models import Role
from mayan.apps.permissions.tests.literals import TEST_ROLE_LABEL
from mayan.apps.user_management.tests import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME,
TEST_GROUP_NAME, TEST_USER_EMAIL, TEST_USER_PASSWORD, TEST_USER_USERNAME
)
from mayan.apps.permissions.tests.mixins import RoleTestCaseMixin
from mayan.apps.user_management.tests.mixins import UserTestCaseMixin
from ..models import AccessControlList
class ACLBaseTestMixin(object):
auto_create_group = True
auto_create_users = True
class ACLTestCaseMixin(RoleTestCaseMixin, UserTestCaseMixin):
def setUp(self):
super(ACLBaseTestMixin, self).setUp()
if self.auto_create_users:
self.admin_user = get_user_model().objects.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.user = get_user_model().objects.create_user(
username=TEST_USER_USERNAME, email=TEST_USER_EMAIL,
password=TEST_USER_PASSWORD
)
if self.auto_create_group:
self.group = Group.objects.create(name=TEST_GROUP_NAME)
self.role = Role.objects.create(label=TEST_ROLE_LABEL)
self.group.user_set.add(self.user)
self.role.groups.add(self.group)
super(ACLTestCaseMixin, self).setUp()
if hasattr(self, '_test_case_user'):
self._test_case_role.groups.add(self._test_case_group)
def grant_access(self, obj, permission):
return AccessControlList.objects.grant(
obj=obj, permission=permission, role=self.role
)
if not hasattr(self, '_test_case_role'):
raise ImproperlyConfigured(
'Enable the creation of the test case user, group, and role '
'in order to enable the usage of ACLs in tests.'
)
def grant_permission(self, permission):
self.role.permissions.add(
permission.stored_permission
return AccessControlList.objects.grant(
obj=obj, permission=permission, role=self._test_case_role
)

View File

@@ -1,30 +1,21 @@
from __future__ import absolute_import, unicode_literals
from django.conf.urls import url
from django.contrib.auth import get_user_model
from django.http import HttpResponse
from django.template import Context, Template
from django.test import TestCase
from django.test.utils import ContextList
from django.urls import clear_url_caches, reverse
from django.urls import reverse
from django_downloadview import assert_download_response
from mayan.apps.acls.tests.mixins import ACLBaseTestMixin
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
from mayan.apps.permissions.classes import Permission
from mayan.apps.smart_settings.classes import Namespace
from mayan.apps.user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_USER_PASSWORD,
TEST_USER_USERNAME
)
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
from .mixins import (
ContentTypeCheckMixin, DatabaseConversionMixin, OpenFileCheckMixin,
TempfileCheckMixin
ClientMethodsTestCaseMixin, ContentTypeCheckMixin, DatabaseConversionMixin,
OpenFileCheckTestCaseMixin, TempfileCheckTestCaseMixin,
TestViewTestCaseMixin
)
class BaseTestCase(DatabaseConversionMixin, ACLBaseTestMixin, ContentTypeCheckMixin, OpenFileCheckMixin, TempfileCheckMixin, TestCase):
class BaseTestCase(DatabaseConversionMixin, ACLTestCaseMixin, ContentTypeCheckMixin, OpenFileCheckTestCaseMixin, TempfileCheckTestCaseMixin, TestCase):
"""
This is the most basic test case class any test in the project should use.
"""
@@ -36,76 +27,9 @@ class BaseTestCase(DatabaseConversionMixin, ACLBaseTestMixin, ContentTypeCheckMi
Permission.invalidate_cache()
class GenericViewTestCase(BaseTestCase):
has_test_view = False
def tearDown(self):
from mayan.urls import urlpatterns
self.client.logout()
if self.has_test_view:
urlpatterns.pop(0)
super(GenericViewTestCase, self).tearDown()
def add_test_view(self, test_object):
from mayan.urls import urlpatterns
def test_view(request):
template = Template('{{ object }}')
context = Context(
{'object': test_object, 'resolved_object': test_object}
)
return HttpResponse(template.render(context=context))
urlpatterns.insert(0, url(TEST_VIEW_URL, test_view, name=TEST_VIEW_NAME))
clear_url_caches()
self.has_test_view = True
def get_test_view(self):
response = self.get(TEST_VIEW_NAME)
if isinstance(response.context, ContextList):
# template widget rendering causes test client response to be
# ContextList rather than RequestContext. Typecast to dictionary
# before updating.
result = dict(response.context).copy()
result.update({'request': response.wsgi_request})
return Context(result)
else:
response.context.update({'request': response.wsgi_request})
return Context(response.context)
def get(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.get(
path=path, data=data, follow=follow
)
def login(self, *args, **kwargs):
logged_in = self.client.login(*args, **kwargs)
return logged_in
def login_user(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
def login_admin_user(self):
self.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
def logout(self):
self.client.logout()
def post(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.post(
path=path, data=data, follow=follow
)
class GenericViewTestCase(ClientMethodsTestCaseMixin, TestViewTestCaseMixin, BaseTestCase):
"""
A generic view test case built on top of the base test case providing
single user test view to test object resolution and shorthand HTTP
method functions.
"""

View File

@@ -4,18 +4,16 @@ import glob
import os
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.conf.urls import url
from django.core import management
from mayan.apps.user_management.tests import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME,
TEST_GROUP_NAME, TEST_USER_EMAIL, TEST_USER_PASSWORD,
TEST_USER_USERNAME
)
from django.http import HttpResponse
from django.template import Context, Template
from django.test.utils import ContextList
from django.urls import clear_url_caches, reverse
from ..settings import setting_temporary_directory
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
from .utils import mute_stdout
@@ -23,6 +21,63 @@ if getattr(settings, 'COMMON_TEST_FILE_HANDLES', False):
import psutil
class ClientMethodsTestCaseMixin(object):
def delete(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.delete(
path=path, data=data, follow=follow
)
def get(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.get(
path=path, data=data, follow=follow
)
def patch(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.patch(
path=path, data=data, follow=follow
)
def post(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.post(
path=path, data=data, follow=follow
)
def put(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.put(
path=path, data=data, follow=follow
)
class ContentTypeCheckMixin(object):
expected_content_type = 'text/html; charset=utf-8'
@@ -55,7 +110,7 @@ class DatabaseConversionMixin(object):
)
class OpenFileCheckMixin(object):
class OpenFileCheckTestCaseMixin(object):
def _get_descriptor_count(self):
process = psutil.Process()
return process.num_fds()
@@ -65,7 +120,7 @@ class OpenFileCheckMixin(object):
return process.open_files()
def setUp(self):
super(OpenFileCheckMixin, self).setUp()
super(OpenFileCheckTestCaseMixin, self).setUp()
if getattr(settings, 'COMMON_TEST_FILE_HANDLES', False):
self._open_files = self._get_open_files()
@@ -80,10 +135,10 @@ class OpenFileCheckMixin(object):
self._skip_file_descriptor_test = False
super(OpenFileCheckMixin, self).tearDown()
super(OpenFileCheckTestCaseMixin, self).tearDown()
class TempfileCheckMixin(object):
class TempfileCheckTestCaseMixin(object):
# Ignore the jvmstat instrumentation and GitLab's CI .config files
# Ignore LibreOffice fontconfig cache dir
ignore_globs = ('hsperfdata_*', '.config', '.cache')
@@ -108,7 +163,7 @@ class TempfileCheckMixin(object):
) - set(ignored_result)
def setUp(self):
super(TempfileCheckMixin, self).setUp()
super(TempfileCheckTestCaseMixin, self).setUp()
if getattr(settings, 'COMMON_TEST_TEMP_FILES', False):
self._temporary_items = self._get_temporary_entries()
@@ -123,4 +178,43 @@ class TempfileCheckMixin(object):
','.join(final_temporary_items - self._temporary_items)
)
)
super(TempfileCheckMixin, self).tearDown()
super(TempfileCheckTestCaseMixin, self).tearDown()
class TestViewTestCaseMixin(object):
has_test_view = False
def tearDown(self):
from mayan.urls import urlpatterns
self.client.logout()
if self.has_test_view:
urlpatterns.pop(0)
super(TestViewTestCaseMixin, self).tearDown()
def add_test_view(self, test_object):
from mayan.urls import urlpatterns
def test_view(request):
template = Template('{{ object }}')
context = Context(
{'object': test_object, 'resolved_object': test_object}
)
return HttpResponse(template.render(context=context))
urlpatterns.insert(0, url(TEST_VIEW_URL, test_view, name=TEST_VIEW_NAME))
clear_url_caches()
self.has_test_view = True
def get_test_view(self):
response = self.get(TEST_VIEW_NAME)
if isinstance(response.context, ContextList):
# template widget rendering causes test client response to be
# ContextList rather than RequestContext. Typecast to dictionary
# before updating.
result = dict(response.context).copy()
result.update({'request': response.wsgi_request})
return Context(result)
else:
response.context.update({'request': response.wsgi_request})
return Context(response.context)

View File

@@ -1,5 +1,5 @@
from __future__ import unicode_literals
TEST_ROLE_2_LABEL = 'test role 2'
TEST_ROLE_LABEL = 'test role'
TEST_CASE_ROLE_LABEL = 'test case role'
TEST_ROLE_LABEL = 'test role 2'
TEST_ROLE_LABEL_EDITED = 'test role label edited'

View File

@@ -0,0 +1,23 @@
from __future__ import unicode_literals
from ..models import Role
from .literals import TEST_CASE_ROLE_LABEL, TEST_ROLE_LABEL
class RoleTestCaseMixin(object):
def setUp(self):
super(RoleTestCaseMixin, self).setUp()
if hasattr(self, '_test_case_group'):
self.create_role()
def create_role(self):
self._test_case_role = Role.objects.create(label=TEST_CASE_ROLE_LABEL)
def grant_permission(self, permission):
self._test_case_role.grant(permission=permission)
class RoleTestMixin(object):
def _create_test_role(self):
self.test_role = Role.objects.create(label=TEST_ROLE_LABEL)

View File

@@ -3,6 +3,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals
from django.core.exceptions import PermissionDenied
from django.http import Http404
from rest_framework.permissions import BasePermission
@@ -33,6 +34,10 @@ class MayanPermission(BasePermission):
view, 'mayan_object_permissions', {}
).get(request.method, None)
object_permissions_raise_404 = getattr(
view, 'mayan_object_permissions_raise_404', ()
)
if required_permission:
try:
if hasattr(view, 'mayan_permission_attribute_check'):
@@ -47,7 +52,10 @@ class MayanPermission(BasePermission):
obj=obj
)
except PermissionDenied:
return False
if request.method in object_permissions_raise_404:
raise Http404
else:
return False
else:
return True
else:

View File

@@ -1,20 +1,15 @@
from __future__ import absolute_import, unicode_literals
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APITestCase
from mayan.apps.acls.tests.mixins import ACLBaseTestMixin
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
from mayan.apps.common.tests.mixins import ClientMethodsTestCaseMixin
from mayan.apps.permissions.classes import Permission
from mayan.apps.smart_settings.classes import Namespace
from mayan.apps.user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_USER_USERNAME,
TEST_USER_PASSWORD
)
from mayan.apps.user_management.tests.mixins import UserTestCaseMixin
class BaseAPITestCase(ACLBaseTestMixin, APITestCase):
class BaseAPITestCase(ClientMethodsTestCaseMixin, ACLTestCaseMixin, UserTestCaseMixin, APITestCase):
"""
API test case class that invalidates permissions and smart settings
"""
@@ -24,78 +19,4 @@ class BaseAPITestCase(ACLBaseTestMixin, APITestCase):
Permission.invalidate_cache()
def tearDown(self):
self.client.logout()
super(BaseAPITestCase, self).tearDown()
def delete(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.delete(
path=path, data=data, follow=follow
)
def get(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.get(
path=path, data=data, follow=follow
)
def login(self, username, password):
logged_in = self.client.login(username=username, password=password)
user = get_user_model().objects.get(username=username)
self.assertTrue(logged_in)
self.assertTrue(user.is_authenticated)
return user.is_authenticated
def login_user(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
def login_admin_user(self):
self.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
def logout(self):
self.client.logout()
def patch(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.patch(
path=path, data=data, follow=follow
)
def post(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.post(
path=path, data=data, follow=follow
)
def put(self, viewname=None, path=None, *args, **kwargs):
data = kwargs.pop('data', {})
follow = kwargs.pop('follow', False)
if viewname:
path = reverse(viewname=viewname, *args, **kwargs)
return self.client.put(
path=path, data=data, follow=follow
)

View File

@@ -1,18 +1,22 @@
from __future__ import unicode_literals
__all__ = (
'TEST_ADMIN_EMAIL', 'TEST_ADMIN_PASSWORD', 'TEST_ADMIN_USERNAME',
'TEST_GROUP_NAME', 'TEST_GROUP_NAME_EDITED', 'TEST_USER_EMAIL',
'TEST_USER_PASSWORD', 'TEST_USER_PASSWORD_EDITED', 'TEST_USER_USERNAME'
)
TEST_ADMIN_EMAIL = 'admin@example.com'
TEST_ADMIN_PASSWORD = 'test admin password'
TEST_ADMIN_USERNAME = 'test_admin'
TEST_CASE_ADMIN_EMAIL = 'admin@example.com'
TEST_CASE_ADMIN_PASSWORD = 'test admin password'
TEST_CASE_ADMIN_USERNAME = 'test_admin'
TEST_CASE_GROUP_NAME = 'test group'
TEST_CASE_USER_EMAIL = 'user@example.com'
TEST_CASE_USER_PASSWORD = 'test user password'
TEST_CASE_USER_USERNAME = 'test_user'
TEST_GROUP_NAME = 'test group'
TEST_GROUP_2_NAME = 'test group 2'
TEST_GROUP_NAME_EDITED = 'test group edited'
TEST_GROUP_2_NAME = 'test group 2'
TEST_GROUP_2_NAME_EDITED = 'test group 2 edited'
TEST_USER_EMAIL = 'user@example.com'
TEST_USER_PASSWORD = 'test user password'

View File

@@ -4,11 +4,60 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from .literals import (
TEST_GROUP_2_NAME, TEST_GROUP_2_NAME_EDITED, TEST_USER_2_EMAIL,
TEST_USER_2_PASSWORD, TEST_USER_2_USERNAME, TEST_USER_2_USERNAME_EDITED
TEST_CASE_ADMIN_EMAIL, TEST_CASE_ADMIN_PASSWORD, TEST_CASE_ADMIN_USERNAME,
TEST_CASE_GROUP_NAME, TEST_CASE_USER_EMAIL, TEST_CASE_USER_PASSWORD,
TEST_CASE_USER_USERNAME, TEST_GROUP_NAME, TEST_GROUP_2_NAME,
TEST_GROUP_2_NAME_EDITED, TEST_USER_2_EMAIL, TEST_USER_2_PASSWORD,
TEST_USER_EMAIL, TEST_USER_USERNAME, TEST_USER_PASSWORD,
TEST_USER_2_USERNAME, TEST_USER_2_USERNAME_EDITED
)
class UserTestCaseMixin(object):
auto_login_admin = False
auto_login_user = True
def setUp(self):
super(UserTestCaseMixin, self).setUp()
if self.auto_login_user:
self._test_case_user = get_user_model().objects.create_user(
username=TEST_CASE_USER_USERNAME, email=TEST_CASE_USER_EMAIL,
password=TEST_CASE_USER_PASSWORD
)
self.login_user()
self._test_case_group = Group.objects.create(name=TEST_GROUP_NAME)
self._test_case_group.user_set.add(self._test_case_user)
elif self.auto_login_admin:
self._test_case_admin_user = get_user_model().objects.create_superuser(
username=TEST_CASE_ADMIN_USERNAME, email=TEST_CASE_ADMIN_EMAIL,
password=TEST_CASE_ADMIN_PASSWORD
)
self.login_admin_user()
def tearDown(self):
self.client.logout()
super(UserTestCaseMixin, self).tearDown()
def login(self, *args, **kwargs):
logged_in = self.client.login(*args, **kwargs)
return logged_in
def login_user(self):
self.login(
username=TEST_CASE_USER_USERNAME, password=TEST_CASE_USER_PASSWORD
)
def login_admin_user(self):
self.login(
username=TEST_CASE_ADMIN_USERNAME,
password=TEST_CASE_ADMIN_PASSWORD
)
def logout(self):
self.client.logout()
class UserTestMixin(object):
def _create_test_group(self):
self.test_group = Group.objects.create(name=TEST_GROUP_2_NAME)
@@ -23,6 +72,8 @@ class UserTestMixin(object):
password=TEST_USER_2_PASSWORD
)
# Group views
def _request_test_group_create_view(self):
reponse = self.post(
viewname='user_management:group_create', data={
@@ -32,19 +83,38 @@ class UserTestMixin(object):
self.test_group = Group.objects.filter(name=TEST_GROUP_2_NAME).first()
return reponse
def _request_test_group_delete_view(self):
return self.post(
viewname='user_management:group_delete', kwargs={
'group_pk': self.test_group.pk
}
)
def _request_test_group_edit_view(self):
return self.post(
viewname='user_management:group_edit', kwargs={
'pk': self.test_group.pk
'group_pk': self.test_group.pk
}, data={
'name': TEST_GROUP_2_NAME_EDITED
}
)
def _request_test_group_list_view(self):
return self.get(viewname='user_management:group_list')
def _request_test_group_members_view(self):
return self.get(
viewname='user_management:group_members',
kwargs={'group_pk': self.test_group.pk}
)
# User views
def _request_test_user_create_view(self):
reponse = self.post(
viewname='user_management:user_create', data={
'username': TEST_USER_2_USERNAME
'username': TEST_USER_2_USERNAME,
'password': TEST_USER_2_PASSWORD
}
)
@@ -53,11 +123,23 @@ class UserTestMixin(object):
).first()
return reponse
def _request_test_user_delete_view(self):
return self.post(
viewname='user_management:user_delete',
kwargs={'user_pk': self.test_user.pk}
)
def _request_test_user_edit_view(self):
return self.post(
viewname='user_management:user_edit', kwargs={
'pk': self.test_user.pk
'user_pk': self.test_user.pk
}, data={
'username': TEST_USER_2_USERNAME_EDITED
}
)
def _request_test_user_groups_view(self):
return self.get(
viewname='user_management:user_groups',
kwargs={'user_pk': self.test_user.pk}
)