diff --git a/app/models.py b/app/models.py index 29272c380..1bcd66a19 100644 --- a/app/models.py +++ b/app/models.py @@ -1165,6 +1165,7 @@ class InboundSms(db.Model): user_number = db.Column(db.String, nullable=False) # the end user's number, that the msg was sent from provider_date = db.Column(db.DateTime) provider_reference = db.Column(db.String) + provider = db.Column(db.String, nullable=True) _content = db.Column('content', db.String, nullable=False) @property diff --git a/app/notifications/receive_notifications.py b/app/notifications/receive_notifications.py index 0ed644904..904e78711 100644 --- a/app/notifications/receive_notifications.py +++ b/app/notifications/receive_notifications.py @@ -4,7 +4,7 @@ import iso8601 from flask import jsonify, Blueprint, current_app, request from notifications_utils.recipients import validate_and_format_phone_number -from app import statsd_client +from app import statsd_client, firetext_client, mmg_client from app.dao.services_dao import dao_fetch_services_by_sms_sender from app.dao.inbound_sms_dao import dao_create_inbound_sms from app.models import InboundSms @@ -38,7 +38,7 @@ def receive_mmg_sms(): # succesfully return 'RECEIVED', 200 - statsd_client.incr('inbound.mmg.succesful') + statsd_client.incr('inbound.mmg.successful') service = potential_services[0] @@ -78,6 +78,7 @@ def create_inbound_mmg_sms_object(service, json): provider_date=provider_date, provider_reference=json.get('ID'), content=message, + provider=mmg_client.name ) dao_create_inbound_sms(inbound) return inbound @@ -86,7 +87,35 @@ def create_inbound_mmg_sms_object(service, json): @receive_notifications_blueprint.route('/notifications/sms/receive/firetext', methods=['POST']) def receive_firetext_sms(): post_data = request.form - current_app.logger.info("Received Firetext notification form data: {}".format(post_data)) + + potential_services = dao_fetch_services_by_sms_sender(post_data['destination']) + if len(potential_services) != 1: + current_app.logger.error('Inbound number "{}" not associated with exactly one service'.format( + post_data['destination'] + )) + statsd_client.incr('inbound.firetext.failed') + return jsonify({ + "status": "ok" + }), 200 + + service = potential_services[0] + + user_number = validate_and_format_phone_number(post_data['source'], international=True) + message = post_data['message'] + timestamp = post_data['time'] + + dao_create_inbound_sms( + InboundSms( + service=service, + notify_number=service.sms_sender, + user_number=user_number, + provider_date=timestamp, + content=message, + provider=firetext_client.name + ) + ) + + statsd_client.incr('inbound.firetext.successful') return jsonify({ "status": "ok" diff --git a/migrations/versions/0092_add_inbound_provider.py b/migrations/versions/0092_add_inbound_provider.py new file mode 100644 index 000000000..f7e5f510e --- /dev/null +++ b/migrations/versions/0092_add_inbound_provider.py @@ -0,0 +1,22 @@ +"""empty message + +Revision ID: 0092_add_inbound_provider +Revises: 0091_letter_billing +Create Date: 2017-06-02 16:07:35.445423 + +""" + +# revision identifiers, used by Alembic. +revision = '0092_add_inbound_provider' +down_revision = '0091_letter_billing' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +def upgrade(): + op.add_column('inbound_sms', sa.Column('provider', sa.String(), nullable=True)) + + +def downgrade(): + op.drop_column('inbound_sms', 'provider') diff --git a/tests/app/notifications/test_receive_notification.py b/tests/app/notifications/test_receive_notification.py index 317d9452e..67949104a 100644 --- a/tests/app/notifications/test_receive_notification.py +++ b/tests/app/notifications/test_receive_notification.py @@ -1,4 +1,5 @@ from datetime import datetime +from unittest.mock import call import pytest from flask import json @@ -10,6 +11,7 @@ from app.notifications.receive_notifications import ( ) from app.models import InboundSms +from tests.app.conftest import sample_service from tests.app.db import create_service @@ -68,6 +70,7 @@ def test_create_inbound_mmg_sms_object(sample_service): assert inbound_sms.provider_reference == 'bar' assert inbound_sms._content != 'hello there 📩' assert inbound_sms.content == 'hello there 📩' + assert inbound_sms.provider == 'mmg' @pytest.mark.parametrize('notify_number', ['foo', 'baz'], ids=['two_matching_services', 'no_matching_services']) @@ -92,7 +95,11 @@ def test_receive_notification_error_if_not_single_matching_service(client, notif assert InboundSms.query.count() == 0 -def test_receive_notification_returns_received_to_firetext(client): +def test_receive_notification_returns_received_to_firetext(notify_db_session, client, mocker): + mock = mocker.patch('app.notifications.receive_notifications.statsd_client.incr') + + create_service(service_name='b', sms_sender='07111111111') + data = "source=07999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00" response = client.post( @@ -103,4 +110,74 @@ def test_receive_notification_returns_received_to_firetext(client): assert response.status_code == 200 result = json.loads(response.get_data(as_text=True)) + mock.assert_has_calls([call('inbound.firetext.successful')]) + assert result['status'] == 'ok' + + +def test_receive_notification_from_firetext_persists_message(notify_db_session, client, mocker): + mocker.patch('app.notifications.receive_notifications.statsd_client.incr') + + service = create_service(service_name='b', sms_sender='07111111111') + + data = "source=07999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00" + + response = client.post( + path='/notifications/sms/receive/firetext', + data=data, + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + + assert response.status_code == 200 + result = json.loads(response.get_data(as_text=True)) + + persisted = InboundSms.query.first() + + assert result['status'] == 'ok' + assert persisted.notify_number == '07111111111' + assert persisted.user_number == '447999999999' + assert persisted.service == service + assert persisted.content == 'this is a message' + assert persisted.provider == 'firetext' + assert persisted.provider_date == datetime(2017, 1, 1, 12, 0, 0, 0) + + +def test_receive_notification_from_firetext_persists_message_with_normalized_phone(notify_db_session, client, mocker): + mock = mocker.patch('app.notifications.receive_notifications.statsd_client.incr') + + create_service(service_name='b', sms_sender='07111111111') + + data = "source=(+44)7999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00" + + response = client.post( + path='/notifications/sms/receive/firetext', + data=data, + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + + assert response.status_code == 200 + result = json.loads(response.get_data(as_text=True)) + + persisted = InboundSms.query.first() + + assert result['status'] == 'ok' + assert persisted.user_number == '447999999999' + + +def test_returns_ok_to_firetext_if_mismatched_sms_sender(notify_db_session, client, mocker): + + mock = mocker.patch('app.notifications.receive_notifications.statsd_client.incr') + + create_service(service_name='b', sms_sender='07111111199') + + data = "source=(+44)7999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00" + + response = client.post( + path='/notifications/sms/receive/firetext', + data=data, + headers=[('Content-Type', 'application/x-www-form-urlencoded')]) + + assert response.status_code == 200 + result = json.loads(response.get_data(as_text=True)) + + assert not InboundSms.query.all() + assert result['status'] == 'ok' + mock.assert_has_calls([call('inbound.firetext.failed')])