Raise better exception on InvalidParameterValue error

There are several reasons why we might get an `InvalidParameterValue`
from the SES API. One, as correctly identified before in
https://github.com/alphagov/notifications-api/pull/713/files
is if we allow an email address on our side that SES rejects.

However, there are other types of errors that could cause an
`InvalidParameterValue`. One example is a `Header too long: 'Subject'`
error that we have seen happen in production. This shouldn't raise an
`InvalidEmailError` as that is not appropriate.

Therefore, we introduce a new exception
`EmailClientNonRetryableException`, that represents any exception back
from an email client that we can use whenever we get a
`InvalidParameterValue` error.

Note, I chose `EmailClientNonRetryableException` rather than
`SESClientNonRetryableException` as our code needs to catch this
exception and it shouldn't be aware of what email client is being used,
it just needs to know that it came from one of the email clients (if in
time we have more than one).

In time, we may wish to extend the approach of having generic
`EmailClient` exceptions and `SMSClient` exceptions as this should be
the most extendable pattern and a good abstraction.
This commit is contained in:
David McDonald
2020-12-30 17:06:49 +00:00
parent 2079202160
commit 2480f91667
5 changed files with 25 additions and 10 deletions

View File

@@ -1,11 +1,11 @@
import pytest
from botocore.exceptions import ClientError
from celery.exceptions import MaxRetriesExceededError
from notifications_utils.recipients import InvalidEmailError
import app
from app.celery import provider_tasks
from app.celery.provider_tasks import deliver_sms, deliver_email
from app.clients.email import EmailClientNonRetryableException
from app.clients.email.aws_ses import AwsSesClientException, AwsSesClientThrottlingSendRateException
from app.exceptions import NotificationTechnicalFailureException
@@ -93,8 +93,11 @@ def test_should_go_into_technical_error_if_exceeds_retries_on_deliver_email_task
assert sample_notification.status == 'technical-failure'
def test_should_technical_error_and_not_retry_if_invalid_email(sample_notification, mocker):
mocker.patch('app.delivery.send_to_providers.send_email_to_provider', side_effect=InvalidEmailError('bad email'))
def test_should_technical_error_and_not_retry_if_EmailClientNonRetryableException(sample_notification, mocker):
mocker.patch(
'app.delivery.send_to_providers.send_email_to_provider',
side_effect=EmailClientNonRetryableException('bad email')
)
mocker.patch('app.celery.provider_tasks.deliver_email.retry')
deliver_email(sample_notification.id)

View File

@@ -1,9 +1,9 @@
import botocore
import pytest
from unittest.mock import Mock, ANY
from notifications_utils.recipients import InvalidEmailError
from app import aws_ses_client
from app.clients.email import EmailClientNonRetryableException
from app.clients.email.aws_ses import get_aws_responses, AwsSesClientException, AwsSesClientThrottlingSendRateException
@@ -91,7 +91,7 @@ def test_send_email_handles_punycode_to_address(notify_api, mocker):
)
def test_send_email_raises_bad_email_as_InvalidEmailError(mocker):
def test_send_email_raises_invalid_parameter_value_error_as_EmailClientNonRetryableException(mocker):
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
mocker.patch.object(aws_ses_client, 'statsd_client', create=True)
error_response = {
@@ -104,7 +104,7 @@ def test_send_email_raises_bad_email_as_InvalidEmailError(mocker):
boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')
mocker.patch.object(aws_ses_client, 'statsd_client', create=True)
with pytest.raises(InvalidEmailError) as excinfo:
with pytest.raises(EmailClientNonRetryableException) as excinfo:
aws_ses_client.send_email(
source=Mock(),
to_addresses='definitely@invalid_email.com',