Test without optional packages

Tox:
* Add Tox factor for extras (all, none, individual ESP).
  For now, only break out ESPs that have specific extra
  dependencies (amazon_ses, sparkpost).
* Install most package dependencies (including extras)
  through the package itself.
* Use new runtests.py environment vars to limit test tags
  when Tox isn't installing all extras.

Travis:
* Rework matrix to request specific TOXENVs directly;
  drop tox-travis.

Test runner (runtests.py):
* Centralize RUN_LIVE_TESTS logic in runtests.py
* Add ANYMAIL_ONLY_TEST and ANYMAIL_SKIP_TESTS env vars
  (comma-separated lists of tags)

Test implementations:
* Tag all ESP-specific tests with ESP
* Tag live tests with "live"
* Don't import ESP-specific packages at test module level. 
  (Test discovery imports test modules before tag-based filtering.)

Closes #104
This commit is contained in:
Mike Edmunds
2019-02-09 15:04:08 -08:00
committed by GitHub
parent 653fdac3cc
commit 978996d7b8
38 changed files with 237 additions and 146 deletions

View File

@@ -5,13 +5,10 @@ import json
from datetime import datetime
from email.mime.application import MIMEApplication
import botocore.config
import botocore.exceptions
import six
from django.core import mail
from django.core.mail import BadHeaderError
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from mock import ANY, patch
from anymail.exceptions import AnymailAPIError, AnymailUnsupportedFeature
@@ -20,6 +17,7 @@ from anymail.message import attach_inline_image_file, AnymailMessage
from .utils import AnymailTestMixin, SAMPLE_IMAGE_FILENAME, sample_image_content, sample_image_path
@tag('amazon_ses')
@override_settings(EMAIL_BACKEND='anymail.backends.amazon_ses.EmailBackend')
class AmazonSESBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
"""TestCase that uses the Amazon SES EmailBackend with a mocked boto3 client"""
@@ -61,8 +59,9 @@ class AmazonSESBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
return mock_operation.return_value
def set_mock_failure(self, response, operation_name="send_raw_email"):
from botocore.exceptions import ClientError
mock_operation = getattr(self.mock_client_instance, operation_name)
mock_operation.side_effect = botocore.exceptions.ClientError(response, operation_name=operation_name)
mock_operation.side_effect = ClientError(response, operation_name=operation_name)
def get_session_params(self):
if self.mock_session.call_args is None:
@@ -111,6 +110,7 @@ class AmazonSESBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
raise AssertionError(msg or "ESP API was called and shouldn't have been")
@tag('amazon_ses')
class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -318,6 +318,7 @@ class AmazonSESBackendStandardEmailTests(AmazonSESBackendMockAPITestCase):
headers={"X-Header": "custom header value\r\ninjected"}).send()
@tag('amazon_ses')
class AmazonSESBackendAnymailFeatureTests(AmazonSESBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -589,6 +590,7 @@ class AmazonSESBackendAnymailFeatureTests(AmazonSESBackendMockAPITestCase):
self.assertEqual(self.message.anymail_status.esp_response, response_content)
@tag('amazon_ses')
class AmazonSESBackendConfigurationTests(AmazonSESBackendMockAPITestCase):
"""Test configuration options"""
@@ -635,7 +637,8 @@ class AmazonSESBackendConfigurationTests(AmazonSESBackendMockAPITestCase):
def test_client_params_in_connection_init(self):
"""You can also supply credentials specifically for a particular EmailBackend connection instance"""
boto_config = botocore.config.Config(connect_timeout=30)
from botocore.config import Config
boto_config = Config(connect_timeout=30)
conn = mail.get_connection(
'anymail.backends.amazon_ses.EmailBackend',
client_params={"aws_session_token": "test-session-token", "config": boto_config})

View File

@@ -5,7 +5,7 @@ from base64 import b64encode
from datetime import datetime
from textwrap import dedent
import botocore.exceptions
from django.test import tag
from django.utils.timezone import utc
from mock import ANY, patch
@@ -18,6 +18,7 @@ from .test_amazon_ses_webhooks import AmazonSESWebhookTestsMixin
from .webhook_cases import WebhookTestCase
@tag('amazon_ses')
class AmazonSESInboundTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
def setUp(self):
@@ -270,7 +271,8 @@ class AmazonSESInboundTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
def test_inbound_s3_failure_message(self):
"""Issue a helpful error when S3 download fails"""
# Boto's error: "An error occurred (403) when calling the HeadObject operation: Forbidden")
self.mock_s3.download_fileobj.side_effect = botocore.exceptions.ClientError(
from botocore.exceptions import ClientError
self.mock_s3.download_fileobj.side_effect = ClientError(
{'Error': {'Code': 403, 'Message': 'Forbidden'}}, operation_name='HeadObject')
raw_ses_event = {
@@ -290,7 +292,7 @@ class AmazonSESInboundTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
"Anymail AmazonSESInboundWebhookView couldn't download S3 object 'YourBucket:inbound/the_object_key'"
) as cm:
self.post_from_sns('/anymail/amazon_ses/inbound/', raw_sns_message)
self.assertIsInstance(cm.exception, botocore.exceptions.ClientError) # both Boto and Anymail exception class
self.assertIsInstance(cm.exception, ClientError) # both Boto and Anymail exception class
self.assertIn("ClientError: An error occurred (403) when calling the HeadObject operation: Forbidden",
str(cm.exception)) # original Boto message included

View File

@@ -5,13 +5,12 @@ import os
import unittest
import warnings
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
try:
ResourceWarning
@@ -24,7 +23,6 @@ AMAZON_SES_TEST_SECRET_ACCESS_KEY = os.getenv("AMAZON_SES_TEST_SECRET_ACCESS_KEY
AMAZON_SES_TEST_REGION_NAME = os.getenv("AMAZON_SES_TEST_REGION_NAME", "us-east-1")
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@unittest.skipUnless(AMAZON_SES_TEST_ACCESS_KEY_ID and AMAZON_SES_TEST_SECRET_ACCESS_KEY,
"Set AMAZON_SES_TEST_ACCESS_KEY_ID and AMAZON_SES_TEST_SECRET_ACCESS_KEY "
"environment variables to run Amazon SES integration tests")
@@ -43,6 +41,7 @@ AMAZON_SES_TEST_REGION_NAME = os.getenv("AMAZON_SES_TEST_REGION_NAME", "us-east-
},
"AMAZON_SES_CONFIGURATION_SET_NAME": "TestConfigurationSet", # actual config set in Anymail test account
})
@tag('amazon_ses', 'live')
class AmazonSESBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):
"""Amazon SES API integration tests

View File

@@ -2,8 +2,7 @@ import json
import warnings
from datetime import datetime
import botocore.exceptions
from django.test import override_settings
from django.test import override_settings, tag
from django.utils.timezone import utc
from mock import ANY, patch
@@ -27,6 +26,7 @@ class AmazonSESWebhookTestsMixin(object):
**kwargs)
@tag('amazon_ses')
class AmazonSESWebhookSecurityTests(WebhookTestCase, AmazonSESWebhookTestsMixin, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.post_from_sns('/anymail/amazon_ses/tracking/',
@@ -43,6 +43,7 @@ class AmazonSESWebhookSecurityTests(WebhookTestCase, AmazonSESWebhookTestsMixin,
self.assertEqual(response["WWW-Authenticate"], 'Basic realm="Anymail WEBHOOK_SECRET"')
@tag('amazon_ses')
class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
def test_bounce_event(self):
# This test includes a complete Amazon SES example event. (Later tests omit some payload for brevity.)
@@ -404,6 +405,7 @@ class AmazonSESNotificationsTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
self.post_from_sns('/anymail/amazon_ses/tracking/', raw_sns_message)
@tag('amazon_ses')
class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTestsMixin):
# Anymail will automatically respond to SNS subscription notifications
# if Anymail is configured to require basic auth via WEBHOOK_SECRET.
@@ -450,7 +452,8 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
def test_sns_subscription_confirmation_failure(self):
"""Auto-confirmation allows error through if confirm call fails"""
self.mock_client_instance.confirm_subscription.side_effect = botocore.exceptions.ClientError({
from botocore.exceptions import ClientError
self.mock_client_instance.confirm_subscription.side_effect = ClientError({
'Error': {
'Type': 'Sender',
'Code': 'InternalError',
@@ -461,7 +464,7 @@ class AmazonSESSubscriptionManagementTests(WebhookTestCase, AmazonSESWebhookTest
'HTTPStatusCode': 500,
}
}, operation_name="confirm_subscription")
with self.assertRaisesMessage(botocore.exceptions.ClientError, "Gremlins!"):
with self.assertRaisesMessage(ClientError, "Gremlins!"):
self.post_from_sns('/anymail/amazon_ses/tracking/', self.SNS_SUBSCRIPTION_CONFIRMATION)
# didn't notify receivers:
self.assertEqual(self.tracking_handler.call_count, 0)

View File

@@ -16,8 +16,7 @@ from email.mime.image import MIMEImage
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
from anymail.exceptions import (
@@ -30,6 +29,7 @@ from .utils import (AnymailTestMixin, sample_email_content,
sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME)
@tag('mailgun')
@override_settings(EMAIL_BACKEND='anymail.backends.mailgun.EmailBackend',
ANYMAIL={'MAILGUN_API_KEY': 'test_api_key'})
class MailgunBackendMockAPITestCase(RequestsBackendMockAPITestCase):
@@ -44,6 +44,7 @@ class MailgunBackendMockAPITestCase(RequestsBackendMockAPITestCase):
self.message = mail.EmailMultiAlternatives('Subject', 'Text Body', 'from@example.com', ['to@example.com'])
@tag('mailgun')
class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -374,6 +375,7 @@ class MailgunBackendStandardEmailTests(MailgunBackendMockAPITestCase):
self.assertEqual(sent, 0)
@tag('mailgun')
class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -563,6 +565,7 @@ class MailgunBackendAnymailFeatureTests(MailgunBackendMockAPITestCase):
# (Anything that requests can serialize as a form field will work with Mailgun)
@tag('mailgun')
class MailgunBackendRecipientsRefusedTests(MailgunBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -594,11 +597,13 @@ class MailgunBackendRecipientsRefusedTests(MailgunBackendMockAPITestCase):
self.assertEqual(sent, 0)
@tag('mailgun')
class MailgunBackendSessionSharingTestCase(SessionSharingTestCasesMixin, MailgunBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('mailgun')
@override_settings(EMAIL_BACKEND="anymail.backends.mailgun.EmailBackend")
class MailgunBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place"""

View File

@@ -3,7 +3,7 @@ from datetime import datetime
from textwrap import dedent
import six
from django.test import override_settings
from django.test import override_settings, tag
from django.utils.timezone import utc
from mock import ANY
@@ -19,6 +19,7 @@ from .utils import sample_image_content, sample_email_content
from .webhook_cases import WebhookTestCase
@tag('mailgun')
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
class MailgunInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):

View File

@@ -9,19 +9,18 @@ from datetime import datetime, timedelta
from time import mktime, sleep
import requests
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
MAILGUN_TEST_API_KEY = os.getenv('MAILGUN_TEST_API_KEY')
MAILGUN_TEST_DOMAIN = os.getenv('MAILGUN_TEST_DOMAIN')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('mailgun', 'live')
@unittest.skipUnless(MAILGUN_TEST_API_KEY and MAILGUN_TEST_DOMAIN,
"Set MAILGUN_TEST_API_KEY and MAILGUN_TEST_DOMAIN environment variables "
"to run Mailgun integration tests")

View File

@@ -4,7 +4,7 @@ from datetime import datetime
import hashlib
import hmac
from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings
from django.test import override_settings, tag
from django.utils.timezone import utc
from mock import ANY
@@ -59,6 +59,7 @@ def querydict_to_postdict(qd):
}
@tag('mailgun')
class MailgunWebhookSettingsTestCase(WebhookTestCase):
def test_requires_api_key(self):
with self.assertRaises(ImproperlyConfigured):
@@ -66,6 +67,7 @@ class MailgunWebhookSettingsTestCase(WebhookTestCase):
data=json.dumps(mailgun_sign_payload({'event-data': {'event': 'delivered'}})))
@tag('mailgun')
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
class MailgunWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
should_warn_if_no_auth = False # because we check webhook signature
@@ -94,6 +96,7 @@ class MailgunWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin
self.assertEqual(response.status_code, 400)
@tag('mailgun')
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
class MailgunTestCase(WebhookTestCase):
# Tests for Mailgun's new webhooks (announced 2018-06-29)
@@ -445,6 +448,7 @@ class MailgunTestCase(WebhookTestCase):
self.assertEqual(event.click_url, "https://example.com/test")
@tag('mailgun')
@override_settings(ANYMAIL_MAILGUN_API_KEY=TEST_API_KEY)
class MailgunLegacyTestCase(WebhookTestCase):
# Tests for Mailgun's "legacy" webhooks

View File

@@ -7,8 +7,7 @@ from email.mime.image import MIMEImage
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import (AnymailAPIError, AnymailSerializationError,
AnymailUnsupportedFeature,
@@ -19,6 +18,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att
@tag('mailjet')
@override_settings(EMAIL_BACKEND='anymail.backends.mailjet.EmailBackend',
ANYMAIL={
'MAILJET_API_KEY': '',
@@ -64,6 +64,7 @@ class MailjetBackendMockAPITestCase(RequestsBackendMockAPITestCase):
])
@tag('mailjet')
class MailjetBackendStandardEmailTests(MailjetBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -354,6 +355,7 @@ class MailjetBackendStandardEmailTests(MailjetBackendMockAPITestCase):
self.message.send()
@tag('mailjet')
class MailjetBackendAnymailFeatureTests(MailjetBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -623,11 +625,13 @@ class MailjetBackendAnymailFeatureTests(MailjetBackendMockAPITestCase):
self.message.send()
@tag('mailjet')
class MailjetBackendSessionSharingTestCase(SessionSharingTestCasesMixin, MailjetBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('mailjet')
@override_settings(EMAIL_BACKEND="anymail.backends.mailjet.EmailBackend")
class MailjetBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place"""

View File

@@ -1,6 +1,7 @@
import json
from base64 import b64encode
from django.test import tag
from mock import ANY
from anymail.inbound import AnymailInboundMessage
@@ -11,6 +12,7 @@ from .utils import sample_image_content, sample_email_content
from .webhook_cases import WebhookTestCase
@tag('mailjet')
class MailjetInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):
raw_event = {

View File

@@ -1,19 +1,18 @@
import os
import unittest
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
MAILJET_TEST_API_KEY = os.getenv('MAILJET_TEST_API_KEY')
MAILJET_TEST_SECRET_KEY = os.getenv('MAILJET_TEST_SECRET_KEY')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('mailjet', 'live')
@unittest.skipUnless(MAILJET_TEST_API_KEY and MAILJET_TEST_SECRET_KEY,
"Set MAILJET_TEST_API_KEY and MAILJET_TEST_SECRET_KEY "
"environment variables to run Mailjet integration tests")

View File

@@ -1,6 +1,7 @@
import json
from datetime import datetime
from django.test import tag
from django.utils.timezone import utc
from mock import ANY
@@ -9,6 +10,7 @@ from anymail.webhooks.mailjet import MailjetTrackingWebhookView
from .webhook_cases import WebhookBasicAuthTestsMixin, WebhookTestCase
@tag('mailjet')
class MailjetWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.client.post('/anymail/mailjet/tracking/',
@@ -17,6 +19,7 @@ class MailjetWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin
# Actual tests are in WebhookBasicAuthTestsMixin
@tag('mailjet')
class MailjetDeliveryTestCase(WebhookTestCase):
def test_sent_event(self):

View File

@@ -7,8 +7,7 @@ from email.mime.image import MIMEImage
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
from anymail.exceptions import (AnymailAPIError, AnymailRecipientsRefused,
@@ -19,6 +18,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att
@tag('mandrill')
@override_settings(EMAIL_BACKEND='anymail.backends.mandrill.EmailBackend',
ANYMAIL={'MANDRILL_API_KEY': 'test_api_key'})
class MandrillBackendMockAPITestCase(RequestsBackendMockAPITestCase):
@@ -35,6 +35,7 @@ class MandrillBackendMockAPITestCase(RequestsBackendMockAPITestCase):
self.message = mail.EmailMultiAlternatives('Subject', 'Text Body', 'from@example.com', ['to@example.com'])
@tag('mandrill')
class MandrillBackendStandardEmailTests(MandrillBackendMockAPITestCase):
"""Test backend support for Django mail wrappers"""
@@ -267,6 +268,7 @@ class MandrillBackendStandardEmailTests(MandrillBackendMockAPITestCase):
self.message.send()
@tag('mandrill')
class MandrillBackendAnymailFeatureTests(MandrillBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -534,6 +536,7 @@ class MandrillBackendAnymailFeatureTests(MandrillBackendMockAPITestCase):
self.assertRegex(str(err), r"Decimal.*is not JSON serializable") # original message
@tag('mandrill')
class MandrillBackendRecipientsRefusedTests(MandrillBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -588,11 +591,13 @@ class MandrillBackendRecipientsRefusedTests(MandrillBackendMockAPITestCase):
self.assertEqual(sent, 1) # refused message is included in sent count
@tag('mandrill')
class MandrillBackendSessionSharingTestCase(SessionSharingTestCasesMixin, MandrillBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('mandrill')
@override_settings(EMAIL_BACKEND="anymail.backends.mandrill.EmailBackend")
class MandrillBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test backend without required settings"""

View File

@@ -1,12 +1,13 @@
from datetime import date
from django.core import mail
from django.test import override_settings
from django.test import override_settings, tag
from anymail.exceptions import AnymailSerializationError
from .test_mandrill_backend import MandrillBackendMockAPITestCase
@tag('mandrill')
class MandrillBackendDjrillFeatureTests(MandrillBackendMockAPITestCase):
"""Test backend support for deprecated features leftover from Djrill"""

View File

@@ -1,6 +1,6 @@
from textwrap import dedent
from django.test import override_settings
from django.test import override_settings, tag
from mock import ANY
from anymail.inbound import AnymailInboundMessage
@@ -11,6 +11,7 @@ from .test_mandrill_webhooks import TEST_WEBHOOK_KEY, mandrill_args
from .webhook_cases import WebhookTestCase
@tag('mandrill')
@override_settings(ANYMAIL_MANDRILL_WEBHOOK_KEY=TEST_WEBHOOK_KEY)
class MandrillInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):

View File

@@ -2,18 +2,17 @@ import os
import unittest
from django.core import mail
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError, AnymailRecipientsRefused
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
MANDRILL_TEST_API_KEY = os.getenv('MANDRILL_TEST_API_KEY')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('mandrill', 'live')
@unittest.skipUnless(MANDRILL_TEST_API_KEY,
"Set MANDRILL_TEST_API_KEY environment variable to run integration tests")
@override_settings(MANDRILL_API_KEY=MANDRILL_TEST_API_KEY,

View File

@@ -6,7 +6,7 @@ import hashlib
import hmac
from base64 import b64encode
from django.core.exceptions import ImproperlyConfigured
from django.test import override_settings
from django.test import override_settings, tag
from django.utils.timezone import utc
from mock import ANY
@@ -48,6 +48,7 @@ def mandrill_args(events=None,
}
@tag('mandrill')
class MandrillWebhookSettingsTestCase(WebhookTestCase):
def test_requires_webhook_key(self):
with self.assertRaisesRegex(ImproperlyConfigured, r'MANDRILL_WEBHOOK_KEY'):
@@ -62,6 +63,7 @@ class MandrillWebhookSettingsTestCase(WebhookTestCase):
self.assertEqual(response.status_code, 200)
@tag('mandrill')
@override_settings(ANYMAIL_MANDRILL_WEBHOOK_KEY=TEST_WEBHOOK_KEY)
class MandrillWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
should_warn_if_no_auth = False # because we check webhook signature
@@ -127,6 +129,7 @@ class MandrillWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixi
self.assertEqual(response.status_code, 400)
@tag('mandrill')
@override_settings(ANYMAIL_MANDRILL_WEBHOOK_KEY=TEST_WEBHOOK_KEY)
class MandrillTrackingTestCase(WebhookTestCase):

View File

@@ -7,8 +7,7 @@ from email.mime.image import MIMEImage
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import (
AnymailAPIError, AnymailSerializationError,
@@ -19,6 +18,7 @@ from .mock_requests_backend import RequestsBackendMockAPITestCase, SessionSharin
from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAME, AnymailTestMixin, decode_att
@tag('postmark')
@override_settings(EMAIL_BACKEND='anymail.backends.postmark.EmailBackend',
ANYMAIL={'POSTMARK_SERVER_TOKEN': 'test_server_token'})
class PostmarkBackendMockAPITestCase(RequestsBackendMockAPITestCase):
@@ -36,6 +36,7 @@ class PostmarkBackendMockAPITestCase(RequestsBackendMockAPITestCase):
self.message = mail.EmailMultiAlternatives('Subject', 'Text Body', 'from@example.com', ['to@example.com'])
@tag('postmark')
class PostmarkBackendStandardEmailTests(PostmarkBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -318,6 +319,7 @@ class PostmarkBackendStandardEmailTests(PostmarkBackendMockAPITestCase):
self.message.send()
@tag('postmark')
class PostmarkBackendAnymailFeatureTests(PostmarkBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -605,6 +607,7 @@ class PostmarkBackendAnymailFeatureTests(PostmarkBackendMockAPITestCase):
self.assertRegex(str(err), r"Decimal.*is not JSON serializable") # original message
@tag('postmark')
class PostmarkBackendRecipientsRefusedTests(PostmarkBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -699,11 +702,13 @@ class PostmarkBackendRecipientsRefusedTests(PostmarkBackendMockAPITestCase):
self.assertEqual(status.recipients['spam@example.com'].status, 'rejected')
@tag('postmark')
class PostmarkBackendSessionSharingTestCase(SessionSharingTestCasesMixin, PostmarkBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('postmark')
@override_settings(EMAIL_BACKEND="anymail.backends.postmark.EmailBackend")
class PostmarkBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place"""

View File

@@ -1,6 +1,7 @@
import json
from base64 import b64encode
from django.test import tag
from mock import ANY
from anymail.exceptions import AnymailConfigurationError
@@ -12,6 +13,7 @@ from .utils import sample_image_content, sample_email_content
from .webhook_cases import WebhookTestCase
@tag('postmark')
class PostmarkInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):
raw_event = {

View File

@@ -1,13 +1,12 @@
import os
import unittest
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
# For most integration tests, Postmark's sandboxed "POSTMARK_API_TEST" token is used.
@@ -16,7 +15,7 @@ POSTMARK_TEST_SERVER_TOKEN = os.getenv('POSTMARK_TEST_SERVER_TOKEN')
POSTMARK_TEST_TEMPLATE_ID = os.getenv('POSTMARK_TEST_TEMPLATE_ID')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('postmark', 'live')
@override_settings(ANYMAIL_POSTMARK_SERVER_TOKEN="POSTMARK_API_TEST",
EMAIL_BACKEND="anymail.backends.postmark.EmailBackend")
class PostmarkBackendIntegrationTests(SimpleTestCase, AnymailTestMixin):

View File

@@ -1,6 +1,7 @@
import json
from datetime import datetime
from django.test import tag
from django.utils.timezone import get_fixed_timezone, utc
from mock import ANY
@@ -10,6 +11,7 @@ from anymail.webhooks.postmark import PostmarkTrackingWebhookView
from .webhook_cases import WebhookBasicAuthTestsMixin, WebhookTestCase
@tag('postmark')
class PostmarkWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.client.post('/anymail/postmark/tracking/',
@@ -18,6 +20,7 @@ class PostmarkWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixi
# Actual tests are in WebhookBasicAuthTestsMixin
@tag('postmark')
class PostmarkDeliveryTestCase(WebhookTestCase):
def test_bounce_event(self):
raw_event = {

View File

@@ -9,8 +9,7 @@ from email.mime.image import MIMEImage
import six
from django.core import mail
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
from anymail.exceptions import (AnymailAPIError, AnymailConfigurationError, AnymailSerializationError,
@@ -24,6 +23,7 @@ from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAM
longtype = int if six.PY3 else long # NOQA: F821
@tag('sendgrid')
@override_settings(EMAIL_BACKEND='anymail.backends.sendgrid.EmailBackend',
ANYMAIL={'SENDGRID_API_KEY': 'test_api_key'})
class SendGridBackendMockAPITestCase(RequestsBackendMockAPITestCase):
@@ -36,6 +36,7 @@ class SendGridBackendMockAPITestCase(RequestsBackendMockAPITestCase):
self.message = mail.EmailMultiAlternatives('Subject', 'Text Body', 'from@example.com', ['to@example.com'])
@tag('sendgrid')
class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -328,6 +329,7 @@ class SendGridBackendStandardEmailTests(SendGridBackendMockAPITestCase):
self.message.send()
@tag('sendgrid')
class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -709,6 +711,7 @@ class SendGridBackendAnymailFeatureTests(SendGridBackendMockAPITestCase):
{"email": "from@example.com", "name": "Sender, Inc."})
@tag('sendgrid')
class SendGridBackendRecipientsRefusedTests(SendGridBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -718,11 +721,13 @@ class SendGridBackendRecipientsRefusedTests(SendGridBackendMockAPITestCase):
pass # not applicable to this backend
@tag('sendgrid')
class SendGridBackendSessionSharingTestCase(SessionSharingTestCasesMixin, SendGridBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('sendgrid')
@override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.EmailBackend")
class SendGridBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place"""
@@ -732,6 +737,7 @@ class SendGridBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin)
mail.send_mail('Subject', 'Message', 'from@example.com', ['to@example.com'])
@tag('sendgrid')
@override_settings(EMAIL_BACKEND="anymail.backends.sendgrid.EmailBackend")
class SendGridBackendDisallowsV2Tests(SimpleTestCase, AnymailTestMixin):
"""Using v2-API-only features should cause errors with v3 backend"""

View File

@@ -2,6 +2,7 @@ import json
from textwrap import dedent
import six
from django.test import tag
from mock import ANY
from anymail.inbound import AnymailInboundMessage
@@ -12,6 +13,7 @@ from .utils import sample_image_content, sample_email_content
from .webhook_cases import WebhookTestCase
@tag('sendgrid')
class SendgridInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):
raw_event = {

View File

@@ -2,19 +2,18 @@ import os
import unittest
from datetime import datetime, timedelta
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
SENDGRID_TEST_API_KEY = os.getenv('SENDGRID_TEST_API_KEY')
SENDGRID_TEST_TEMPLATE_ID = os.getenv('SENDGRID_TEST_TEMPLATE_ID')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('sendgrid', 'live')
@unittest.skipUnless(SENDGRID_TEST_API_KEY,
"Set SENDGRID_TEST_API_KEY environment variable "
"to run SendGrid integration tests")

View File

@@ -1,6 +1,7 @@
import json
from datetime import datetime
from django.test import tag
from django.utils.timezone import utc
from mock import ANY
@@ -9,6 +10,7 @@ from anymail.webhooks.sendgrid import SendGridTrackingWebhookView
from .webhook_cases import WebhookBasicAuthTestsMixin, WebhookTestCase
@tag('sendgrid')
class SendGridWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.client.post('/anymail/sendgrid/tracking/',
@@ -17,6 +19,7 @@ class SendGridWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixi
# Actual tests are in WebhookBasicAuthTestsMixin
@tag('sendgrid')
class SendGridDeliveryTestCase(WebhookTestCase):
def test_processed_event(self):

View File

@@ -10,8 +10,7 @@ from email.mime.image import MIMEImage
import six
from django.core import mail
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone
from anymail.exceptions import (AnymailAPIError, AnymailConfigurationError, AnymailSerializationError,
@@ -25,6 +24,7 @@ from .utils import sample_image_content, sample_image_path, SAMPLE_IMAGE_FILENAM
longtype = int if six.PY3 else long # NOQA: F821
@tag('sendinblue')
@override_settings(EMAIL_BACKEND='anymail.backends.sendinblue.EmailBackend',
ANYMAIL={'SENDINBLUE_API_KEY': 'test_api_key'})
class SendinBlueBackendMockAPITestCase(RequestsBackendMockAPITestCase):
@@ -38,6 +38,7 @@ class SendinBlueBackendMockAPITestCase(RequestsBackendMockAPITestCase):
self.message = mail.EmailMultiAlternatives('Subject', 'Text Body', 'from@example.com', ['to@example.com'])
@tag('sendinblue')
class SendinBlueBackendStandardEmailTests(SendinBlueBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -274,6 +275,7 @@ class SendinBlueBackendStandardEmailTests(SendinBlueBackendMockAPITestCase):
self.message.send()
@tag('sendinblue')
class SendinBlueBackendAnymailFeatureTests(SendinBlueBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -510,6 +512,7 @@ class SendinBlueBackendAnymailFeatureTests(SendinBlueBackendMockAPITestCase):
self.assertRegex(str(err), r"Decimal.*is not JSON serializable") # original message
@tag('sendinblue')
class SendinBlueBackendRecipientsRefusedTests(SendinBlueBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -519,11 +522,13 @@ class SendinBlueBackendRecipientsRefusedTests(SendinBlueBackendMockAPITestCase):
pass # not applicable to this backend
@tag('sendinblue')
class SendinBlueBackendSessionSharingTestCase(SessionSharingTestCasesMixin, SendinBlueBackendMockAPITestCase):
"""Requests session sharing tests"""
pass # tests are defined in the mixin
@tag('sendinblue')
@override_settings(EMAIL_BACKEND="anymail.backends.sendinblue.EmailBackend")
class SendinBlueBackendImproperlyConfiguredTests(SimpleTestCase, AnymailTestMixin):
"""Test ESP backend without required settings in place"""

View File

@@ -1,18 +1,17 @@
import os
import unittest
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, RUN_LIVE_TESTS
from .utils import AnymailTestMixin
SENDINBLUE_TEST_API_KEY = os.getenv('SENDINBLUE_TEST_API_KEY')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('sendinblue', 'live')
@unittest.skipUnless(SENDINBLUE_TEST_API_KEY,
"Set SENDINBLUE_TEST_API_KEY environment variable "
"to run SendinBlue integration tests")

View File

@@ -1,6 +1,7 @@
import json
from datetime import datetime
from django.test import tag
from django.utils.timezone import utc
from mock import ANY
@@ -9,6 +10,7 @@ from anymail.webhooks.sendinblue import SendinBlueTrackingWebhookView
from .webhook_cases import WebhookBasicAuthTestsMixin, WebhookTestCase
@tag('sendinblue')
class SendinBlueWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.client.post('/anymail/sendinblue/tracking/',
@@ -17,6 +19,7 @@ class SendinBlueWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMi
# Actual tests are in WebhookBasicAuthTestsMixin
@tag('sendinblue')
class SendinBlueDeliveryTestCase(WebhookTestCase):
# SendinBlue's webhook payload data doesn't seem to be documented anywhere.
# There's a list of webhook events at https://apidocs.sendinblue.com/webhooks/#3.

View File

@@ -8,11 +8,9 @@ import os
import requests
import six
from django.core import mail
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from django.utils.timezone import get_fixed_timezone, override as override_current_timezone, utc
from mock import patch
from sparkpost.exceptions import SparkPostAPIException
from anymail.exceptions import (AnymailAPIError, AnymailUnsupportedFeature, AnymailRecipientsRefused,
AnymailConfigurationError, AnymailInvalidAddress)
@@ -21,6 +19,7 @@ from anymail.message import attach_inline_image_file
from .utils import AnymailTestMixin, decode_att, SAMPLE_IMAGE_FILENAME, sample_image_path, sample_image_content
@tag('sparkpost')
@override_settings(EMAIL_BACKEND='anymail.backends.sparkpost.EmailBackend',
ANYMAIL={'SPARKPOST_API_KEY': 'test_api_key'})
class SparkPostBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
@@ -48,6 +47,7 @@ class SparkPostBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
return self.mock_send.return_value
def set_mock_failure(self, status_code=400, raw=b'{"errors":[{"message":"test error"}]}', encoding='utf-8'):
from sparkpost.exceptions import SparkPostAPIException
# Need to build a real(-ish) requests.Response for SparkPostAPIException
response = requests.Response()
response.status_code = status_code
@@ -82,6 +82,7 @@ class SparkPostBackendMockAPITestCase(SimpleTestCase, AnymailTestMixin):
raise AssertionError(msg or "ESP API was called and shouldn't have been")
@tag('sparkpost')
class SparkPostBackendStandardEmailTests(SparkPostBackendMockAPITestCase):
"""Test backend support for Django standard email features"""
@@ -325,6 +326,7 @@ class SparkPostBackendStandardEmailTests(SparkPostBackendMockAPITestCase):
self.message.send()
@tag('sparkpost')
class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
"""Test backend support for Anymail added features"""
@@ -545,6 +547,7 @@ class SparkPostBackendAnymailFeatureTests(SparkPostBackendMockAPITestCase):
# modify those errors.
@tag('sparkpost')
class SparkPostBackendRecipientsRefusedTests(SparkPostBackendMockAPITestCase):
"""Should raise AnymailRecipientsRefused when *all* recipients are rejected or invalid"""
@@ -586,6 +589,7 @@ class SparkPostBackendRecipientsRefusedTests(SparkPostBackendMockAPITestCase):
self.assertEqual(sent, 1) # refused message is included in sent count
@tag('sparkpost')
@override_settings(EMAIL_BACKEND="anymail.backends.sparkpost.EmailBackend")
class SparkPostBackendConfigurationTests(SimpleTestCase, AnymailTestMixin):
"""Test various SparkPost client options"""

View File

@@ -2,6 +2,7 @@ import json
from base64 import b64encode
from textwrap import dedent
from django.test import tag
from mock import ANY
from anymail.inbound import AnymailInboundMessage
@@ -12,6 +13,7 @@ from .utils import sample_image_content, sample_email_content
from .webhook_cases import WebhookTestCase
@tag('sparkpost')
class SparkpostInboundTestCase(WebhookTestCase):
def test_inbound_basics(self):
event = {

View File

@@ -2,18 +2,17 @@ import os
import unittest
from datetime import datetime, timedelta
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test import SimpleTestCase, override_settings, tag
from anymail.exceptions import AnymailAPIError
from anymail.message import AnymailMessage
from .utils import AnymailTestMixin, sample_image_path, RUN_LIVE_TESTS
from .utils import AnymailTestMixin, sample_image_path
SPARKPOST_TEST_API_KEY = os.getenv('SPARKPOST_TEST_API_KEY')
@unittest.skipUnless(RUN_LIVE_TESTS, "RUN_LIVE_TESTS disabled in this environment")
@tag('sparkpost', 'live')
@unittest.skipUnless(SPARKPOST_TEST_API_KEY,
"Set SPARKPOST_TEST_API_KEY environment variable "
"to run SparkPost integration tests")

View File

@@ -1,6 +1,7 @@
import json
from datetime import datetime
from django.test import tag
from django.utils.timezone import utc
from mock import ANY
@@ -10,6 +11,7 @@ from anymail.webhooks.sparkpost import SparkPostTrackingWebhookView
from .webhook_cases import WebhookBasicAuthTestsMixin, WebhookTestCase
@tag('sparkpost')
class SparkPostWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMixin):
def call_webhook(self):
return self.client.post('/anymail/sparkpost/tracking/',
@@ -18,6 +20,7 @@ class SparkPostWebhookSecurityTestCase(WebhookTestCase, WebhookBasicAuthTestsMix
# Actual tests are in WebhookBasicAuthTestsMixin
@tag('sparkpost')
class SparkPostDeliveryTestCase(WebhookTestCase):
def test_ping_event(self):

View File

@@ -8,32 +8,12 @@ import uuid
import warnings
from base64 import b64decode
from contextlib import contextmanager
from distutils.util import strtobool
import six
from django.test import Client
from six.moves import StringIO
def envbool(var, default=False):
"""Returns value of environment variable var as a bool, or default if not set.
Converts `'true'` to `True`, and `'false'` to `False`.
See :func:`~distutils.util.strtobool` for full list of allowable values.
"""
val = os.getenv(var, None)
if val is None:
return default
else:
return strtobool(val)
# RUN_LIVE_TESTS: whether to run live API integration tests.
# True by default, except in CONTINUOUS_INTEGRATION job.
# (See comments and overrides in .travis.yml.)
RUN_LIVE_TESTS = envbool('RUN_LIVE_TESTS', default=not envbool('CONTINUOUS_INTEGRATION'))
def decode_att(att):
"""Returns the original data from base64-encoded attachment content"""
return b64decode(att.encode('ascii'))