Backport test case improvements
Add random primary key mixin. Split test case code into mixins. Make the view test case and the API test cases part of the same class hierachy. Update tests that failed due to the new import locations. Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
@@ -2,19 +2,21 @@ from __future__ import unicode_literals
|
||||
|
||||
import glob
|
||||
import os
|
||||
import random
|
||||
|
||||
from furl import furl
|
||||
|
||||
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 django.db import models
|
||||
from django.db.models.signals import post_save, pre_save
|
||||
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 acls.models import AccessControlList
|
||||
from permissions.models import Role
|
||||
from permissions.tests.literals import TEST_ROLE_LABEL
|
||||
from user_management.tests import (
|
||||
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL,
|
||||
TEST_GROUP_NAME, TEST_USER_EMAIL, TEST_USER_USERNAME, TEST_USER_PASSWORD
|
||||
)
|
||||
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
|
||||
|
||||
from ..settings import setting_temporary_directory
|
||||
|
||||
@@ -22,24 +24,75 @@ if getattr(settings, 'COMMON_TEST_FILE_HANDLES', False):
|
||||
import psutil
|
||||
|
||||
|
||||
class ContentTypeCheckMixin(object):
|
||||
class ClientMethodsTestCaseMixin(object):
|
||||
def _build_verb_kwargs(self, viewname=None, path=None, *args, **kwargs):
|
||||
data = kwargs.pop('data', {})
|
||||
follow = kwargs.pop('follow', False)
|
||||
query = kwargs.pop('query', {})
|
||||
|
||||
if viewname:
|
||||
path = reverse(viewname=viewname, *args, **kwargs)
|
||||
|
||||
path = furl(url=path)
|
||||
path.args.update(query)
|
||||
|
||||
return {'follow': follow, 'data': data, 'path': path.tostr()}
|
||||
|
||||
def delete(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.delete(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def get(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.get(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def patch(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.patch(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def post(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.post(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def put(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.put(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ContentTypeCheckTestCaseMixin(object):
|
||||
expected_content_type = 'text/html; charset=utf-8'
|
||||
|
||||
def _pre_setup(self):
|
||||
super(ContentTypeCheckMixin, self)._pre_setup()
|
||||
super(ContentTypeCheckTestCaseMixin, self)._pre_setup()
|
||||
test_instance = self
|
||||
|
||||
class CustomClient(self.client_class):
|
||||
def request(self, *args, **kwargs):
|
||||
response = super(CustomClient, self).request(*args, **kwargs)
|
||||
|
||||
content_type = response._headers['content-type'][1]
|
||||
test_instance.assertEqual(
|
||||
content_type, test_instance.expected_content_type,
|
||||
msg='Unexpected response content type: {}, expected: {}.'.format(
|
||||
content_type, test_instance.expected_content_type
|
||||
content_type = response._headers.get('content-type', [None, ''])[1]
|
||||
if test_instance.expected_content_type:
|
||||
test_instance.assertEqual(
|
||||
content_type, test_instance.expected_content_type,
|
||||
msg='Unexpected response content type: {}, expected: {}.'.format(
|
||||
content_type, test_instance.expected_content_type
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -53,7 +106,7 @@ class DatabaseConversionMixin(object):
|
||||
)
|
||||
|
||||
|
||||
class OpenFileCheckMixin(object):
|
||||
class OpenFileCheckTestCaseMixin(object):
|
||||
def _get_descriptor_count(self):
|
||||
process = psutil.Process()
|
||||
return process.num_fds()
|
||||
@@ -63,7 +116,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()
|
||||
|
||||
@@ -78,10 +131,84 @@ class OpenFileCheckMixin(object):
|
||||
|
||||
self._skip_file_descriptor_test = False
|
||||
|
||||
super(OpenFileCheckMixin, self).tearDown()
|
||||
super(OpenFileCheckTestCaseMixin, self).tearDown()
|
||||
|
||||
|
||||
class TempfileCheckMixin(object):
|
||||
class RandomPrimaryKeyModelMonkeyPatchMixin(object):
|
||||
random_primary_key_random_floor = 100
|
||||
random_primary_key_random_ceiling = 10000
|
||||
random_primary_key_maximum_attempts = 100
|
||||
|
||||
@staticmethod
|
||||
def get_unique_primary_key(model):
|
||||
pk_list = model._meta.default_manager.values_list('pk', flat=True)
|
||||
|
||||
attempts = 0
|
||||
while True:
|
||||
primary_key = random.randint(
|
||||
RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_random_floor,
|
||||
RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_random_ceiling
|
||||
)
|
||||
|
||||
if primary_key not in pk_list:
|
||||
break
|
||||
|
||||
attempts = attempts + 1
|
||||
|
||||
if attempts > RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_maximum_attempts:
|
||||
raise Exception(
|
||||
'Maximum number of retries for an unique random primary '
|
||||
'key reached.'
|
||||
)
|
||||
|
||||
return primary_key
|
||||
|
||||
def setUp(self):
|
||||
self.method_save_original = models.Model.save
|
||||
|
||||
def method_save_new(instance, *args, **kwargs):
|
||||
if instance.pk:
|
||||
return self.method_save_original(instance, *args, **kwargs)
|
||||
else:
|
||||
# Set meta.auto_created to True to have the original save_base
|
||||
# not send the pre_save signal which would normally send
|
||||
# the instance without a primary key. Since we assign a random
|
||||
# primary key any pre_save signal handler that relies on an
|
||||
# empty primary key will fail.
|
||||
# The meta.auto_created and manual pre_save sending emulates
|
||||
# the original behavior. Since meta.auto_created also disables
|
||||
# the post_save signal we must also send it ourselves.
|
||||
# This hack work with Django 1.11 .save_base() but can break
|
||||
# in future versions if that method is updated.
|
||||
pre_save.send(
|
||||
sender=instance.__class__, instance=instance, raw=False,
|
||||
update_fields=None,
|
||||
)
|
||||
instance._meta.auto_created = True
|
||||
instance.pk = RandomPrimaryKeyModelMonkeyPatchMixin.get_unique_primary_key(
|
||||
model=instance._meta.model
|
||||
)
|
||||
instance.id = instance.pk
|
||||
|
||||
result = instance.save_base(force_insert=True)
|
||||
instance._meta.auto_created = False
|
||||
|
||||
post_save.send(
|
||||
sender=instance.__class__, instance=instance, created=True,
|
||||
update_fields=None, raw=False
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
setattr(models.Model, 'save', method_save_new)
|
||||
super(RandomPrimaryKeyModelMonkeyPatchMixin, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
models.Model.save = self.method_save_original
|
||||
super(RandomPrimaryKeyModelMonkeyPatchMixin, self).tearDown()
|
||||
|
||||
|
||||
class TempfileCheckTestCasekMixin(object):
|
||||
# Ignore the jvmstat instrumentation and GitLab's CI .config files
|
||||
# Ignore LibreOffice fontconfig cache dir
|
||||
ignore_globs = ('hsperfdata_*', '.config', '.cache')
|
||||
@@ -106,7 +233,7 @@ class TempfileCheckMixin(object):
|
||||
) - set(ignored_result)
|
||||
|
||||
def setUp(self):
|
||||
super(TempfileCheckMixin, self).setUp()
|
||||
super(TempfileCheckTestCasekMixin, self).setUp()
|
||||
if getattr(settings, 'COMMON_TEST_TEMP_FILES', False):
|
||||
self._temporary_items = self._get_temporary_entries()
|
||||
|
||||
@@ -121,33 +248,43 @@ class TempfileCheckMixin(object):
|
||||
','.join(final_temporary_items - self._temporary_items)
|
||||
)
|
||||
)
|
||||
super(TempfileCheckMixin, self).tearDown()
|
||||
super(TempfileCheckTestCasekMixin, self).tearDown()
|
||||
|
||||
|
||||
class UserMixin(object):
|
||||
def setUp(self):
|
||||
super(UserMixin, self).setUp()
|
||||
self.admin_user = get_user_model().objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
class TestViewTestCaseMixin(object):
|
||||
has_test_view = False
|
||||
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username=TEST_USER_USERNAME, email=TEST_USER_EMAIL,
|
||||
password=TEST_USER_PASSWORD
|
||||
)
|
||||
def tearDown(self):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
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)
|
||||
self.client.logout()
|
||||
if self.has_test_view:
|
||||
urlpatterns.pop(0)
|
||||
super(TestViewTestCaseMixin, self).tearDown()
|
||||
|
||||
def grant_access(self, permission, obj):
|
||||
AccessControlList.objects.grant(
|
||||
permission=permission, role=self.role, obj=obj
|
||||
)
|
||||
def add_test_view(self, test_object):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
def grant_permission(self, permission):
|
||||
self.role.permissions.add(
|
||||
permission.stored_permission
|
||||
)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user