mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-09 15:29:50 -04:00
Add process_mmg_responses
Refactor process_firetext_responses Removed the abstract ClientResponses for firetext and mmg. There is a map for each response to handle the status codes sent by each client. Since MMG has about 20 different status code, none of which seem to be a pending state (unlike firetext that has 3 status one for pending - network delay). For MMG status codes, look for 00 as successful, everything else is assumed to be a failure.
This commit is contained in:
@@ -80,7 +80,8 @@ def init_app(app):
|
|||||||
no_auth_req = [
|
no_auth_req = [
|
||||||
url_for('status.show_status'),
|
url_for('status.show_status'),
|
||||||
url_for('notifications.process_ses_response'),
|
url_for('notifications.process_ses_response'),
|
||||||
url_for('notifications.process_firetext_response')
|
url_for('notifications.process_firetext_response'),
|
||||||
|
url_for('notifications.process_mmg_response')
|
||||||
]
|
]
|
||||||
if request.path not in no_auth_req:
|
if request.path not in no_auth_req:
|
||||||
from app.authentication import auth
|
from app.authentication import auth
|
||||||
|
|||||||
@@ -313,9 +313,6 @@ def send_email(service_id, notification_id, subject, from_address, encrypted_not
|
|||||||
@notify_celery.task(name='send-sms-code')
|
@notify_celery.task(name='send-sms-code')
|
||||||
def send_sms_code(encrypted_verification):
|
def send_sms_code(encrypted_verification):
|
||||||
verification_message = encryption.decrypt(encrypted_verification)
|
verification_message = encryption.decrypt(encrypted_verification)
|
||||||
# send_sms_via_firetext(validate_and_format_phone_number(verification_message['to']),
|
|
||||||
# verification_message['secret_code'],
|
|
||||||
# 'send-sms-code')
|
|
||||||
try:
|
try:
|
||||||
mmg_client.send_sms(validate_and_format_phone_number(verification_message['to']),
|
mmg_client.send_sms(validate_and_format_phone_number(verification_message['to']),
|
||||||
verification_message['secret_code'],
|
verification_message['secret_code'],
|
||||||
|
|||||||
@@ -6,34 +6,34 @@ from app.clients.sms import (
|
|||||||
)
|
)
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from requests import request, RequestException, HTTPError
|
from requests import request, RequestException, HTTPError
|
||||||
from app.clients import ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE
|
from app.clients import STATISTICS_DELIVERED, STATISTICS_FAILURE
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
firetext_responses = {
|
||||||
|
'0': {
|
||||||
|
"message": 'Delivered',
|
||||||
|
"notification_statistics_status": STATISTICS_DELIVERED,
|
||||||
|
"success": True,
|
||||||
|
"notification_status": 'delivered'
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
"message": 'Declined',
|
||||||
|
"success": False,
|
||||||
|
"notification_statistics_status": STATISTICS_FAILURE,
|
||||||
|
"notification_status": 'failed'
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
"message": 'Undelivered (Pending with Network)',
|
||||||
|
"success": False,
|
||||||
|
"notification_statistics_status": None,
|
||||||
|
"notification_status": 'sent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FiretextResponses(ClientResponse):
|
|
||||||
def __init__(self):
|
def get_firetext_responses(status):
|
||||||
ClientResponse.__init__(self)
|
return firetext_responses[status]
|
||||||
self.__response_model__ = {
|
|
||||||
'0': {
|
|
||||||
"message": 'Delivered',
|
|
||||||
"notification_statistics_status": STATISTICS_DELIVERED,
|
|
||||||
"success": True,
|
|
||||||
"notification_status": 'delivered'
|
|
||||||
},
|
|
||||||
'1': {
|
|
||||||
"message": 'Declined',
|
|
||||||
"success": False,
|
|
||||||
"notification_statistics_status": STATISTICS_FAILURE,
|
|
||||||
"notification_status": 'failed'
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
"message": 'Undelivered (Pending with Network)',
|
|
||||||
"success": False,
|
|
||||||
"notification_statistics_status": None,
|
|
||||||
"notification_status": 'sent'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class FiretextClientException(SmsClientException):
|
class FiretextClientException(SmsClientException):
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from monotonic import monotonic
|
from monotonic import monotonic
|
||||||
from requests import (request, RequestException, HTTPError)
|
from requests import (request, RequestException, HTTPError)
|
||||||
from app.clients import (ClientResponse, STATISTICS_DELIVERED, STATISTICS_FAILURE)
|
from app.clients import (STATISTICS_DELIVERED, STATISTICS_FAILURE)
|
||||||
from app.clients.sms import (SmsClient, SmsClientException)
|
from app.clients.sms import (SmsClient, SmsClientException)
|
||||||
|
|
||||||
|
mmg_response_map = {
|
||||||
|
'00': {
|
||||||
|
"message": 'Delivered',
|
||||||
|
"notification_statistics_status": STATISTICS_DELIVERED,
|
||||||
|
"success": True,
|
||||||
|
"notification_status": 'delivered'
|
||||||
|
},
|
||||||
|
'default': {
|
||||||
|
"message": 'Declined',
|
||||||
|
"success": False,
|
||||||
|
"notification_statistics_status": STATISTICS_FAILURE,
|
||||||
|
"notification_status": 'failed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FiretextResponses(ClientResponse):
|
|
||||||
def __init__(self):
|
def get_mmg_responses(status):
|
||||||
ClientResponse.__init__(self)
|
return mmg_response_map.get(status, mmg_response_map.get('default'))
|
||||||
self.__response_model__ = {
|
|
||||||
'0': {
|
|
||||||
"message": 'Delivered',
|
|
||||||
"notification_statistics_status": STATISTICS_DELIVERED,
|
|
||||||
"success": True,
|
|
||||||
"notification_status": 'delivered'
|
|
||||||
},
|
|
||||||
'1': {
|
|
||||||
"message": 'Declined',
|
|
||||||
"success": False,
|
|
||||||
"notification_statistics_status": STATISTICS_FAILURE,
|
|
||||||
"notification_status": 'failed'
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
"message": 'Undelivered (Pending with Network)',
|
|
||||||
"success": False,
|
|
||||||
"notification_statistics_status": None,
|
|
||||||
"notification_status": 'sent'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MMGClientException(SmsClientException):
|
class MMGClientException(SmsClientException):
|
||||||
|
|||||||
71
app/notifications/process_client_response.py
Normal file
71
app/notifications/process_client_response.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import uuid
|
||||||
|
from flask import current_app
|
||||||
|
from app.dao import notifications_dao
|
||||||
|
from app.clients.sms.firetext import get_firetext_responses
|
||||||
|
from app.clients.sms.mmg import get_mmg_responses
|
||||||
|
|
||||||
|
sms_response_mapper = {'MMG': get_mmg_responses,
|
||||||
|
'Firetext': get_firetext_responses
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_callback_data(data, fields, client_name):
|
||||||
|
errors = []
|
||||||
|
for f in fields:
|
||||||
|
if len(data.get(f, '')) <= 0:
|
||||||
|
error = "{} callback failed: {} missing".format(client_name, f)
|
||||||
|
errors.append(error)
|
||||||
|
return errors if len(errors) > 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
def process_sms_client_response(status, reference, client_name):
|
||||||
|
success = None
|
||||||
|
errors = None
|
||||||
|
# validate reference
|
||||||
|
if reference == 'send-sms-code':
|
||||||
|
success = "{} callback succeeded: send-sms-code".format(client_name)
|
||||||
|
return success, errors
|
||||||
|
|
||||||
|
try:
|
||||||
|
uuid.UUID(reference, version=4)
|
||||||
|
except ValueError:
|
||||||
|
message = "{} callback with invalid reference {}".format(client_name, reference)
|
||||||
|
return success, message
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_parser = sms_response_mapper[client_name]
|
||||||
|
except KeyError:
|
||||||
|
return success, 'unknown sms client: {}'.format(client_name)
|
||||||
|
|
||||||
|
# validate status
|
||||||
|
try:
|
||||||
|
response_dict = response_parser(status)
|
||||||
|
current_app.logger.info('{} callback return status of {} for reference: {}'.format(client_name,
|
||||||
|
status, reference))
|
||||||
|
except KeyError:
|
||||||
|
msg = "{} callback failed: status {} not found.".format(client_name, status)
|
||||||
|
return success, msg
|
||||||
|
|
||||||
|
notification_status = response_dict['notification_status']
|
||||||
|
notification_statistics_status = response_dict['notification_statistics_status']
|
||||||
|
notification_status_message = response_dict['message']
|
||||||
|
notification_success = response_dict['success']
|
||||||
|
|
||||||
|
# record stats
|
||||||
|
update_success = notifications_dao.update_notification_status_by_id(reference,
|
||||||
|
notification_status,
|
||||||
|
notification_statistics_status)
|
||||||
|
if update_success == 0:
|
||||||
|
status_error = "{} callback failed: notification {} not found. Status {}".format(client_name,
|
||||||
|
reference,
|
||||||
|
notification_status_message)
|
||||||
|
return success, status_error
|
||||||
|
|
||||||
|
if not notification_success:
|
||||||
|
current_app.logger.info(
|
||||||
|
"{} delivery failed: notification {} has error found. Status {}".format(client_name,
|
||||||
|
reference,
|
||||||
|
notification_status_message))
|
||||||
|
|
||||||
|
success = "{} callback succeeded. reference {} updated".format(client_name, reference)
|
||||||
|
return success, errors
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
@@ -11,7 +10,6 @@ from flask import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from utils.template import Template
|
from utils.template import Template
|
||||||
from app.clients.sms.firetext import FiretextResponses
|
|
||||||
from app.clients.email.aws_ses import AwsSesResponses
|
from app.clients.email.aws_ses import AwsSesResponses
|
||||||
from app import api_user, encryption, create_uuid, DATETIME_FORMAT, DATE_FORMAT
|
from app import api_user, encryption, create_uuid, DATETIME_FORMAT, DATE_FORMAT
|
||||||
from app.authentication.auth import require_admin
|
from app.authentication.auth import require_admin
|
||||||
@@ -20,7 +18,10 @@ from app.dao import (
|
|||||||
services_dao,
|
services_dao,
|
||||||
notifications_dao
|
notifications_dao
|
||||||
)
|
)
|
||||||
|
from app.notifications.process_client_response import (
|
||||||
|
validate_callback_data,
|
||||||
|
process_sms_client_response
|
||||||
|
)
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
email_notification_schema,
|
email_notification_schema,
|
||||||
sms_template_notification_schema,
|
sms_template_notification_schema,
|
||||||
@@ -35,9 +36,7 @@ notifications = Blueprint('notifications', __name__)
|
|||||||
from app.errors import register_errors
|
from app.errors import register_errors
|
||||||
|
|
||||||
register_errors(notifications)
|
register_errors(notifications)
|
||||||
|
|
||||||
aws_response = AwsSesResponses()
|
aws_response = AwsSesResponses()
|
||||||
firetext_response = FiretextResponses()
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/email/ses', methods=['POST'])
|
@notifications.route('/notifications/email/ses', methods=['POST'])
|
||||||
@@ -145,90 +144,43 @@ def is_not_a_notification(source):
|
|||||||
|
|
||||||
@notifications.route('/notifications/sms/mmg', methods=['POST'])
|
@notifications.route('/notifications/sms/mmg', methods=['POST'])
|
||||||
def process_mmg_response():
|
def process_mmg_response():
|
||||||
current_app.logger.info('MMG client callback form{}'.format(request.form))
|
client_name = 'MMG'
|
||||||
status, error1 = _get_from_response(form=request.form, field='status', client_name='MMG')
|
data = json.loads(request.data)
|
||||||
reference, error2 = _get_from_response(form=request.form, field='reference', client_name='MMG')
|
validation_errors = validate_callback_data(data=data,
|
||||||
errors = [error1, error2]
|
fields=['status', 'CID'],
|
||||||
errors.remove(None)
|
client_name=client_name)
|
||||||
if len(errors) > 0:
|
if validation_errors:
|
||||||
|
[current_app.logger.info(e) for e in validation_errors]
|
||||||
|
return jsonify(result='error', message=validation_errors), 400
|
||||||
|
|
||||||
|
success, errors = process_sms_client_response(status=data.get('status'),
|
||||||
|
reference=data.get('CID'),
|
||||||
|
client_name='MMG')
|
||||||
|
if errors:
|
||||||
|
[current_app.logger.info(e) for e in errors]
|
||||||
return jsonify(result='error', message=errors), 400
|
return jsonify(result='error', message=errors), 400
|
||||||
if reference == 'send-sms-code':
|
|
||||||
return jsonify(result="success", message="MMG callback succeeded: send-sms-code"), 200
|
|
||||||
|
|
||||||
|
|
||||||
def _get_from_response(form, field, client_name):
|
|
||||||
error = None
|
|
||||||
form_field = None
|
|
||||||
if len(form.get(field, '')) <= 0:
|
|
||||||
current_app.logger.info(
|
|
||||||
"{} callback failed: {} missing".format(client_name, field)
|
|
||||||
)
|
|
||||||
error = "{} callback failed: {} missing".format(client_name, field)
|
|
||||||
else:
|
else:
|
||||||
form_field = form[field]
|
return jsonify(result='success', message=success), 200
|
||||||
return form_field, error
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/sms/firetext', methods=['POST'])
|
@notifications.route('/notifications/sms/firetext', methods=['POST'])
|
||||||
def process_firetext_response():
|
def process_firetext_response():
|
||||||
status, error1 = _get_from_response(form=request.form, field='status', client_name='Firetext')
|
client_name = 'Firetext'
|
||||||
reference, error2 = _get_from_response(form=request.form, field='reference', client_name='Firetext')
|
validation_errors = validate_callback_data(data=request.form,
|
||||||
errors = [error1, error2]
|
fields=['status', 'reference'],
|
||||||
errors = errors.remove(None)
|
client_name=client_name)
|
||||||
|
if validation_errors:
|
||||||
|
current_app.logger.info(validation_errors)
|
||||||
|
return jsonify(result='error', message=validation_errors), 400
|
||||||
|
|
||||||
if len(errors) > 0:
|
success, errors = process_sms_client_response(status=request.form.get('status'),
|
||||||
|
reference=request.form.get('reference'),
|
||||||
|
client_name=client_name)
|
||||||
|
if errors:
|
||||||
|
[current_app.logger.info(e) for e in errors]
|
||||||
return jsonify(result='error', message=errors), 400
|
return jsonify(result='error', message=errors), 400
|
||||||
|
else:
|
||||||
if reference == 'send-sms-code':
|
return jsonify(result='success', message=success), 200
|
||||||
return jsonify(result="success", message="Firetext callback succeeded: send-sms-code"), 200
|
|
||||||
|
|
||||||
try:
|
|
||||||
uuid.UUID(reference, version=4)
|
|
||||||
except ValueError:
|
|
||||||
current_app.logger.info(
|
|
||||||
"Firetext callback with invalid reference {}".format(reference)
|
|
||||||
)
|
|
||||||
return jsonify(
|
|
||||||
result="error", message="Firetext callback with invalid reference {}".format(reference)
|
|
||||||
), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
firetext_response.response_code_to_object(status)
|
|
||||||
except KeyError:
|
|
||||||
current_app.logger.info(
|
|
||||||
"Firetext callback failed: status {} not found.".format(status)
|
|
||||||
)
|
|
||||||
return jsonify(result="error", message="Firetext callback failed: status {} not found.".format(status)), 400
|
|
||||||
|
|
||||||
notification_status = firetext_response.response_code_to_notification_status(status)
|
|
||||||
notification_statistics_status = firetext_response.response_code_to_notification_statistics_status(status)
|
|
||||||
|
|
||||||
if notifications_dao.update_notification_status_by_id(
|
|
||||||
reference,
|
|
||||||
notification_status,
|
|
||||||
notification_statistics_status
|
|
||||||
) == 0:
|
|
||||||
current_app.logger.info(
|
|
||||||
"Firetext callback failed: notification {} not found. Status {}".format(reference, status)
|
|
||||||
)
|
|
||||||
return jsonify(
|
|
||||||
result="error",
|
|
||||||
message="Firetext callback failed: notification {} not found. Status {}".format(
|
|
||||||
reference,
|
|
||||||
firetext_response.response_code_to_message(status)
|
|
||||||
)
|
|
||||||
), 404
|
|
||||||
|
|
||||||
if not firetext_response.response_code_to_notification_success(status):
|
|
||||||
current_app.logger.info(
|
|
||||||
"Firetext delivery failed: notification {} has error found. Status {}".format(
|
|
||||||
reference,
|
|
||||||
FiretextResponses().response_code_to_message(status)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return jsonify(
|
|
||||||
result="success", message="Firetext callback succeeded. reference {} updated".format(reference)
|
|
||||||
), 200
|
|
||||||
|
|
||||||
|
|
||||||
@notifications.route('/notifications/<uuid:notification_id>', methods=['GET'])
|
@notifications.route('/notifications/<uuid:notification_id>', methods=['GET'])
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ class AnyStringWith(str):
|
|||||||
def firetext_error():
|
def firetext_error():
|
||||||
return {'code': 0, 'description': 'error'}
|
return {'code': 0, 'description': 'error'}
|
||||||
|
|
||||||
|
mmg_error = {'Error': '40', 'Description': 'error'}
|
||||||
|
|
||||||
|
|
||||||
def test_should_call_delete_successful_notifications_in_task(notify_api, mocker):
|
def test_should_call_delete_successful_notifications_in_task(notify_api, mocker):
|
||||||
mocker.patch('app.celery.tasks.delete_successful_notifications_created_more_than_a_day_ago')
|
mocker.patch('app.celery.tasks.delete_successful_notifications_created_more_than_a_day_ago')
|
||||||
@@ -667,7 +669,7 @@ def test_should_throw_mmg_client_exception(mocker):
|
|||||||
'secret_code': '12345'}
|
'secret_code': '12345'}
|
||||||
|
|
||||||
encrypted_notification = encryption.encrypt(notification)
|
encrypted_notification = encryption.encrypt(notification)
|
||||||
mocker.patch('app.mmg_client.send_sms', side_effect=MMGClientException(firetext_error()))
|
mocker.patch('app.mmg_client.send_sms', side_effect=MMGClientException(mmg_error))
|
||||||
send_sms_code(encrypted_notification)
|
send_sms_code(encrypted_notification)
|
||||||
mmg_client.send_sms.assert_called_once_with(format_phone_number(validate_phone_number(notification['to'])),
|
mmg_client.send_sms.assert_called_once_with(format_phone_number(validate_phone_number(notification['to'])),
|
||||||
notification['secret_code'],
|
notification['secret_code'],
|
||||||
|
|||||||
@@ -1,32 +1,33 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from app.clients.sms import firetext
|
from app.clients.sms.firetext import get_firetext_responses
|
||||||
|
|
||||||
responses = firetext.FiretextResponses()
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_correct_details_for_delivery():
|
def test_should_return_correct_details_for_delivery():
|
||||||
assert responses.response_code_to_message('0') == 'Delivered'
|
response_dict = get_firetext_responses('0')
|
||||||
assert responses.response_code_to_notification_status('0') == 'delivered'
|
assert response_dict['message'] == 'Delivered'
|
||||||
assert responses.response_code_to_notification_statistics_status('0') == 'delivered'
|
assert response_dict['notification_status'] == 'delivered'
|
||||||
assert responses.response_code_to_notification_success('0')
|
assert response_dict['notification_statistics_status'] == 'delivered'
|
||||||
|
assert response_dict['success']
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_correct_details_for_bounced():
|
def test_should_return_correct_details_for_bounced():
|
||||||
assert responses.response_code_to_message('1') == 'Declined'
|
response_dict = get_firetext_responses('1')
|
||||||
assert responses.response_code_to_notification_status('1') == 'failed'
|
assert response_dict['message'] == 'Declined'
|
||||||
assert responses.response_code_to_notification_statistics_status('1') == 'failure'
|
assert response_dict['notification_status'] == 'failed'
|
||||||
assert not responses.response_code_to_notification_success('1')
|
assert response_dict['notification_statistics_status'] == 'failure'
|
||||||
|
assert not response_dict['success']
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_correct_details_for_complaint():
|
def test_should_return_correct_details_for_complaint():
|
||||||
assert responses.response_code_to_message('2') == 'Undelivered (Pending with Network)'
|
response_dict = get_firetext_responses('2')
|
||||||
assert responses.response_code_to_notification_status('2') == 'sent'
|
assert response_dict['message'] == 'Undelivered (Pending with Network)'
|
||||||
assert not responses.response_code_to_notification_statistics_status('2')
|
assert response_dict['notification_status'] == 'sent'
|
||||||
assert not responses.response_code_to_notification_success('2')
|
assert not response_dict['notification_statistics_status']
|
||||||
|
assert not response_dict['success']
|
||||||
|
|
||||||
|
|
||||||
def test_should_be_none_if_unrecognised_status_code():
|
def test_should_be_none_if_unrecognised_status_code():
|
||||||
with pytest.raises(KeyError) as e:
|
with pytest.raises(KeyError) as e:
|
||||||
responses.response_code_to_object('99')
|
get_firetext_responses('99')
|
||||||
assert '99' in str(e.value)
|
assert '99' in str(e.value)
|
||||||
|
|||||||
25
tests/app/clients/test_mmg.py
Normal file
25
tests/app/clients/test_mmg.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from app.clients.sms.mmg import get_mmg_responses
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_return_correct_details_for_delivery():
|
||||||
|
response_dict = get_mmg_responses('00')
|
||||||
|
assert response_dict['message'] == 'Delivered'
|
||||||
|
assert response_dict['notification_status'] == 'delivered'
|
||||||
|
assert response_dict['notification_statistics_status'] == 'delivered'
|
||||||
|
assert response_dict['success']
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_return_correct_details_for_bounced():
|
||||||
|
response_dict = get_mmg_responses('50')
|
||||||
|
assert response_dict['message'] == 'Declined'
|
||||||
|
assert response_dict['notification_status'] == 'failed'
|
||||||
|
assert response_dict['notification_statistics_status'] == 'failure'
|
||||||
|
assert not response_dict['success']
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_be_none_if_unrecognised_status_code():
|
||||||
|
response_dict = get_mmg_responses('blah')
|
||||||
|
assert response_dict['message'] == 'Declined'
|
||||||
|
assert response_dict['notification_status'] == 'failed'
|
||||||
|
assert response_dict['notification_statistics_status'] == 'failure'
|
||||||
|
assert not response_dict['success']
|
||||||
91
tests/app/notifications/test_process_client_response.py
Normal file
91
tests/app/notifications/test_process_client_response.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from app.models import NotificationStatistics
|
||||||
|
from app.notifications.process_client_response import (
|
||||||
|
validate_callback_data,
|
||||||
|
process_sms_client_response
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_callback_data_returns_none_when_valid():
|
||||||
|
form = {'status': 'good',
|
||||||
|
'reference': 'send-sms-code'}
|
||||||
|
fields = ['status', 'reference']
|
||||||
|
client_name = 'sms client'
|
||||||
|
|
||||||
|
assert validate_callback_data(form, fields, client_name) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_callback_data_return_errors_when_fields_are_empty():
|
||||||
|
form = {'monkey': 'good'}
|
||||||
|
fields = ['status', 'cid']
|
||||||
|
client_name = 'sms client'
|
||||||
|
|
||||||
|
errors = validate_callback_data(form, fields, client_name)
|
||||||
|
assert len(errors) == 2
|
||||||
|
assert "{} callback failed: {} missing".format(client_name, 'status') in errors
|
||||||
|
assert "{} callback failed: {} missing".format(client_name, 'cid') in errors
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_return_success_for_send_sms_code_reference():
|
||||||
|
success, error = process_sms_client_response(status='000', reference='send-sms-code', client_name='sms-client')
|
||||||
|
assert success == "{} callback succeeded: send-sms-code".format('sms-client')
|
||||||
|
assert error is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_returns_error_bad_reference():
|
||||||
|
success, error = process_sms_client_response(status='000', reference='something-bad', client_name='sms-client')
|
||||||
|
assert success is None
|
||||||
|
assert error == "{} callback with invalid reference {}".format('sms-client', 'something-bad')
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_returns_error_for_unknown_sms_client():
|
||||||
|
success, error = process_sms_client_response(status='000', reference=str(uuid.uuid4()), client_name='sms-client')
|
||||||
|
assert success is None
|
||||||
|
assert error == 'unknown sms client: {}'.format('sms-client')
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_returns_error_for_unknown_status():
|
||||||
|
success, error = process_sms_client_response(status='000', reference=str(uuid.uuid4()), client_name='Firetext')
|
||||||
|
assert success is None
|
||||||
|
assert error == "{} callback failed: status {} not found.".format('Firetext', '000')
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_updates_notification_stats_for_valid_request(notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_notification):
|
||||||
|
stats = NotificationStatistics.query.all()
|
||||||
|
assert len(stats) == 1
|
||||||
|
assert stats[0].sms_requested == 1
|
||||||
|
assert stats[0].sms_delivered == 0
|
||||||
|
assert stats[0].sms_error == 0
|
||||||
|
success, error = process_sms_client_response(status='0', reference=str(sample_notification.id),
|
||||||
|
client_name='Firetext')
|
||||||
|
assert error is None
|
||||||
|
assert success == "{} callback succeeded. reference {} updated".format('Firetext', sample_notification.id)
|
||||||
|
stats = NotificationStatistics.query.all()
|
||||||
|
assert len(stats) == 1
|
||||||
|
assert stats[0].sms_requested == 1
|
||||||
|
assert stats[0].sms_delivered == 1
|
||||||
|
assert stats[0].sms_error == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_sms_response_updates_notification_stats_for_valid_request_with_failed_status(notify_api,
|
||||||
|
notify_db,
|
||||||
|
notify_db_session,
|
||||||
|
sample_notification):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
stats = NotificationStatistics.query.all()
|
||||||
|
assert len(stats) == 1
|
||||||
|
assert stats[0].sms_requested == 1
|
||||||
|
assert stats[0].sms_delivered == 0
|
||||||
|
assert stats[0].sms_error == 0
|
||||||
|
success, error = process_sms_client_response(status='1', reference=str(sample_notification.id),
|
||||||
|
client_name='Firetext')
|
||||||
|
assert success == "{} callback succeeded. reference {} updated".format('Firetext', sample_notification.id)
|
||||||
|
assert error is None
|
||||||
|
stats = NotificationStatistics.query.all()
|
||||||
|
assert len(stats) == 1
|
||||||
|
assert stats[0].sms_requested == 1
|
||||||
|
assert stats[0].sms_delivered == 0
|
||||||
|
assert stats[0].sms_error == 1
|
||||||
@@ -15,42 +15,6 @@ from app.dao.notifications_dao import get_notification_by_id, dao_get_notificati
|
|||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
|
||||||
def test_get_status_name_from_response_return_status():
|
|
||||||
from app.notifications.rest import _get_from_response
|
|
||||||
form = {'status': 'good'}
|
|
||||||
client_name = 'sms client'
|
|
||||||
status, error = _get_from_response(form, 'status', client_name)
|
|
||||||
expected = 'good'
|
|
||||||
assert expected == status
|
|
||||||
assert error is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_status_name_returns_error():
|
|
||||||
from app.notifications.rest import _get_from_response
|
|
||||||
form = {'status': '',
|
|
||||||
'another': 'some'}
|
|
||||||
client_name = 'sms client'
|
|
||||||
errors = []
|
|
||||||
status, error1 = _get_from_response(form, 'reference', client_name)
|
|
||||||
expected = "{} callback failed: reference missing".format(client_name)
|
|
||||||
assert expected == error1
|
|
||||||
assert status is None
|
|
||||||
errors.append(error1)
|
|
||||||
status, error2 = _get_from_response(form, 'status', client_name)
|
|
||||||
errors.append(error2)
|
|
||||||
another, error3 = _get_from_response(form, 'another', client_name)
|
|
||||||
if error3:
|
|
||||||
errors.append(error3)
|
|
||||||
assert "sms client callback failed: status missing" == error2
|
|
||||||
assert len(errors) == 2
|
|
||||||
assert error1 in errors
|
|
||||||
assert error2 in errors
|
|
||||||
assert error3 not in errors
|
|
||||||
err = [error1, error2, error3]
|
|
||||||
err = filter(None, err)
|
|
||||||
assert len(list(err)) == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_notification_by_id(notify_api, sample_notification):
|
def test_get_notification_by_id(notify_api, sample_notification):
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
with notify_api.test_client() as client:
|
with notify_api.test_client() as client:
|
||||||
@@ -1066,7 +1030,7 @@ def test_firetext_callback_should_return_400_if_empty_reference(notify_api):
|
|||||||
json_resp = json.loads(response.get_data(as_text=True))
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert json_resp['result'] == 'error'
|
assert json_resp['result'] == 'error'
|
||||||
assert json_resp['message'] == 'Firetext callback failed: reference missing'
|
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||||
|
|
||||||
|
|
||||||
def test_firetext_callback_should_return_400_if_no_reference(notify_api):
|
def test_firetext_callback_should_return_400_if_no_reference(notify_api):
|
||||||
@@ -1080,7 +1044,7 @@ def test_firetext_callback_should_return_400_if_no_reference(notify_api):
|
|||||||
json_resp = json.loads(response.get_data(as_text=True))
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert json_resp['result'] == 'error'
|
assert json_resp['result'] == 'error'
|
||||||
assert json_resp['message'] == 'Firetext callback failed: reference missing'
|
assert json_resp['message'] == ['Firetext callback failed: reference missing']
|
||||||
|
|
||||||
|
|
||||||
def test_firetext_callback_should_return_200_if_send_sms_reference(notify_api):
|
def test_firetext_callback_should_return_200_if_send_sms_reference(notify_api):
|
||||||
@@ -1102,13 +1066,13 @@ def test_firetext_callback_should_return_400_if_no_status(notify_api):
|
|||||||
with notify_api.test_client() as client:
|
with notify_api.test_client() as client:
|
||||||
response = client.post(
|
response = client.post(
|
||||||
path='/notifications/sms/firetext',
|
path='/notifications/sms/firetext',
|
||||||
data='mobile=441234123123&time=2016-03-10 14:17:00',
|
data='mobile=441234123123&time=2016-03-10 14:17:00&reference=send-sms-code',
|
||||||
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
|
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
|
||||||
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert json_resp['result'] == 'error'
|
assert json_resp['result'] == 'error'
|
||||||
assert json_resp['message'] == 'Firetext callback failed: status missing'
|
assert json_resp['message'] == ['Firetext callback failed: status missing']
|
||||||
|
|
||||||
|
|
||||||
def test_firetext_callback_should_return_400_if_unknown_status(notify_api):
|
def test_firetext_callback_should_return_400_if_unknown_status(notify_api):
|
||||||
@@ -1151,7 +1115,7 @@ def test_firetext_callback_should_return_404_if_cannot_find_notification_id(noti
|
|||||||
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
|
headers=[('Content-Type', 'application/x-www-form-urlencoded')])
|
||||||
|
|
||||||
json_resp = json.loads(response.get_data(as_text=True))
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
assert response.status_code == 404
|
assert response.status_code == 400
|
||||||
assert json_resp['result'] == 'error'
|
assert json_resp['result'] == 'error'
|
||||||
assert json_resp['message'] == 'Firetext callback failed: notification {} not found. Status {}'.format(
|
assert json_resp['message'] == 'Firetext callback failed: notification {} not found. Status {}'.format(
|
||||||
missing_notification_id,
|
missing_notification_id,
|
||||||
@@ -1272,6 +1236,60 @@ def test_firetext_callback_should_update_multiple_notification_status_sent(notif
|
|||||||
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].sms_error == 0
|
assert dao_get_notification_statistics_for_service(notification1.service_id)[0].sms_error == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_mmg_response_return_200_when_cid_is_send_sms_code(notify_api):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
data = json.dumps({"reference": "10100164",
|
||||||
|
"CID": "send-sms-code",
|
||||||
|
"MSISDN": "447775349060",
|
||||||
|
"status": "00",
|
||||||
|
"deliverytime": "2016-04-05 16:01:07"})
|
||||||
|
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
response = client.post(path='notifications/sms/mmg',
|
||||||
|
data=data,
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 200
|
||||||
|
json_data = json.loads(response.data)
|
||||||
|
assert json_data['result'] == 'success'
|
||||||
|
assert json_data['message'] == 'MMG callback succeeded: send-sms-code'
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_mmg_response_returns_200_when_cid_is_valid_notification_id(notify_api, sample_notification):
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
data = json.dumps({"reference": "mmg_reference",
|
||||||
|
"CID": str(sample_notification.id),
|
||||||
|
"MSISDN": "447777349060",
|
||||||
|
"status": "00",
|
||||||
|
"deliverytime": "2016-04-05 16:01:07"})
|
||||||
|
|
||||||
|
response = client.post(path='notifications/sms/mmg',
|
||||||
|
data=data,
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 200
|
||||||
|
json_data = json.loads(response.data)
|
||||||
|
assert json_data['result'] == 'success'
|
||||||
|
assert json_data['message'] == 'MMG callback succeeded. reference {} updated'.format(sample_notification.id)
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_mmg_response_returns_400_for_malformed_data(notify_api):
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
data = json.dumps({"reference": "mmg_reference",
|
||||||
|
"monkey": 'random thing',
|
||||||
|
"MSISDN": "447777349060",
|
||||||
|
"no_status": "00",
|
||||||
|
"deliverytime": "2016-04-05 16:01:07"})
|
||||||
|
|
||||||
|
response = client.post(path='notifications/sms/mmg',
|
||||||
|
data=data,
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 400
|
||||||
|
json_data = json.loads(response.data)
|
||||||
|
assert json_data['result'] == 'error'
|
||||||
|
assert len(json_data['message']) == 2
|
||||||
|
assert "{} callback failed: {} missing".format('MMG', 'status') in json_data['message']
|
||||||
|
assert "{} callback failed: {} missing".format('MMG', 'CID') in json_data['message']
|
||||||
|
|
||||||
|
|
||||||
def test_ses_callback_should_not_need_auth(notify_api):
|
def test_ses_callback_should_not_need_auth(notify_api):
|
||||||
with notify_api.test_request_context():
|
with notify_api.test_request_context():
|
||||||
with notify_api.test_client() as client:
|
with notify_api.test_client() as client:
|
||||||
|
|||||||
Reference in New Issue
Block a user