Use override_settings rather than mucking with settings in tests

Second attempt to address possible test sequencing issue around tests that alter settings. (Failures in Travis tests not reproducible locally.)

Back-ports override_settings from Django 1.4 for compatibility with Django 1.3.
This commit is contained in:
medmunds
2014-04-20 15:48:12 -07:00
parent ea2499f92f
commit 8815601b65
5 changed files with 110 additions and 58 deletions

View File

@@ -1,19 +1,22 @@
import json import json
from mock import patch from mock import patch
from django.conf import settings
from django.test import TestCase from django.test import TestCase
from .utils import override_settings
@override_settings(MANDRILL_API_KEY="FAKE_API_KEY_FOR_TESTING",
EMAIL_BACKEND="djrill.mail.backends.djrill.DjrillBackend")
class DjrillBackendMockAPITestCase(TestCase): class DjrillBackendMockAPITestCase(TestCase):
"""TestCase that uses Djrill EmailBackend with a mocked Mandrill API""" """TestCase that uses Djrill EmailBackend with a mocked Mandrill API"""
class MockResponse: class MockResponse:
"""requests.post return value mock sufficient for DjrillBackend""" """requests.post return value mock sufficient for DjrillBackend"""
def __init__(self, status_code=200, content="{}", json=['']): def __init__(self, status_code=200, content="{}", json=None):
self.status_code = status_code self.status_code = status_code
self.content = content self.content = content
self._json = json self._json = json if json is not None else ['']
def json(self): def json(self):
return self._json return self._json
@@ -23,15 +26,8 @@ class DjrillBackendMockAPITestCase(TestCase):
self.mock_post = self.patch.start() self.mock_post = self.patch.start()
self.mock_post.return_value = self.MockResponse() self.mock_post.return_value = self.MockResponse()
settings.MANDRILL_API_KEY = "FAKE_API_KEY_FOR_TESTING"
# Django TestCase sets up locmem EmailBackend; override it here
self.original_email_backend = settings.EMAIL_BACKEND
settings.EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend"
def tearDown(self): def tearDown(self):
self.patch.stop() self.patch.stop()
settings.EMAIL_BACKEND = self.original_email_backend
def assert_mandrill_called(self, endpoint): def assert_mandrill_called(self, endpoint):
"""Verifies the (mock) Mandrill API was called on endpoint. """Verifies the (mock) Mandrill API was called on endpoint.

View File

@@ -4,13 +4,15 @@ from email.mime.base import MIMEBase
from email.mime.image import MIMEImage from email.mime.image import MIMEImage
import os import os
from django.conf import settings
from django.core import mail from django.core import mail
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.mail import make_msgid from django.core.mail import make_msgid
from django.test import TestCase
from djrill import MandrillAPIError, NotSupportedByMandrillError from djrill import MandrillAPIError, NotSupportedByMandrillError
from djrill.tests.mock_backend import DjrillBackendMockAPITestCase from .mock_backend import DjrillBackendMockAPITestCase
from .utils import override_settings
def decode_att(att): def decode_att(att):
"""Returns the original data from base64-encoded attachment content""" """Returns the original data from base64-encoded attachment content"""
@@ -46,12 +48,6 @@ class DjrillBackendTests(DjrillBackendMockAPITestCase):
self.assertEqual(len(data['message']['to']), 1) self.assertEqual(len(data['message']['to']), 1)
self.assertEqual(data['message']['to'][0]['email'], "to@example.com") self.assertEqual(data['message']['to'][0]['email'], "to@example.com")
def test_missing_api_key(self):
del settings.MANDRILL_API_KEY
with self.assertRaises(ImproperlyConfigured):
mail.send_mail('Subject', 'Message', 'from@example.com',
['to@example.com'])
def test_name_addr(self): def test_name_addr(self):
"""Make sure RFC2822 name-addr format (with display-name) is allowed """Make sure RFC2822 name-addr format (with display-name) is allowed
@@ -462,3 +458,13 @@ class DjrillMandrillFeatureTests(DjrillBackendMockAPITestCase):
sent = msg.send(fail_silently=True) sent = msg.send(fail_silently=True)
self.assertEqual(sent, 0) self.assertEqual(sent, 0)
self.assertIsNone(msg.mandrill_response) self.assertIsNone(msg.mandrill_response)
@override_settings(EMAIL_BACKEND="djrill.mail.backends.djrill.DjrillBackend")
class DjrillImproperlyConfiguredTests(TestCase):
"""Test Djrill backend without Djrill-specific settings in place"""
def test_missing_api_key(self):
with self.assertRaises(ImproperlyConfigured):
mail.send_mail('Subject', 'Message', 'from@example.com',
['to@example.com'])

View File

@@ -1,19 +1,13 @@
from django.core import mail from django.core import mail
from djrill.tests.mock_backend import DjrillBackendMockAPITestCase from .mock_backend import DjrillBackendMockAPITestCase
from .utils import override_settings
from django.conf import settings
class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase): class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase):
"""Test Djrill backend support for Mandrill subaccounts""" """Test Djrill backend support for Mandrill subaccounts"""
def test_send_basic(self): def test_send_basic(self):
# Make sure we don't have a MANDRILL_SUBACCOUNT from a previous test
try:
del settings.MANDRILL_SUBACCOUNT
except AttributeError:
pass
mail.send_mail('Subject here', 'Here is the message.', mail.send_mail('Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'], fail_silently=False) 'from@example.com', ['to@example.com'], fail_silently=False)
self.assert_mandrill_called("/messages/send.json") self.assert_mandrill_called("/messages/send.json")
@@ -26,9 +20,8 @@ class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase):
self.assertEqual(data['message']['to'][0]['email'], "to@example.com") self.assertEqual(data['message']['to'][0]['email'], "to@example.com")
self.assertFalse('subaccount' in data['message']) self.assertFalse('subaccount' in data['message'])
@override_settings(MANDRILL_SUBACCOUNT="test_subaccount")
def test_send_from_subaccount(self): def test_send_from_subaccount(self):
settings.MANDRILL_SUBACCOUNT = "test_subaccount"
mail.send_mail('Subject here', 'Here is the message.', mail.send_mail('Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'], fail_silently=False) 'from@example.com', ['to@example.com'], fail_silently=False)
self.assert_mandrill_called("/messages/send.json") self.assert_mandrill_called("/messages/send.json")
@@ -39,10 +32,10 @@ class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase):
self.assertEqual(data['message']['from_email'], "from@example.com") self.assertEqual(data['message']['from_email'], "from@example.com")
self.assertEqual(len(data['message']['to']), 1) self.assertEqual(len(data['message']['to']), 1)
self.assertEqual(data['message']['to'][0]['email'], "to@example.com") self.assertEqual(data['message']['to'][0]['email'], "to@example.com")
self.assertEqual(data['message']['subaccount'], settings.MANDRILL_SUBACCOUNT) self.assertEqual(data['message']['subaccount'], "test_subaccount")
@override_settings(MANDRILL_SUBACCOUNT="global_setting_subaccount")
def test_subaccount_message_overrides_setting(self): def test_subaccount_message_overrides_setting(self):
settings.MANDRILL_SUBACCOUNT = "global_setting_subaccount"
message = mail.EmailMessage( message = mail.EmailMessage(
'Subject here', 'Here is the message', 'Subject here', 'Here is the message',
'from@example.com', ['to@example.com']) 'from@example.com', ['to@example.com'])

View File

@@ -3,12 +3,13 @@ import hashlib
import hmac import hmac
import json import json
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
from ..compat import b from ..compat import b
from ..signals import webhook_event from ..signals import webhook_event
from .utils import override_settings
class DjrillWebhookSecretMixinTests(TestCase): class DjrillWebhookSecretMixinTests(TestCase):
@@ -17,71 +18,59 @@ class DjrillWebhookSecretMixinTests(TestCase):
""" """
def test_missing_secret(self): def test_missing_secret(self):
del settings.DJRILL_WEBHOOK_SECRET
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):
self.client.get('/webhook/') self.client.get('/webhook/')
@override_settings(DJRILL_WEBHOOK_SECRET='abc123')
def test_incorrect_secret(self): def test_incorrect_secret(self):
settings.DJRILL_WEBHOOK_SECRET = 'abc123'
response = self.client.head('/webhook/?secret=wrong') response = self.client.head('/webhook/?secret=wrong')
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@override_settings(DJRILL_WEBHOOK_SECRET='abc123')
def test_default_secret_name(self): def test_default_secret_name(self):
del settings.DJRILL_WEBHOOK_SECRET_NAME
settings.DJRILL_WEBHOOK_SECRET = 'abc123'
response = self.client.head('/webhook/?secret=abc123') response = self.client.head('/webhook/?secret=abc123')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@override_settings(DJRILL_WEBHOOK_SECRET='abc123', DJRILL_WEBHOOK_SECRET_NAME='verysecret')
def test_custom_secret_name(self): def test_custom_secret_name(self):
settings.DJRILL_WEBHOOK_SECRET = 'abc123'
settings.DJRILL_WEBHOOK_SECRET_NAME = 'verysecret'
response = self.client.head('/webhook/?verysecret=abc123') response = self.client.head('/webhook/?verysecret=abc123')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@override_settings(DJRILL_WEBHOOK_SECRET='abc123',
DJRILL_WEBHOOK_SIGNATURE_KEY="signature")
class DjrillWebhookSignatureMixinTests(TestCase): class DjrillWebhookSignatureMixinTests(TestCase):
""" """
Test mixin used in optional Mandrill webhook signature support Test mixin used in optional Mandrill webhook signature support
""" """
def setUp(self):
settings.DJRILL_WEBHOOK_SECRET = 'abc123'
settings.DJRILL_WEBHOOK_SIGNATURE_KEY = "signature"
settings.DJRILL_WEBHOOK_URL = "/webhook/?secret=abc123"
def test_incorrect_settings(self): def test_incorrect_settings(self):
del settings.DJRILL_WEBHOOK_URL
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):
self.client.post('/webhook/?secret=abc123') self.client.post('/webhook/?secret=abc123')
settings.DJRILL_WEBHOOK_URL = "/webhook/?secret=abc123"
@override_settings(DJRILL_WEBHOOK_URL="/webhook/?secret=abc123",
DJRILL_WEBHOOK_SIGNATURE_KEY = "anothersignature")
def test_unauthorized(self): def test_unauthorized(self):
settings.DJRILL_WEBHOOK_SIGNATURE_KEY = "anothersignature"
response = self.client.post(settings.DJRILL_WEBHOOK_URL) response = self.client.post(settings.DJRILL_WEBHOOK_URL)
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
@override_settings(DJRILL_WEBHOOK_URL="/webhook/?secret=abc123")
def test_signature(self): def test_signature(self):
signature = hmac.new(key=b(settings.DJRILL_WEBHOOK_SIGNATURE_KEY), msg = b(settings.DJRILL_WEBHOOK_URL+"mandrill_events[]"), digestmod=hashlib.sha1) signature = hmac.new(key=b(settings.DJRILL_WEBHOOK_SIGNATURE_KEY),
msg=b(settings.DJRILL_WEBHOOK_URL+"mandrill_events[]"),
digestmod=hashlib.sha1)
hash_string = b64encode(signature.digest()) hash_string = b64encode(signature.digest())
response = self.client.post('/webhook/?secret=abc123', data={"mandrill_events":"[]"}, **{"HTTP_X_MANDRILL_SIGNATURE" : hash_string}) response = self.client.post('/webhook/?secret=abc123', data={"mandrill_events":"[]"},
**{"HTTP_X_MANDRILL_SIGNATURE": hash_string})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def tearDown(self):
del settings.DJRILL_WEBHOOK_SIGNATURE_KEY
del settings.DJRILL_WEBHOOK_URL
@override_settings(DJRILL_WEBHOOK_SECRET='abc123')
class DjrillWebhookViewTests(TestCase): class DjrillWebhookViewTests(TestCase):
""" """
Test optional Mandrill webhook view Test optional Mandrill webhook view
""" """
def setUp(self):
settings.DJRILL_WEBHOOK_SECRET = 'abc123'
def test_head_request(self): def test_head_request(self):
response = self.client.head('/webhook/?secret=abc123') response = self.client.head('/webhook/?secret=abc123')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

68
djrill/tests/utils.py Normal file
View File

@@ -0,0 +1,68 @@
__all__ = (
'override_settings',
)
try:
from django.test.utils import override_settings
except ImportError:
# Back-port override_settings from Django 1.4
# https://github.com/django/django/blob/stable/1.4.x/django/test/utils.py
from django.conf import settings, UserSettingsHolder
from django.utils.functional import wraps
class override_settings(object):
"""
Acts as either a decorator, or a context manager. If it's a decorator it
takes a function and returns a wrapped function. If it's a contextmanager
it's used with the ``with`` statement. In either event entering/exiting
are called before and after, respectively, the function/block is executed.
"""
def __init__(self, **kwargs):
self.options = kwargs
self.wrapped = settings._wrapped
def __enter__(self):
self.enable()
def __exit__(self, exc_type, exc_value, traceback):
self.disable()
def __call__(self, test_func):
from django.test import TransactionTestCase
if isinstance(test_func, type) and issubclass(test_func, TransactionTestCase):
original_pre_setup = test_func._pre_setup
original_post_teardown = test_func._post_teardown
def _pre_setup(innerself):
self.enable()
original_pre_setup(innerself)
def _post_teardown(innerself):
original_post_teardown(innerself)
self.disable()
test_func._pre_setup = _pre_setup
test_func._post_teardown = _post_teardown
return test_func
else:
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
return inner
def enable(self):
override = UserSettingsHolder(settings._wrapped)
for key, new_value in self.options.items():
setattr(override, key, new_value)
settings._wrapped = override
# No setting_changed signal in Django 1.3
# for key, new_value in self.options.items():
# setting_changed.send(sender=settings._wrapped.__class__,
# setting=key, value=new_value)
def disable(self):
settings._wrapped = self.wrapped
# No setting_changed signal in Django 1.3
# for key in self.options:
# new_value = getattr(settings, key, None)
# setting_changed.send(sender=settings._wrapped.__class__,
# setting=key, value=new_value)