diff --git a/mayan/apps/acls/tests/mixins.py b/mayan/apps/acls/tests/mixins.py index 353d734b03..7c32cb7287 100644 --- a/mayan/apps/acls/tests/mixins.py +++ b/mayan/apps/acls/tests/mixins.py @@ -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 ) diff --git a/mayan/apps/common/tests/base.py b/mayan/apps/common/tests/base.py index 81c97bed83..82f9f19b21 100644 --- a/mayan/apps/common/tests/base.py +++ b/mayan/apps/common/tests/base.py @@ -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. + """ diff --git a/mayan/apps/common/tests/mixins.py b/mayan/apps/common/tests/mixins.py index f44bd7f4e9..621353bd68 100644 --- a/mayan/apps/common/tests/mixins.py +++ b/mayan/apps/common/tests/mixins.py @@ -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) diff --git a/mayan/apps/permissions/tests/literals.py b/mayan/apps/permissions/tests/literals.py index 52d75bdea2..42815f1718 100644 --- a/mayan/apps/permissions/tests/literals.py +++ b/mayan/apps/permissions/tests/literals.py @@ -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' diff --git a/mayan/apps/permissions/tests/mixins.py b/mayan/apps/permissions/tests/mixins.py new file mode 100644 index 0000000000..212fb554c4 --- /dev/null +++ b/mayan/apps/permissions/tests/mixins.py @@ -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) diff --git a/mayan/apps/rest_api/permissions.py b/mayan/apps/rest_api/permissions.py index 88c37cd746..476b551a21 100644 --- a/mayan/apps/rest_api/permissions.py +++ b/mayan/apps/rest_api/permissions.py @@ -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: diff --git a/mayan/apps/rest_api/tests/base.py b/mayan/apps/rest_api/tests/base.py index f088f2c8c9..1a7de24fd1 100644 --- a/mayan/apps/rest_api/tests/base.py +++ b/mayan/apps/rest_api/tests/base.py @@ -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 - ) diff --git a/mayan/apps/user_management/tests/literals.py b/mayan/apps/user_management/tests/literals.py index 17577445a3..0cc6ddadc8 100644 --- a/mayan/apps/user_management/tests/literals.py +++ b/mayan/apps/user_management/tests/literals.py @@ -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' diff --git a/mayan/apps/user_management/tests/mixins.py b/mayan/apps/user_management/tests/mixins.py index 77ff3726c9..81643866ef 100644 --- a/mayan/apps/user_management/tests/mixins.py +++ b/mayan/apps/user_management/tests/mixins.py @@ -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} + )