from datetime import datetime from django.core.exceptions import ImproperlyConfigured from django.core.mail import get_connection, send_mail from django.test import SimpleTestCase from django.test.utils import override_settings from django.utils.timezone import utc from anymail.exceptions import AnymailConfigurationError, AnymailUnsupportedFeature from anymail.message import AnymailMessage from .utils import AnymailTestMixin recorded_send_params = [] @override_settings(EMAIL_BACKEND='anymail.backends.test.TestBackend', ANYMAIL_TEST_SAMPLE_SETTING='sample', # required TestBackend setting ANYMAIL_TEST_RECORDED_SEND_PARAMS=recorded_send_params) class TestBackendTestCase(SimpleTestCase, AnymailTestMixin): """Base TestCase using Anymail's TestBackend""" def setUp(self): super(TestBackendTestCase, self).setUp() del recorded_send_params[:] # empty the list from previous tests # Simple message useful for many tests self.message = AnymailMessage('Subject', 'Text Body', 'from@example.com', ['to@example.com']) @staticmethod def get_send_params(): return recorded_send_params[-1] @override_settings(EMAIL_BACKEND='anymail.backends.test.TestBackend') # but no ANYMAIL settings overrides class BackendSettingsTests(SimpleTestCase, AnymailTestMixin): # (so not TestBackendTestCase) """Test settings initializations for Anymail EmailBackends""" @override_settings(ANYMAIL={'TEST_SAMPLE_SETTING': 'setting_from_anymail_settings'}) def test_anymail_setting(self): """ESP settings usually come from ANYMAIL settings dict""" backend = get_connection() self.assertEqual(backend.sample_setting, 'setting_from_anymail_settings') @override_settings(TEST_SAMPLE_SETTING='setting_from_bare_settings') def test_bare_setting(self): """ESP settings are also usually allowed at root of settings file""" backend = get_connection() self.assertEqual(backend.sample_setting, 'setting_from_bare_settings') @override_settings(ANYMAIL={'TEST_SAMPLE_SETTING': 'setting_from_settings'}) def test_connection_kwargs_overrides_settings(self): """Can override settings file in get_connection""" backend = get_connection() self.assertEqual(backend.sample_setting, 'setting_from_settings') backend = get_connection(sample_setting='setting_from_kwargs') self.assertEqual(backend.sample_setting, 'setting_from_kwargs') def test_missing_setting(self): """Settings without defaults must be provided""" with self.assertRaises(AnymailConfigurationError) as cm: get_connection() self.assertIsInstance(cm.exception, ImproperlyConfigured) # Django consistency errmsg = str(cm.exception) self.assertRegex(errmsg, r'\bTEST_SAMPLE_SETTING\b') self.assertRegex(errmsg, r'\bANYMAIL_TEST_SAMPLE_SETTING\b') @override_settings(ANYMAIL={'SENDGRID_USERNAME': 'username_from_settings', 'SENDGRID_PASSWORD': 'password_from_settings'}) def test_username_password_kwargs_overrides(self): """Overrides for 'username' and 'password' should work like other overrides""" # These are special-cased because of default args in Django core mail functions. # (Use the SendGrid backend, which has settings named 'username' and 'password'.) backend = get_connection('anymail.backends.sendgrid.SendGridBackend') self.assertEqual(backend.username, 'username_from_settings') self.assertEqual(backend.password, 'password_from_settings') backend = get_connection('anymail.backends.sendgrid.SendGridBackend', username='username_from_kwargs', password='password_from_kwargs') self.assertEqual(backend.username, 'username_from_kwargs') self.assertEqual(backend.password, 'password_from_kwargs') class UnsupportedFeatureTests(TestBackendTestCase): """Tests mail features not supported by backend are handled properly""" def test_unsupported_feature(self): """Unsupported features raise AnymailUnsupportedFeature""" # TestBackend doesn't support non-HTML alternative parts self.message.attach_alternative(b'FAKE_MP3_DATA', 'audio/mpeg') with self.assertRaises(AnymailUnsupportedFeature): self.message.send() @override_settings(ANYMAIL={ 'IGNORE_UNSUPPORTED_FEATURES': True }) def test_ignore_unsupported_features(self): """Setting prevents exception""" self.message.attach_alternative(b'FAKE_MP3_DATA', 'audio/mpeg') self.message.send() # should not raise exception class SendDefaultsTests(TestBackendTestCase): """Tests backend support for global SEND_DEFAULTS and _SEND_DEFAULTS""" @override_settings(ANYMAIL={ 'SEND_DEFAULTS': { # This isn't an exhaustive list of Anymail message attrs; just one of each type 'metadata': {'global': 'globalvalue'}, 'send_at': datetime(2016, 5, 12, 4, 17, 0, tzinfo=utc), 'tags': ['globaltag'], 'template_id': 'my-template', 'track_clicks': True, 'esp_extra': {'globalextra': 'globalsetting'}, } }) def test_send_defaults(self): """Test that (non-esp-specific) send defaults are applied""" self.message.send() params = self.get_send_params() # All these values came from ANYMAIL_SEND_DEFAULTS: self.assertEqual(params['metadata'], {'global': 'globalvalue'}) self.assertEqual(params['send_at'], datetime(2016, 5, 12, 4, 17, 0, tzinfo=utc)) self.assertEqual(params['tags'], ['globaltag']) self.assertEqual(params['template_id'], 'my-template') self.assertEqual(params['track_clicks'], True) self.assertEqual(params['globalextra'], 'globalsetting') # TestBackend merges esp_extra into params @override_settings(ANYMAIL={ 'TEST_SEND_DEFAULTS': { # "TEST" is the name of the TestBackend's ESP 'metadata': {'global': 'espvalue'}, 'tags': ['esptag'], 'track_opens': False, 'esp_extra': {'globalextra': 'espsetting'}, } }) def test_esp_send_defaults(self): """Test that esp-specific send defaults are applied""" self.message.send() params = self.get_send_params() self.assertEqual(params['metadata'], {'global': 'espvalue'}) self.assertEqual(params['tags'], ['esptag']) self.assertEqual(params['track_opens'], False) self.assertEqual(params['globalextra'], 'espsetting') # TestBackend merges esp_extra into params @override_settings(ANYMAIL={ 'SEND_DEFAULTS': { 'metadata': {'global': 'globalvalue', 'other': 'othervalue'}, 'tags': ['globaltag'], 'track_clicks': True, 'track_opens': False, 'esp_extra': {'globalextra': 'globalsetting'}, } }) def test_send_defaults_combine_with_message(self): """Individual message settings are *merged into* the global send defaults""" self.message.metadata = {'message': 'messagevalue', 'other': 'override'} self.message.tags = ['messagetag'] self.message.track_clicks = False self.message.esp_extra = {'messageextra': 'messagesetting'} self.message.send() params = self.get_send_params() self.assertEqual(params['metadata'], { # metadata merged 'global': 'globalvalue', # global default preserved 'message': 'messagevalue', # message setting added 'other': 'override'}) # message setting overrides global default self.assertEqual(params['tags'], ['globaltag', 'messagetag']) # tags concatenated self.assertEqual(params['track_clicks'], False) # message overrides self.assertEqual(params['track_opens'], False) # (no message setting) self.assertEqual(params['globalextra'], 'globalsetting') self.assertEqual(params['messageextra'], 'messagesetting') # Send another message to make sure original SEND_DEFAULTS unchanged send_mail('subject', 'body', 'from@example.com', ['to@example.com']) params = self.get_send_params() self.assertEqual(params['metadata'], {'global': 'globalvalue', 'other': 'othervalue'}) self.assertEqual(params['tags'], ['globaltag']) self.assertEqual(params['track_clicks'], True) self.assertEqual(params['track_opens'], False) self.assertEqual(params['globalextra'], 'globalsetting') @override_settings(ANYMAIL={ 'SEND_DEFAULTS': { # This isn't an exhaustive list of Anymail message attrs; just one of each type 'metadata': {'global': 'globalvalue'}, 'tags': ['globaltag'], 'template_id': 'global-template', 'esp_extra': {'globalextra': 'globalsetting'}, }, 'TEST_SEND_DEFAULTS': { # "TEST" is the name of the TestBackend's ESP 'merge_global_data': {'esp': 'espmerge'}, 'metadata': {'esp': 'espvalue'}, 'tags': ['esptag'], 'esp_extra': {'espextra': 'espsetting'}, } }) def test_esp_send_defaults_override_globals(self): """ESP-specific send defaults override *individual* global defaults""" self.message.send() params = self.get_send_params() self.assertEqual(params['merge_global_data'], {'esp': 'espmerge'}) # esp-defaults only self.assertEqual(params['metadata'], {'esp': 'espvalue'}) self.assertEqual(params['tags'], ['esptag']) self.assertEqual(params['template_id'], 'global-template') # global-defaults only self.assertEqual(params['espextra'], 'espsetting') self.assertNotIn('globalextra', params) # entire esp_extra is overriden by esp-send-defaults