import sys
import threading
from contextlib import contextmanager
import pytest
import urllib.parse
from django.contrib.auth.models import AnonymousUser, User
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS, connections
from django.template.loaders.cached import Loader as CachedLoader
from django.test import TestCase as DjangoTestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
try:
from mock import patch
except ImportError:
from unittest.mock import patch
# Based on: https://github.com/revsys/django-test-plus/blob/master/test_plus/test.py#L30
[docs]class _AssertNumQueriesLessThanContext(CaptureQueriesContext):
def __init__(self, test_case, num, connection):
self.test_case = test_case
self.num = num
super(_AssertNumQueriesLessThanContext, self).__init__(connection)
[docs] def __exit__(self, exc_type, exc_value, traceback):
super(_AssertNumQueriesLessThanContext, self).__exit__(
exc_type, exc_value, traceback
)
if exc_type is not None:
return
executed = len(self)
self.test_case.assertTrue(
executed < self.num,
"%d queries executed, expected less than %d" % (executed, self.num),
)
[docs]class TestCase(DjangoTestCase):
# Based on: https://github.com/revsys/django-test-plus/blob/master/test_plus/test.py#L236
[docs] def assertNumQueriesLessThan(self, num, *args, **kwargs):
func = kwargs.pop('func', None)
using = kwargs.pop("using", DEFAULT_DB_ALIAS)
conn = connections[using]
context = _AssertNumQueriesLessThanContext(self, num, conn)
if func is None:
return context
with context:
func(*args, **kwargs)
[docs] def assertRegex(self, text, regex, msg=None):
super(DjangoTestCase, self).assertRegex(text, regex, msg)
[docs] def assertNotRegex(self, text, regex, msg=None):
super(DjangoTestCase, self).assertNotRegex(text, regex, msg)
[docs]class IgnorePasswordAuthBackend(object):
"""An authentication backend which accepts any password for an existing
user.
It's configured in ``test_settings.py`` and available for all tests.
"""
[docs] supports_authentication = True
[docs] description = _("Testing backend")
[docs] def authenticate(self, request, username=None, password=None, **kwargs):
if not username:
return None
if password:
return None
try:
return User.objects.get(username=username)
except User.DoesNotExist:
raise AssertionError(
'Tried to log in as %r without password, '
'but such a user does not exist. Probably the test '
'forgot to import a database fixture.' % (username,)
)
[docs] def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
[docs]class FakeTimeMiddleware(object):
[docs] _fake_timestamp = threading.local()
def __init__(self, get_response):
self.get_response = get_response
[docs] def __call__(self, request):
self._process_request(request)
return self.get_response(request)
[docs] def _process_request(self, request):
if not hasattr(request, 'timestamp'):
raise ImproperlyConfigured(
"FakeTimeMiddleware must go after TimestampingMiddleware"
)
fake_timestamp = getattr(self._fake_timestamp, 'value', None)
if fake_timestamp:
request.timestamp = fake_timestamp
@contextmanager
[docs]def fake_time(timestamp):
"""A context manager which causes all requests having the specified
timestamp, regardless of the real wall clock time."""
cache.clear()
FakeTimeMiddleware._fake_timestamp.value = timestamp
yield
del FakeTimeMiddleware._fake_timestamp.value
@contextmanager
[docs]def fake_timezone_now(timestamp):
with patch.object(timezone, 'now', return_value=timestamp):
with fake_time(timestamp):
yield
[docs]def get_url(url_or_viewname, qs, *args, **kwargs):
if url_or_viewname.startswith('/'):
url = url_or_viewname
assert not args
assert not kwargs
else:
url = reverse(url_or_viewname, *args, **kwargs)
if qs:
url += '?' + urllib.parse.urlencode(qs)
return url
[docs]def check_not_accessible(testcase, url_or_viewname, qs=None, *args, **kwargs):
data = kwargs.pop('data', {})
url = get_url(url_or_viewname, qs, *args, **kwargs)
response = testcase.client.get(url, data=data, follow=True)
testcase.assertIn(response.status_code, (403, 404, 200))
if response.status_code == 200:
testcase.assertIn('/login/', repr(response.redirect_chain))
[docs]def check_is_accessible(testcase, url_or_viewname, qs=None, *args, **kwargs):
data = kwargs.pop('data', {})
url = get_url(url_or_viewname, qs, *args, **kwargs)
response = testcase.client.get(url, data=data, follow=True)
testcase.assertNotIn(response.status_code, (403, 404))
if response.status_code == 200:
testcase.assertNotIn('/login/', repr(response.redirect_chain))
[docs]def check_ajax_not_accessible(testcase, url_or_viewname, *args, **kwargs):
data = kwargs.pop('data', {})
url = get_url(url_or_viewname, None, *args, **kwargs)
response = testcase.client.get(
url, data=data, HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
testcase.assertIn(response.status_code, (403, 404))
[docs]class TestsUtilsMixin(object):
[docs] def assertAllIn(self, elems, container, msg=None):
"""Checks that ``container`` contains all ``elems``."""
for e in elems:
self.assertIn(e, container, msg)
[docs] def assertNoneIn(self, elems, container, msg=None):
"""Checks that ``container`` doesn't contain any of ``elems``."""
for e in elems:
self.assertNotIn(e, container, msg)
[docs]def needs_linux(fn):
return pytest.mark.skipif(
sys.platform not in ('linux', 'linux2'), reason="This test needs Linux"
)(fn)