2023-08-11 11:47:57 -07:00
|
|
|
import json
|
|
|
|
|
from unittest import mock
|
2021-03-10 13:55:06 +00:00
|
|
|
from unittest.mock import ANY, Mock
|
|
|
|
|
|
2016-10-13 16:07:32 +01:00
|
|
|
import botocore
|
2016-03-21 09:20:38 +00:00
|
|
|
import pytest
|
|
|
|
|
|
2023-08-11 11:47:57 -07:00
|
|
|
from app import AwsSesStubClient, aws_ses_client
|
2020-12-30 17:06:49 +00:00
|
|
|
from app.clients.email import EmailClientNonRetryableException
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.clients.email.aws_ses import (
|
|
|
|
|
AwsSesClientException,
|
|
|
|
|
AwsSesClientThrottlingSendRateException,
|
|
|
|
|
get_aws_responses,
|
|
|
|
|
)
|
2016-03-21 09:20:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_should_return_correct_details_for_delivery():
|
2016-04-06 16:34:45 +01:00
|
|
|
response_dict = get_aws_responses('Delivery')
|
|
|
|
|
assert response_dict['message'] == 'Delivered'
|
|
|
|
|
assert response_dict['notification_status'] == 'delivered'
|
|
|
|
|
assert response_dict['notification_statistics_status'] == 'delivered'
|
|
|
|
|
assert response_dict['success']
|
2016-03-21 09:20:38 +00:00
|
|
|
|
|
|
|
|
|
2016-05-17 15:38:49 +01:00
|
|
|
def test_should_return_correct_details_for_hard_bounced():
|
|
|
|
|
response_dict = get_aws_responses('Permanent')
|
|
|
|
|
assert response_dict['message'] == 'Hard bounced'
|
|
|
|
|
assert response_dict['notification_status'] == 'permanent-failure'
|
|
|
|
|
assert response_dict['notification_statistics_status'] == 'failure'
|
|
|
|
|
assert not response_dict['success']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_should_return_correct_details_for_soft_bounced():
|
|
|
|
|
response_dict = get_aws_responses('Temporary')
|
|
|
|
|
assert response_dict['message'] == 'Soft bounced'
|
|
|
|
|
assert response_dict['notification_status'] == 'temporary-failure'
|
2016-04-06 16:34:45 +01:00
|
|
|
assert response_dict['notification_statistics_status'] == 'failure'
|
|
|
|
|
assert not response_dict['success']
|
2016-03-21 09:20:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_should_return_correct_details_for_complaint():
|
2016-04-06 16:34:45 +01:00
|
|
|
response_dict = get_aws_responses('Complaint')
|
|
|
|
|
assert response_dict['message'] == 'Complaint'
|
2016-04-08 16:13:10 +01:00
|
|
|
assert response_dict['notification_status'] == 'delivered'
|
|
|
|
|
assert response_dict['notification_statistics_status'] == 'delivered'
|
|
|
|
|
assert response_dict['success']
|
2016-03-21 09:20:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_should_be_none_if_unrecognised_status_code():
|
|
|
|
|
with pytest.raises(KeyError) as e:
|
2016-04-06 16:34:45 +01:00
|
|
|
get_aws_responses('99')
|
2016-03-21 09:20:38 +00:00
|
|
|
assert '99' in str(e.value)
|
2016-07-04 17:29:41 +01:00
|
|
|
|
|
|
|
|
|
2019-08-12 13:53:22 +01:00
|
|
|
@pytest.mark.parametrize('reply_to_address, expected_value', [
|
|
|
|
|
(None, []),
|
|
|
|
|
('foo@bar.com', ['foo@bar.com']),
|
|
|
|
|
('føøøø@bååååår.com', ['føøøø@xn--br-yiaaaaa.com'])
|
|
|
|
|
], ids=['empty', 'single_email', 'punycode'])
|
2016-07-04 17:29:41 +01:00
|
|
|
def test_send_email_handles_reply_to_address(notify_api, mocker, reply_to_address, expected_value):
|
|
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
|
|
|
|
|
with notify_api.app_context():
|
|
|
|
|
aws_ses_client.send_email(
|
2019-08-12 13:53:22 +01:00
|
|
|
source=Mock(),
|
|
|
|
|
to_addresses='to@address.com',
|
|
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock(),
|
2016-07-04 17:29:41 +01:00
|
|
|
reply_to_address=reply_to_address
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
boto_mock.send_email.assert_called_once_with(
|
|
|
|
|
Source=ANY,
|
|
|
|
|
Destination=ANY,
|
|
|
|
|
Message=ANY,
|
|
|
|
|
ReplyToAddresses=expected_value
|
|
|
|
|
)
|
2016-10-13 16:07:32 +01:00
|
|
|
|
|
|
|
|
|
2019-08-12 13:53:22 +01:00
|
|
|
def test_send_email_handles_punycode_to_address(notify_api, mocker):
|
|
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
|
|
|
|
|
with notify_api.app_context():
|
|
|
|
|
aws_ses_client.send_email(
|
|
|
|
|
Mock(),
|
|
|
|
|
to_addresses='føøøø@bååååår.com',
|
|
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
boto_mock.send_email.assert_called_once_with(
|
|
|
|
|
Source=ANY,
|
|
|
|
|
Destination={'ToAddresses': ['føøøø@xn--br-yiaaaaa.com'], 'CcAddresses': [], 'BccAddresses': []},
|
|
|
|
|
Message=ANY,
|
|
|
|
|
ReplyToAddresses=ANY
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2020-12-30 17:06:49 +00:00
|
|
|
def test_send_email_raises_invalid_parameter_value_error_as_EmailClientNonRetryableException(mocker):
|
2016-10-13 16:07:32 +01:00
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
error_response = {
|
|
|
|
|
'Error': {
|
|
|
|
|
'Code': 'InvalidParameterValue',
|
|
|
|
|
'Message': 'some error message from amazon',
|
|
|
|
|
'Type': 'Sender'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')
|
|
|
|
|
|
2020-12-30 17:06:49 +00:00
|
|
|
with pytest.raises(EmailClientNonRetryableException) as excinfo:
|
2016-10-13 16:07:32 +01:00
|
|
|
aws_ses_client.send_email(
|
|
|
|
|
source=Mock(),
|
2019-08-12 13:53:22 +01:00
|
|
|
to_addresses='definitely@invalid_email.com',
|
2016-10-13 16:07:32 +01:00
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock()
|
|
|
|
|
)
|
|
|
|
|
|
2017-01-09 16:22:27 +00:00
|
|
|
assert 'some error message from amazon' in str(excinfo.value)
|
2016-10-13 16:07:32 +01:00
|
|
|
|
|
|
|
|
|
2020-08-13 17:18:19 +01:00
|
|
|
def test_send_email_raises_send_rate_throttling_as_AwsSesClientThrottlingSendRateException(mocker):
|
|
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
error_response = {
|
|
|
|
|
'Error': {
|
|
|
|
|
'Code': 'Throttling',
|
|
|
|
|
'Message': 'Maximum sending rate exceeded.',
|
|
|
|
|
'Type': 'Sender'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AwsSesClientThrottlingSendRateException):
|
|
|
|
|
aws_ses_client.send_email(
|
|
|
|
|
source=Mock(),
|
|
|
|
|
to_addresses='foo@bar.com',
|
|
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_send_email_does_not_raise_AwsSesClientThrottlingSendRateException_if_non_send_rate_throttling(mocker):
|
|
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
error_response = {
|
|
|
|
|
'Error': {
|
|
|
|
|
'Code': 'Throttling',
|
|
|
|
|
'Message': 'Daily message quota exceeded',
|
|
|
|
|
'Type': 'Sender'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AwsSesClientException):
|
|
|
|
|
aws_ses_client.send_email(
|
|
|
|
|
source=Mock(),
|
|
|
|
|
to_addresses='foo@bar.com',
|
|
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2016-10-13 16:07:32 +01:00
|
|
|
def test_send_email_raises_other_errs_as_AwsSesClientException(mocker):
|
|
|
|
|
boto_mock = mocker.patch.object(aws_ses_client, '_client', create=True)
|
|
|
|
|
error_response = {
|
|
|
|
|
'Error': {
|
|
|
|
|
'Code': 'ServiceUnavailable',
|
|
|
|
|
'Message': 'some error message from amazon',
|
|
|
|
|
'Type': 'Sender'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
boto_mock.send_email.side_effect = botocore.exceptions.ClientError(error_response, 'opname')
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AwsSesClientException) as excinfo:
|
|
|
|
|
aws_ses_client.send_email(
|
|
|
|
|
source=Mock(),
|
2019-08-12 13:53:22 +01:00
|
|
|
to_addresses='foo@bar.com',
|
2016-10-13 16:07:32 +01:00
|
|
|
subject=Mock(),
|
|
|
|
|
body=Mock()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert 'some error message from amazon' in str(excinfo.value)
|
2023-08-11 11:47:57 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@mock.patch('app.clients.email.aws_ses_stub.request')
|
|
|
|
|
def test_send_email_stub(mock_request):
|
|
|
|
|
mock_request.return_value = FakeResponse()
|
|
|
|
|
stub = AwsSesStubClient()
|
|
|
|
|
stub.init_app("fake")
|
|
|
|
|
answer = stub.send_email(
|
|
|
|
|
'fake@fake.gov',
|
|
|
|
|
'recipient@wherever.com',
|
|
|
|
|
'TestTest',
|
|
|
|
|
'TestBody')
|
|
|
|
|
print(answer)
|
|
|
|
|
assert answer == 'SomeId'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeResponse:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
|
|
t = {"MessageId": "SomeId"}
|
|
|
|
|
self.text = json.dumps(t)
|
|
|
|
|
|
|
|
|
|
def raise_for_status(self):
|
|
|
|
|
print("raised for status")
|