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:
Roberto Rosario
2019-04-02 02:31:35 -04:00
parent 8cbae9021b
commit 586d41eeff
13 changed files with 310 additions and 266 deletions

View File

@@ -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)