def test_ses_callback_should_not_set_status_once_status_is_delivered(notify_api,
                                                                     notify_db,
                                                                     notify_db_session,
                                                                     sample_email_template):
    with notify_api.test_request_context():
        with notify_api.test_client() as client:
            notification = create_sample_notification(
                notify_db,
                notify_db_session,
                template=sample_email_template,
                reference='ref',
                status='delivered',
                sent_at=datetime.utcnow()
            )

            assert get_notification_by_id(notification.id).status == 'delivered'

            response = client.post(
                path='/notifications/email/ses',
                data=ses_soft_bounce_callback(),
                headers=[('Content-Type', 'text/plain; charset=UTF-8')]
            )
            json_resp = json.loads(response.get_data(as_text=True))
            assert response.status_code == 404
            assert json_resp['result'] == 'error'
            assert json_resp['message'] == 'SES callback failed: notification either not found or already updated from sending. Status temporary-failure'  # noqa
            assert get_notification_by_id(notification.id).status == 'delivered'
def test_ses_callback_should_set_status_to_permanent_failure(notify_api,
                                                             notify_db,
                                                             notify_db_session,
                                                             sample_email_template):
    with notify_api.test_request_context():
        with notify_api.test_client() as client:
            notification = create_sample_notification(
                notify_db,
                notify_db_session,
                template=sample_email_template,
                reference='ref',
                status='sending',
                sent_at=datetime.utcnow()
            )

            assert get_notification_by_id(notification.id).status == 'sending'

            response = client.post(
                path='/notifications/email/ses',
                data=ses_hard_bounce_callback(),
                headers=[('Content-Type', 'text/plain; charset=UTF-8')]
            )
            json_resp = json.loads(response.get_data(as_text=True))
            assert response.status_code == 200
            assert json_resp['result'] == 'success'
            assert json_resp['message'] == 'SES callback succeeded'
            assert get_notification_by_id(notification.id).status == 'permanent-failure'
Beispiel #3
0
def test_firetext_callback_should_update_notification_status_failed(
        notify_db, notify_db_session, client, sample_template, mocker
):
    mocker.patch('app.statsd_client.incr')
    mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )
    notification = create_sample_notification(
        notify_db,
        notify_db_session,
        template=sample_template,
        reference='ref',
        status='sending',
        sent_at=datetime.utcnow())

    original = get_notification_by_id(notification.id)
    assert original.status == 'sending'

    data = 'mobile=61412345678&status=1&time=2016-03-10 14:17:00&reference={}'.format(
        notification.id)
    response = firetext_post(client, data)

    json_resp = json.loads(response.get_data(as_text=True))
    assert response.status_code == 200
    assert json_resp['result'] == 'success'
    assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format(
        notification.id
    )
    assert get_notification_by_id(notification.id).status == 'permanent-failure'
Beispiel #4
0
def test_ses_callback_does_not_call_send_delivery_status_if_no_db_entry(
        client,
        notify_db,
        notify_db_session,
        sample_email_template,
        mocker):
    with freeze_time('2001-01-01T12:00:00'):

        send_mock = mocker.patch(
            'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
        )
        notification = create_sample_notification(
            notify_db,
            notify_db_session,
            template=sample_email_template,
            reference='ref',
            status='sending',
            sent_at=datetime.utcnow()
        )

        assert get_notification_by_id(notification.id).status == 'sending'

        assert process_ses_results_task(ses_notification_callback(reference='ref'))
        assert get_notification_by_id(notification.id).status == 'delivered'

        send_mock.assert_not_called()
Beispiel #5
0
def test_ses_callback_should_update_notification_status(
        notify_db, notify_db_session, sample_email_template, mocker):
    with freeze_time("2001-01-01T12:00:00"):
        mocker.patch("app.statsd_client.incr")
        mocker.patch("app.statsd_client.timing_with_dates")
        send_mock = mocker.patch(
            "app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async"
        )
        notification = create_sample_notification(
            notify_db,
            notify_db_session,
            template=sample_email_template,
            reference="ref",
            status="sending",
            sent_at=datetime.utcnow(),
        )
        callback_api = create_service_callback_api(
            service=sample_email_template.service,
            url="https://original_url.com")
        assert get_notification_by_id(notification.id).status == "sending"

        assert process_ses_results(ses_notification_callback(reference="ref"))
        notification = get_notification_by_id(notification.id)
        assert notification.status == "delivered"
        assert notification.provider_response is None
        statsd_client.timing_with_dates.assert_any_call(
            "callback.ses.elapsed-time", datetime.utcnow(),
            notification.sent_at)
        statsd_client.incr.assert_any_call("callback.ses.delivered")
        updated_notification = Notification.query.get(notification.id)
        encrypted_data = create_delivery_status_callback_data(
            updated_notification, callback_api)
        send_mock.assert_called_once_with(
            [str(notification.id), encrypted_data], queue="service-callbacks")
def test_process_sns_results_calls_service_callback(sample_template,
                                                    notify_db_session,
                                                    notify_db, mocker):
    with freeze_time('2021-01-01T12:00:00'):
        mocker.patch('app.statsd_client.incr')
        mocker.patch('app.statsd_client.timing_with_dates')
        send_mock = mocker.patch(
            'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
        )
        notification = create_sample_notification(notify_db,
                                                  notify_db_session,
                                                  template=sample_template,
                                                  reference='ref',
                                                  status=NOTIFICATION_SENT,
                                                  sent_by='sns',
                                                  sent_at=datetime.utcnow())
        callback_api = create_service_callback_api(
            service=sample_template.service, url="https://example.com")
        assert get_notification_by_id(
            notification.id).status == NOTIFICATION_SENT

        assert process_sns_results(sns_success_callback(reference='ref'))
        assert get_notification_by_id(
            notification.id).status == NOTIFICATION_DELIVERED
        statsd_client.timing_with_dates.assert_any_call(
            "callback.sns.elapsed-time", datetime.utcnow(),
            notification.sent_at)
        statsd_client.incr.assert_any_call("callback.sns.delivered")
        updated_notification = get_notification_by_id(notification.id)
        encrypted_data = create_delivery_status_callback_data(
            updated_notification, callback_api)
        send_mock.assert_called_once_with(
            [str(notification.id), encrypted_data], queue="service-callbacks")
def test_process_sns_results_failed(
    sample_template,
    notify_db,
    notify_db_session,
    mocker,
    provider_response,
    expected_status,
    should_log_warning,
):
    mock_logger = mocker.patch(
        'app.celery.process_sns_receipts_tasks.current_app.logger.info')
    mock_warning_logger = mocker.patch(
        'app.celery.process_sns_receipts_tasks.current_app.logger.warning')

    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              template=sample_template,
                                              reference='ref',
                                              status=NOTIFICATION_SENT,
                                              sent_by='sns',
                                              sent_at=datetime.utcnow())
    assert get_notification_by_id(notification.id).status == NOTIFICATION_SENT
    assert process_sns_results(
        sns_failed_callback(provider_response=provider_response,
                            reference='ref'))
    assert get_notification_by_id(notification.id).status == expected_status

    mock_logger.assert_called_once_with((
        f'SNS delivery failed: notification id {notification.id} and reference ref has error found. '
        f'Provider response: {provider_response}'))

    assert mock_warning_logger.call_count == int(should_log_warning)
Beispiel #8
0
def test_ses_callback_should_update_notification_status(
        client, notify_db_session, sample_email_template, mocker):
    with freeze_time('2001-01-01T12:00:00'):
        mocker.patch('app.statsd_client.incr')
        mocker.patch('app.statsd_client.timing_with_dates')
        send_mock = mocker.patch(
            'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
        )
        notification = create_notification(
            template=sample_email_template,
            status='sending',
            reference='ref',
        )
        callback_api = create_service_callback_api(
            service=sample_email_template.service,
            url="https://original_url.com")
        assert get_notification_by_id(notification.id).status == 'sending'

        assert process_ses_results(ses_notification_callback(reference='ref'))
        assert get_notification_by_id(notification.id).status == 'delivered'
        statsd_client.timing_with_dates.assert_any_call(
            "callback.ses.elapsed-time", datetime.utcnow(),
            notification.sent_at)
        statsd_client.incr.assert_any_call("callback.ses.delivered")
        updated_notification = Notification.query.get(notification.id)
        encrypted_data = create_delivery_status_callback_data(
            updated_notification, callback_api)
        send_mock.assert_called_once_with(
            [str(notification.id), encrypted_data], queue="service-callbacks")
def test_firetext_callback_should_update_notification_status(
        client, mocker, sample_notification):
    mocker.patch('app.statsd_client.incr')
    send_mock = mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )
    sample_notification.status = 'sending'

    original = get_notification_by_id(sample_notification.id)
    assert original.status == 'sending'
    data = 'mobile=441234123123&status=0&time=2016-03-10 14:17:00&reference={}'.format(
        sample_notification.id)
    response = firetext_post(client, data)

    json_resp = json.loads(response.get_data(as_text=True))
    assert response.status_code == 200
    assert json_resp['result'] == 'success'
    assert json_resp[
        'message'] == 'Firetext callback succeeded. reference {} updated'.format(
            sample_notification.id)
    updated = get_notification_by_id(sample_notification.id)
    assert updated.status == 'delivered'
    assert get_notification_by_id(sample_notification.id).status == 'delivered'
    assert send_mock.called_once_with([sample_notification.id],
                                      queue="notify-internal-tasks")
def test_firetext_callback_should_update_notification_status_failed(
        notify_db, notify_db_session, notify_api, sample_template, mocker
):
    with notify_api.test_request_context():
        with notify_api.test_client() as client:
            mocker.patch('app.statsd_client.incr')

            notification = create_sample_notification(
                notify_db,
                notify_db_session,
                template=sample_template,
                reference='ref',
                status='sending',
                sent_at=datetime.utcnow())

            original = get_notification_by_id(notification.id)
            assert original.status == 'sending'

            response = client.post(
                path='/notifications/sms/firetext',
                data='mobile=441234123123&status=1&time=2016-03-10 14:17:00&reference={}'.format(
                    notification.id
                ),
                headers=[('Content-Type', 'application/x-www-form-urlencoded')])

            json_resp = json.loads(response.get_data(as_text=True))
            assert response.status_code == 200
            assert json_resp['result'] == 'success'
            assert json_resp['message'] == 'Firetext callback succeeded. reference {} updated'.format(
                notification.id
            )
            assert get_notification_by_id(notification.id).status == 'permanent-failure'
Beispiel #11
0
def test_ses_callback_should_set_status_to_permanent_failure(
    notify_db,
    notify_db_session,
    sample_email_template,
    mocker,
    bounce_subtype,
    provider_response,
):
    send_mock = mocker.patch(
        "app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async"
    )
    notification = create_sample_notification(
        notify_db,
        notify_db_session,
        template=sample_email_template,
        reference="ref",
        status="sending",
        sent_at=datetime.utcnow(),
    )
    create_service_callback_api(service=sample_email_template.service,
                                url="https://original_url.com")

    assert get_notification_by_id(notification.id).status == "sending"
    assert process_ses_results(
        ses_hard_bounce_callback(reference="ref",
                                 bounce_subtype=bounce_subtype))

    notification = get_notification_by_id(notification.id)
    assert notification.status == "permanent-failure"
    assert notification.provider_response == provider_response
    assert send_mock.called
def test_process_mmg_response_status_4_updates_notification_with_temporary_failed(
        notify_db, notify_db_session, client, mocker):
    mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )
    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              status='sending',
                                              sent_at=datetime.utcnow())

    data = json.dumps({
        "reference": "mmg_reference",
        "CID": str(notification.id),
        "MSISDN": "447777349060",
        "status": 4
    })

    response = mmg_post(client, data)
    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(
            notification.id)
    assert get_notification_by_id(
        notification.id).status == 'temporary-failure'
Beispiel #13
0
def cancel_notification_for_service(service_id, notification_id):
    notification = notifications_dao.get_notification_by_id(
        notification_id, service_id)

    if not notification:
        raise InvalidRequest('Notification not found', status_code=404)
    elif notification.notification_type != LETTER_TYPE:
        raise InvalidRequest(
            'Notification cannot be cancelled - only letters can be cancelled',
            status_code=400)
    elif not letter_can_be_cancelled(notification.status,
                                     notification.created_at):
        print_day = letter_print_day(notification.created_at)

        raise InvalidRequest(
            "It’s too late to cancel this letter. Printing started {} at 5.30pm"
            .format(print_day),
            status_code=400)

    updated_notification = notifications_dao.update_notification_status_by_id(
        notification_id,
        NOTIFICATION_CANCELLED,
    )

    return jsonify(
        notification_with_template_schema.dump(updated_notification).data), 200
def deliver_email(self, notification_id):
    try:
        current_app.logger.info(
            "Start sending email for notification id: {}".format(
                notification_id))
        notification = notifications_dao.get_notification_by_id(
            notification_id)
        if not notification:
            raise NoResultFound()
        send_to_providers.send_email_to_provider(notification)
    except InvalidEmailError as e:
        current_app.logger.exception(e)
        update_notification_status_by_id(notification_id, 'technical-failure')
    except Exception as e:
        try:
            current_app.logger.exception(
                "RETRY: Email notification {} failed".format(notification_id))
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. " \
                      "The task send_email_to_provider failed for notification {}. " \
                      "Notification has been updated to technical-failure".format(notification_id)
            update_notification_status_by_id(notification_id,
                                             NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
def test_should_persist_notification(notify_api, sample_template,
                                     sample_email_template,
                                     template_type,
                                     fake_uuid, mocker):
    with notify_api.test_request_context(), notify_api.test_client() as client:
        mocked = mocker.patch('app.celery.provider_tasks.deliver_{}.apply_async'.format(template_type))
        mocker.patch('app.dao.notifications_dao.create_uuid', return_value=fake_uuid)
        template = sample_template if template_type == 'sms' else sample_email_template
        to = sample_template.service.created_by.mobile_number if template_type == 'sms' \
            else sample_email_template.service.created_by.email_address
        data = {
            'to': to,
            'template': template.id
        }
        api_key = ApiKey(
            service=template.service,
            name='team_key',
            created_by=template.created_by,
            key_type=KEY_TYPE_TEAM)
        save_model_api_key(api_key)
        auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))

        response = client.post(
            path='/notifications/{}'.format(template_type),
            data=json.dumps(data),
            headers=[('Content-Type', 'application/json'), ('Authorization', 'Bearer {}'.format(auth_header))])

        mocked.assert_called_once_with([fake_uuid], queue='send-{}'.format(template_type))
        assert response.status_code == 201

        notification = notifications_dao.get_notification_by_id(fake_uuid)
        assert notification.to == to
        assert notification.template_id == template.id
        assert notification.notification_type == template_type
def test_should_call_send_sms_response_task_if_research_mode(notify_db, sample_service, sample_notification, mocker,
                                                             research_mode, key_type):
    mocker.patch('app.mmg_client.send_sms')
    mocker.patch('app.mmg_client.get_name', return_value="mmg")
    mocker.patch('app.celery.research_mode_tasks.send_sms_response.apply_async')

    if research_mode:
        sample_service.research_mode = True
        notify_db.session.add(sample_service)
        notify_db.session.commit()

    sample_notification.key_type = key_type

    send_to_providers.send_sms_to_provider(
        sample_notification
    )
    assert not mmg_client.send_sms.called
    send_to_providers.send_sms_response.apply_async.assert_called_once_with(
        ('mmg', str(sample_notification.id), sample_notification.to), queue='research-mode'
    )

    persisted_notification = notifications_dao.get_notification_by_id(sample_notification.id)
    assert persisted_notification.to == sample_notification.to
    assert persisted_notification.template_id == sample_notification.template_id
    assert persisted_notification.status == 'sending'
    assert persisted_notification.sent_at <= datetime.utcnow()
    assert persisted_notification.sent_by == 'mmg'
    assert not persisted_notification.personalisation
def test_send_sms_should_use_template_version_from_notification_not_latest(
        sample_template, mocker):
    db_notification = create_notification(
        template=sample_template,
        to_field='+447234123123',
        status='created',
        reply_to_text=sample_template.service.get_default_sms_sender())

    mocker.patch('app.mmg_client.send_sms')

    version_on_notification = sample_template.version

    # Change the template
    from app.dao.templates_dao import dao_update_template, dao_get_template_by_id
    sample_template.content = sample_template.content + " another version of the template"
    dao_update_template(sample_template)
    t = dao_get_template_by_id(sample_template.id)
    assert t.version > version_on_notification

    send_to_providers.send_sms_to_provider(db_notification)

    mmg_client.send_sms.assert_called_once_with(
        to=validate_and_format_phone_number("+447234123123"),
        content="Sample service: This is a template:\nwith a newline",
        reference=str(db_notification.id),
        sender=current_app.config['FROM_NUMBER'])

    persisted_notification = notifications_dao.get_notification_by_id(
        db_notification.id)
    assert persisted_notification.to == db_notification.to
    assert persisted_notification.template_id == sample_template.id
    assert persisted_notification.template_version == version_on_notification
    assert persisted_notification.template_version != sample_template.version
    assert persisted_notification.status == 'sending'
    assert not persisted_notification.personalisation
def get_pdf_for_notification(notification_id):
    _data = {"notification_id": notification_id}
    validate(_data, notification_by_id)
    notification = notifications_dao.get_notification_by_id(
        notification_id, authenticated_service.id, _raise=True)

    if notification.notification_type != LETTER_TYPE:
        raise BadRequestError(message="Notification is not a letter")

    if notification.status == NOTIFICATION_VIRUS_SCAN_FAILED:
        raise BadRequestError(message='Document did not pass the virus scan')

    if notification.status == NOTIFICATION_TECHNICAL_FAILURE:
        raise BadRequestError(
            message='PDF not available for letters in status {}'.format(
                notification.status))

    if notification.status == NOTIFICATION_PENDING_VIRUS_CHECK:
        raise PDFNotReadyError()

    try:
        pdf_data, metadata = get_letter_pdf_and_metadata(notification)
    except Exception:
        raise PDFNotReadyError()

    return send_file(filename_or_fp=BytesIO(pdf_data),
                     mimetype='application/pdf')
def test_should_persist_notification(client, sample_template,
                                     sample_email_template, fake_uuid, mocker,
                                     template_type, queue_name):
    mocked = mocker.patch(
        'app.celery.provider_tasks.deliver_{}.apply_async'.format(
            template_type))
    mocker.patch('app.notifications.process_notifications.uuid.uuid4',
                 return_value=fake_uuid)

    template = sample_template if template_type == SMS_TYPE else sample_email_template
    to = sample_template.service.created_by.mobile_number if template_type == SMS_TYPE \
        else sample_email_template.service.created_by.email_address
    data = {'to': to, 'template': template.id}
    api_key = ApiKey(service=template.service,
                     name='team_key',
                     created_by=template.created_by,
                     key_type=KEY_TYPE_TEAM)
    save_model_api_key(api_key)
    auth_header = create_jwt_token(secret=api_key.secret,
                                   client_id=str(api_key.service_id))

    response = client.post(path='/notifications/{}'.format(template_type),
                           data=json.dumps(data),
                           headers=[('Content-Type', 'application/json'),
                                    ('Authorization',
                                     'Bearer {}'.format(auth_header))])

    mocked.assert_called_once_with([fake_uuid], queue=queue_name)
    assert response.status_code == 201

    notification = notifications_dao.get_notification_by_id(fake_uuid)
    assert notification.to == to
    assert notification.template_id == template.id
    assert notification.notification_type == template_type
def create_letters_pdf(self, notification_id):
    try:
        notification = get_notification_by_id(notification_id, _raise=True)
        pdf_data, billable_units = get_letters_pdf(
            notification.template,
            contact_block=notification.reply_to_text,
            filename=notification.service.letter_branding
            and notification.service.letter_branding.filename,
            values=notification.personalisation)

        upload_letter_pdf(notification, pdf_data)

        if notification.key_type != KEY_TYPE_TEST:
            notification.billable_units = billable_units
            dao_update_notification(notification)

        current_app.logger.info(
            'Letter notification reference {reference}: billable units set to {billable_units}'
            .format(reference=str(notification.reference),
                    billable_units=billable_units))

    except (RequestException, BotoClientError):
        try:
            current_app.logger.exception(
                "Letters PDF notification creation for id: {} failed".format(
                    notification_id))
            self.retry(queue=QueueNames.RETRY)
        except MaxRetriesExceededError:
            current_app.logger.error(
                "RETRY FAILED: task create_letters_pdf failed for notification {}"
                .format(notification_id), )
            update_notification_status_by_id(notification_id,
                                             'technical-failure')
def test_should_call_send_sms_response_task_if_research_mode(
        notify_db, sample_service, sample_notification, mocker, research_mode,
        key_type):
    mocker.patch('app.mmg_client.send_sms')
    mocker.patch('app.delivery.send_to_providers.send_sms_response')

    if research_mode:
        sample_service.research_mode = True
        notify_db.session.add(sample_service)
        notify_db.session.commit()

    sample_notification.key_type = key_type

    send_to_providers.send_sms_to_provider(sample_notification)
    assert not mmg_client.send_sms.called

    app.delivery.send_to_providers.send_sms_response.assert_called_once_with(
        'mmg', str(sample_notification.id), sample_notification.to)

    persisted_notification = notifications_dao.get_notification_by_id(
        sample_notification.id)
    assert persisted_notification.to == sample_notification.to
    assert persisted_notification.template_id == sample_notification.template_id
    assert persisted_notification.status == 'sending'
    assert persisted_notification.sent_at <= datetime.utcnow()
    assert persisted_notification.sent_by == 'mmg'
    assert not persisted_notification.personalisation
Beispiel #22
0
def deliver_email(self, notification_id):
    try:
        current_app.logger.info(
            "Start sending email for notification id: {}".format(
                notification_id))
        notification = notifications_dao.get_notification_by_id(
            notification_id)
        if not notification:
            raise NoResultFound()
        send_to_providers.send_email_to_provider(notification)
    except EmailClientNonRetryableException as e:
        current_app.logger.exception(
            f"Email notification {notification_id} failed: {e}")
        update_notification_status_by_id(notification_id, 'technical-failure')
    except Exception as e:
        try:
            if isinstance(e, AwsSesClientThrottlingSendRateException):
                current_app.logger.warning(
                    f"RETRY: Email notification {notification_id} was rate limited by SES"
                )
            else:
                current_app.logger.exception(
                    f"RETRY: Email notification {notification_id} failed")

            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. " \
                      "The task send_email_to_provider failed for notification {}. " \
                      "Notification has been updated to technical-failure".format(notification_id)
            update_notification_status_by_id(notification_id,
                                             NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
def test_should_delete_notification_and_return_error_if_sqs_fails(
        client, sample_email_template, sample_template, fake_uuid, mocker,
        template_type, queue_name):
    mocked = mocker.patch(
        'app.celery.provider_tasks.deliver_{}.apply_async'.format(
            template_type),
        side_effect=Exception("failed to talk to SQS"))
    mocker.patch('app.notifications.process_notifications.uuid.uuid4',
                 return_value=fake_uuid)

    template = sample_template if template_type == SMS_TYPE else sample_email_template
    to = sample_template.service.created_by.mobile_number if template_type == SMS_TYPE \
        else sample_email_template.service.created_by.email_address
    data = {'to': to, 'template': template.id}
    api_key = ApiKey(service=template.service,
                     name='team_key',
                     created_by=template.created_by,
                     key_type=KEY_TYPE_TEAM)
    save_model_api_key(api_key)
    auth_header = create_jwt_token(secret=api_key.secret,
                                   client_id=str(api_key.service_id))

    with pytest.raises(Exception) as e:
        client.post(path='/notifications/{}'.format(template_type),
                    data=json.dumps(data),
                    headers=[('Content-Type', 'application/json'),
                             ('Authorization', 'Bearer {}'.format(auth_header))
                             ])
    assert str(e.value) == 'failed to talk to SQS'

    mocked.assert_called_once_with([fake_uuid], queue=queue_name)
    assert not notifications_dao.get_notification_by_id(fake_uuid)
    assert not NotificationHistory.query.get(fake_uuid)
Beispiel #24
0
def deliver_sms(self, notification_id):
    try:
        current_app.logger.info(
            "Start sending SMS for notification id: {}".format(
                notification_id))
        notification = notifications_dao.get_notification_by_id(
            notification_id)
        if not notification:
            raise NoResultFound()
        send_to_providers.send_sms_to_provider(notification)
    except Exception as e:
        if isinstance(e, SmsClientResponseException):
            current_app.logger.warning(
                "SMS notification delivery for id: {} failed".format(
                    notification_id))
        else:
            current_app.logger.exception(
                "SMS notification delivery for id: {} failed".format(
                    notification_id))

        try:
            if self.request.retries == 0:
                self.retry(queue=QueueNames.RETRY, countdown=0)
            else:
                self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. The task send_sms_to_provider failed for notification {}. " \
                      "Notification has been updated to technical-failure".format(notification_id)
            update_notification_status_by_id(notification_id,
                                             NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
def test_should_call_send_sms_response_task_if_research_mode(
    notify_db, sample_service, sample_notification, mocker, research_mode, key_type
):
    reference = str(uuid.uuid4())
    mocker.patch("app.aws_sns_client.send_sms")
    mocker.patch("app.delivery.send_to_providers.send_sms_response", return_value=reference)

    if research_mode:
        sample_service.research_mode = True
        notify_db.session.add(sample_service)
        notify_db.session.commit()

    sample_notification.key_type = key_type

    send_to_providers.send_sms_to_provider(sample_notification)
    assert not aws_sns_client.send_sms.called

    app.delivery.send_to_providers.send_sms_response.assert_called_once_with("sns", sample_notification.to)

    persisted_notification = notifications_dao.get_notification_by_id(sample_notification.id)
    assert persisted_notification.to == sample_notification.to
    assert persisted_notification.template_id == sample_notification.template_id
    assert persisted_notification.status == "sent"
    assert persisted_notification.sent_at <= datetime.utcnow()
    assert persisted_notification.sent_by == "sns"
    assert persisted_notification.reference == reference
    assert not persisted_notification.personalisation
Beispiel #26
0
def test_ses_callback_should_set_status_to_temporary_failure(
        client, notify_db_session, sample_email_template, mocker):
    send_mock = mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )
    notification = create_notification(
        template=sample_email_template,
        status='sending',
        reference='ref',
    )
    create_service_callback_api(service=notification.service,
                                url="https://original_url.com")
    assert get_notification_by_id(notification.id).status == 'sending'
    assert process_ses_results(ses_soft_bounce_callback(reference='ref'))
    assert get_notification_by_id(
        notification.id).status == 'temporary-failure'
    assert send_mock.called
def test_ses_callback_should_not_set_status_once_status_is_delivered(
        sample_email_template):
    notification = create_notification(
        sample_email_template,
        status='delivered',
    )

    assert get_notification_by_id(notification.id).status == 'delivered'
Beispiel #28
0
def test_ses_callback_should_update_notification_status_when_receiving_new_delivery_receipt(
        sample_email_template, mocker):
    notification = create_notification(template=sample_email_template,
                                       reference="ref",
                                       status="delivered")

    assert process_ses_results(ses_hard_bounce_callback(reference="ref"))
    assert get_notification_by_id(
        notification.id).status == "permanent-failure"
def lookup_va_profile_id(self, notification_id):
    current_app.logger.info(
        f"Retrieving VA Profile ID from MPI for notification {notification_id}"
    )
    notification = notifications_dao.get_notification_by_id(notification_id)

    try:
        va_profile_id = mpi_client.get_va_profile_id(notification)
        notification.recipient_identifiers.set(
            RecipientIdentifier(notification_id=notification.id,
                                id_type=IdentifierType.VA_PROFILE_ID.value,
                                id_value=va_profile_id))
        notifications_dao.dao_update_notification(notification)
        current_app.logger.info(
            f"Successfully updated notification {notification_id} with VA PROFILE ID {va_profile_id}"
        )

    except MpiRetryableException as e:
        current_app.logger.warning(
            f"Received {str(e)} for notification {notification_id}.")
        try:
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. " \
                      f"The task lookup_va_profile_id failed for notification {notification_id}. " \
                      "Notification has been updated to technical-failure"

            notifications_dao.update_notification_status_by_id(
                notification_id,
                NOTIFICATION_TECHNICAL_FAILURE,
                status_reason=e.failure_reason)
            raise NotificationTechnicalFailureException(message) from e

    except (BeneficiaryDeceasedException, IdentifierNotFound,
            MultipleActiveVaProfileIdsException) as e:
        message = f"{e.__class__.__name__} - {str(e)}: " \
                  f"Can't proceed after querying MPI for VA Profile ID for {notification_id}. " \
                  "Stopping execution of following tasks. Notification has been updated to permanent-failure."
        current_app.logger.warning(message)
        self.request.chain = None
        notifications_dao.update_notification_status_by_id(
            notification_id,
            NOTIFICATION_PERMANENT_FAILURE,
            status_reason=e.failure_reason)

    except Exception as e:
        message = f"Failed to retrieve VA Profile ID from MPI for notification: {notification_id} " \
                  "Notification has been updated to technical-failure"
        current_app.logger.exception(message)

        status_reason = e.failure_reason if hasattr(
            e, 'failure_reason') else 'Unknown error from MPI'
        notifications_dao.update_notification_status_by_id(
            notification_id,
            NOTIFICATION_TECHNICAL_FAILURE,
            status_reason=status_reason)
        raise NotificationTechnicalFailureException(message) from e
Beispiel #30
0
def test_ses_callback_should_not_update_notification_status_if_already_delivered(sample_email_template, mocker):
    mock_dup = mocker.patch('app.notifications.notifications_ses_callback.notifications_dao._duplicate_update_warning')
    mock_upd = mocker.patch('app.notifications.notifications_ses_callback.notifications_dao._update_notification_status')
    notification = create_notification(template=sample_email_template, reference='ref', status='delivered')

    assert process_ses_results_task(ses_notification_callback(reference='ref')) is None
    assert get_notification_by_id(notification.id).status == 'delivered'

    mock_dup.assert_called_once_with(notification, 'delivered')
    assert mock_upd.call_count == 0
def test_ses_callback_should_set_status_to_permanent_failure(
        client, notify_db, notify_db_session, sample_email_template, mocker):
    send_mock = mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )
    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              template=sample_email_template,
                                              reference='ref',
                                              status='sending',
                                              sent_at=datetime.utcnow())
    create_service_callback_api(service=sample_email_template.service,
                                url="https://original_url.com")

    assert get_notification_by_id(notification.id).status == 'sending'
    assert process_ses_results_task(ses_hard_bounce_callback(reference='ref'))
    assert get_notification_by_id(
        notification.id).status == 'permanent-failure'
    assert send_mock.called
Beispiel #32
0
def test_ses_callback_should_not_set_status_once_status_is_delivered(
        client, notify_db, notify_db_session, sample_email_template, mocker):
    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              template=sample_email_template,
                                              reference='ref',
                                              status='delivered',
                                              sent_at=datetime.utcnow())

    assert get_notification_by_id(notification.id).status == 'delivered'
Beispiel #33
0
def test_sap_callback_should_update_notification_status(
        notify_db, notify_db_session, client, sample_sap_oauth2_client,
        sample_email_template, mocker):
    mocker.patch('app.statsd_client.incr')
    send_mock = mocker.patch(
        'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
    )

    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              template=sample_email_template,
                                              reference='ref',
                                              status='sending',
                                              sent_at=datetime.utcnow())

    original = get_notification_by_id(notification.id)

    assert original.status == 'sending'

    data = {
        "messageId": "1234",
        "status": "DELIVERED",
        "recipient": "+61412345678",
        "message": "message"
    }

    response = sap_post(client, sample_sap_oauth2_client, notification.id,
                        data)
    json_resp = json.loads(response.get_data(as_text=True))

    assert response.status_code == 200
    assert json_resp['result'] == 'success'
    assert json_resp[
        'message'] == 'sap callback succeeded. reference {} updated'.format(
            notification.id)

    updated = get_notification_by_id(notification.id)

    assert updated.status == 'delivered'
    assert get_notification_by_id(notification.id).status == 'delivered'
    assert send_mock.called_once_with([notification.id],
                                      queue="notify-internal-tasks")
def test_process_sns_results_delivered(sample_template, notify_db,
                                       notify_db_session, mocker):
    mock_logger = mocker.patch(
        'app.celery.process_sns_receipts_tasks.current_app.logger.info')

    notification = create_sample_notification(notify_db,
                                              notify_db_session,
                                              template=sample_template,
                                              reference='ref',
                                              status=NOTIFICATION_SENT,
                                              sent_by='sns',
                                              sent_at=datetime.utcnow())
    assert get_notification_by_id(notification.id).status == NOTIFICATION_SENT
    assert process_sns_results(sns_success_callback(reference='ref'))
    assert get_notification_by_id(
        notification.id).status == NOTIFICATION_DELIVERED

    mock_logger.assert_called_once_with(
        f'SNS callback return status of delivered for notification: {notification.id}'
    )
def test_should_not_update_notification_if_research_mode_on_exception(sample_service, sample_notification, mocker):
    mock_send_sms = mocker.patch("app.delivery.send_to_providers.send_sms_response", side_effect=Exception())
    sample_service.research_mode = True
    sample_notification.billable_units = 0

    with pytest.raises(Exception):
        send_to_providers.send_sms_to_provider(sample_notification)

    persisted_notification = notifications_dao.get_notification_by_id(sample_notification.id)
    assert persisted_notification.billable_units == 0
    assert mock_send_sms.called
Beispiel #36
0
def send_sms(
    self, service_id, notification_id, encrypted_notification, created_at, api_key_id=None, key_type=KEY_TYPE_NORMAL
):
    notification = encryption.decrypt(encrypted_notification)
    service = dao_fetch_service_by_id(service_id)

    if not service_allowed_to_send_to(notification["to"], service, key_type):
        current_app.logger.info("SMS {} failed as restricted service".format(notification_id))
        return

    try:
        saved_notification = persist_notification(
            template_id=notification["template"],
            template_version=notification["template_version"],
            recipient=notification["to"],
            service_id=service.id,
            personalisation=notification.get("personalisation"),
            notification_type=SMS_TYPE,
            api_key_id=api_key_id,
            key_type=key_type,
            created_at=created_at,
            job_id=notification.get("job", None),
            job_row_number=notification.get("row_number", None),
            notification_id=notification_id,
        )

        provider_tasks.deliver_sms.apply_async(
            [str(saved_notification.id)], queue="send-sms" if not service.research_mode else "research-mode"
        )

        current_app.logger.info(
            "SMS {} created at {} for job {}".format(saved_notification.id, created_at, notification.get("job", None))
        )

    except SQLAlchemyError as e:
        if not get_notification_by_id(notification_id):
            # Sometimes, SQS plays the same message twice. We should be able to catch an IntegrityError, but it seems
            # SQLAlchemy is throwing a FlushError. So we check if the notification id already exists then do not
            # send to the retry queue.
            current_app.logger.error(
                "RETRY: send_sms notification for job {} row number {} and notification id {}".format(
                    notification.get("job", None), notification.get("row_number", None), notification_id
                )
            )
            current_app.logger.exception(e)
            try:
                raise self.retry(queue="retry", exc=e)
            except self.MaxRetriesExceededError:
                current_app.logger.error(
                    "RETRY FAILED: send_sms notification for job {} row number {} and notification id {}".format(
                        notification.get("job", None), notification.get("row_number", None), notification_id
                    )
                )
                current_app.logger.exception(e)
def test_ses_callback_should_update_notification_status(
        notify_api,
        notify_db,
        notify_db_session,
        sample_email_template,
        mocker):
    with notify_api.test_request_context():
        with notify_api.test_client() as client:
            with freeze_time('2001-01-01T12:00:00'):
                mocker.patch('app.statsd_client.incr')
                mocker.patch('app.statsd_client.timing_with_dates')
                notification = create_sample_notification(
                    notify_db,
                    notify_db_session,
                    template=sample_email_template,
                    reference='ref',
                    status='sending',
                    sent_at=datetime.utcnow()
                )

                assert get_notification_by_id(notification.id).status == 'sending'

                response = client.post(
                    path='/notifications/email/ses',
                    data=ses_notification_callback(),
                    headers=[('Content-Type', 'text/plain; charset=UTF-8')]
                )
                json_resp = json.loads(response.get_data(as_text=True))
                assert response.status_code == 200
                assert json_resp['result'] == 'success'
                assert json_resp['message'] == 'SES callback succeeded'
                assert get_notification_by_id(notification.id).status == 'delivered'
                app.statsd_client.timing_with_dates.assert_any_call(
                    "callback.ses.elapsed-time", datetime.utcnow(), notification.sent_at
                )
                app.statsd_client.incr.assert_any_call("callback.ses.delivered")
def test_should_not_set_billable_units_if_research_mode(notify_db, sample_service, sample_notification, mocker):
    mocker.patch('app.mmg_client.send_sms')
    mocker.patch('app.mmg_client.get_name', return_value="mmg")
    mocker.patch('app.celery.research_mode_tasks.send_sms_response.apply_async')

    sample_service.research_mode = True
    notify_db.session.add(sample_service)
    notify_db.session.commit()

    send_to_providers.send_sms_to_provider(
        sample_notification
    )

    persisted_notification = notifications_dao.get_notification_by_id(sample_notification.id)
    assert persisted_notification.billable_units == 0
def test_should_set_billable_units_to_zero_in_research_mode_or_test_key(
        notify_db, sample_service, sample_notification, mocker, research_mode, key_type):

    mocker.patch('app.mmg_client.send_sms')
    mocker.patch('app.mmg_client.get_name', return_value="mmg")
    mocker.patch('app.celery.research_mode_tasks.send_sms_response.apply_async')
    if research_mode:
        sample_service.research_mode = True
        notify_db.session.add(sample_service)
        notify_db.session.commit()
    sample_notification.key_type = key_type

    send_to_providers.send_sms_to_provider(
        sample_notification
    )

    assert notifications_dao.get_notification_by_id(sample_notification.id).billable_units == 0
def deliver_sms(self, notification_id):
    try:
        notification = notifications_dao.get_notification_by_id(notification_id)
        if not notification:
            raise NoResultFound()
        send_to_providers.send_sms_to_provider(notification)
    except Exception as e:
        try:
            current_app.logger.exception(
                "RETRY: SMS notification {} failed".format(notification_id)
            )
            self.retry(queue="retry", countdown=retry_iteration_to_delay(self.request.retries))
        except self.MaxRetriesExceededError:
            current_app.logger.exception(
                "RETRY FAILED: task send_sms_to_provider failed for notification {}".format(notification_id),
            )
            update_notification_status_by_id(notification_id, 'technical-failure')
Beispiel #41
0
def send_notification_to_provider(notification_id):
    notification = notifications_dao.get_notification_by_id(notification_id)
    if not notification:
        return jsonify({"result": "error", "message": "No result found"}), 404

    if notification.notification_type == EMAIL_TYPE:
        send_response(
            send_to_providers.send_email_to_provider,
            provider_tasks.deliver_email,
            notification,
            'send-email')
    else:
        send_response(
            send_to_providers.send_sms_to_provider,
            provider_tasks.deliver_sms,
            notification,
            'send-sms')
    return jsonify({}), 204
def test_process_mmg_response_status_2_updates_notification_with_permanently_failed(
    notify_db, notify_db_session, notify_api
):
    with notify_api.test_client() as client:
        notification = create_sample_notification(
            notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow()
        )
        data = json.dumps({"reference": "mmg_reference",
                           "CID": str(notification.id),
                           "MSISDN": "447777349060",
                           "status": 2})

        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(notification.id)
        assert get_notification_by_id(notification.id).status == 'permanent-failure'
def test_process_mmg_response_returns_200_when_cid_is_valid_notification_id(
        notify_db, notify_db_session, notify_api
):
    with notify_api.test_client() as client:
        notification = create_sample_notification(
            notify_db, notify_db_session, status='sending', sent_at=datetime.utcnow()
        )
        data = json.dumps({"reference": "mmg_reference",
                           "CID": str(notification.id),
                           "MSISDN": "447777349060",
                           "status": "3",
                           "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(notification.id)
        assert get_notification_by_id(notification.id).status == 'delivered'
def test_send_sms_should_use_template_version_from_notification_not_latest(
        notify_db,
        notify_db_session,
        sample_template,
        mocker):
    db_notification = sample_notification(notify_db, notify_db_session,
                                          template=sample_template, to_field='+447234123123',
                                          status='created')

    mocker.patch('app.mmg_client.send_sms')
    mocker.patch('app.mmg_client.get_name', return_value="mmg")
    version_on_notification = sample_template.version

    # Change the template
    from app.dao.templates_dao import dao_update_template, dao_get_template_by_id
    sample_template.content = sample_template.content + " another version of the template"
    dao_update_template(sample_template)
    t = dao_get_template_by_id(sample_template.id)
    assert t.version > version_on_notification

    send_to_providers.send_sms_to_provider(
        db_notification
    )

    mmg_client.send_sms.assert_called_once_with(
        to=format_phone_number(validate_phone_number("+447234123123")),
        content="Sample service: This is a template:\nwith a newline",
        reference=str(db_notification.id),
        sender=None
    )

    persisted_notification = notifications_dao.get_notification_by_id(db_notification.id)
    assert persisted_notification.to == db_notification.to
    assert persisted_notification.template_id == sample_template.id
    assert persisted_notification.template_version == version_on_notification
    assert persisted_notification.template_version != sample_template.version
    assert persisted_notification.status == 'sending'
    assert not persisted_notification.personalisation