diff --git a/app/celery/tasks.py b/app/celery/tasks.py index fcf70b54d..eb2be882f 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -211,6 +211,13 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at): restricted = True try: + + template = Template( + dao_get_template_by_id(notification['template']).__dict__, + values=notification.get('personalisation', {}), + prefix=service.name + ) + sent_at = datetime.utcnow() notification_db_object = Notification( id=notification_id, @@ -221,42 +228,35 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at): status='failed' if restricted else 'sending', created_at=datetime.strptime(created_at, DATETIME_FORMAT), sent_at=sent_at, - sent_by=client.get_name() + sent_by=client.get_name(), + content_char_count=get_character_count_of_content(template.replaced) ) - dao_create_notification(notification_db_object, TEMPLATE_TYPE_SMS) + dao_create_notification(notification_db_object, TEMPLATE_TYPE_SMS, client.get_name()) if restricted: return try: - template = Template( - dao_get_template_by_id(notification['template']).__dict__, - values=notification.get('personalisation', {}), - prefix=service.name - ) - client.send_sms( to=validate_and_format_phone_number(notification['to']), content=template.replaced, reference=str(notification_id) ) - # Record the character count of the content after placeholders included - notification_db_object.content_char_count = get_character_count_of_content(template.replaced) - dao_update_notification(notification_db_object) + except MMGClientException as e: current_app.logger.error( "SMS notification {} failed".format(notification_id) ) current_app.logger.exception(e) notification_db_object.status = 'failed' - dao_update_notification(notification_db_object) + dao_update_notification(notification_db_object) current_app.logger.info( "SMS {} created at {} sent at {}".format(notification_id, created_at, sent_at) ) except SQLAlchemyError as e: - current_app.logger.debug(e) + current_app.logger.exception(e) @notify_celery.task(name="send-email") @@ -286,7 +286,8 @@ def send_email(service_id, notification_id, from_address, encrypted_notification sent_at=sent_at, sent_by=client.get_name() ) - dao_create_notification(notification_db_object, TEMPLATE_TYPE_EMAIL) + + dao_create_notification(notification_db_object, TEMPLATE_TYPE_EMAIL, client.get_name()) if restricted: return @@ -307,13 +308,13 @@ def send_email(service_id, notification_id, from_address, encrypted_notification except AwsSesClientException as e: current_app.logger.exception(e) notification_db_object.status = 'failed' - dao_update_notification(notification_db_object) + dao_update_notification(notification_db_object) current_app.logger.info( "Email {} created at {} sent at {}".format(notification_id, created_at, sent_at) ) except SQLAlchemyError as e: - current_app.logger.debug(e) + current_app.logger.exception(e) @notify_celery.task(name='send-sms-code') diff --git a/app/clients/sms/mmg.py b/app/clients/sms/mmg.py index b28fdd4a6..271b280b4 100644 --- a/app/clients/sms/mmg.py +++ b/app/clients/sms/mmg.py @@ -43,7 +43,7 @@ class MMGClient(SmsClient): super(SmsClient, self).__init__(*args, **kwargs) self.api_key = config.get('MMG_API_KEY') self.from_number = config.get('MMG_FROM_NUMBER') - self.name = 'MMG' + self.name = 'mmg' def get_name(self): return self.name diff --git a/app/commands.py b/app/commands.py new file mode 100644 index 000000000..9e97ce28c --- /dev/null +++ b/app/commands.py @@ -0,0 +1,30 @@ +from datetime import datetime +from decimal import Decimal +from flask.ext.script import Command, Manager, Option +from app.models import PROVIDERS +from app.dao.provider_rates_dao import create_provider_rates + + +class CreateProviderRateCommand(Command): + + option_list = ( + Option('-p', '--provider_name', dest="provider_name", help='Provider name'), + Option('-c', '--cost', dest="cost", help='Cost (pence) per message including decimals'), + Option('-d', '--valid_from', dest="valid_from", help="Date (%Y-%m-%dT%H:%M:%S) valid from") + ) + + def run(self, provider_name, cost, valid_from): + if provider_name not in PROVIDERS: + raise Exception("Invalid provider name, must be one of ({})".format(', '.join(PROVIDERS))) + + try: + cost = Decimal(cost) + except: + raise Exception("Invalid cost value.") + + try: + valid_from = datetime.strptime('%Y-%m-%dT%H:%M:%S', valid_from) + except: + raise Exception("Invalid valid_from date. Use the format %Y-%m-%dT%H:%M:%S") + + create_provider_rates(provider_name, valid_from, cost) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index cef0d9b55..14964b568 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -18,7 +18,8 @@ from app.models import ( TemplateStatistics, TEMPLATE_TYPE_SMS, TEMPLATE_TYPE_EMAIL, - Template + Template, + ProviderStatistics ) from app.clients import ( @@ -74,7 +75,7 @@ def dao_get_template_statistics_for_service(service_id, limit_days=None): @transactional -def dao_create_notification(notification, notification_type): +def dao_create_notification(notification, notification_type, provider): if notification.job_id: db.session.query(Job).filter_by( id=notification.job_id @@ -108,6 +109,22 @@ def dao_create_notification(notification, notification_type): service_id=notification.service_id) db.session.add(template_stats) + update_count = db.session.query(ProviderStatistics).filter_by( + day=date.today(), + service_id=notification.service_id, + provider=provider + ).update({'unit_count': ProviderStatistics.unit_count + ( + 1 if notification_type == TEMPLATE_TYPE_EMAIL else get_sms_message_count(notification.content_char_count))}) + + if update_count == 0: + provider_stats = ProviderStatistics( + day=notification.created_at.date(), + service_id=notification.service_id, + provider=provider, + unit_count=1 if notification_type == TEMPLATE_TYPE_EMAIL else get_sms_message_count( + notification.content_char_count)) + db.session.add(provider_stats) + db.session.add(notification) diff --git a/app/dao/provider_rates_dao.py b/app/dao/provider_rates_dao.py new file mode 100644 index 000000000..e42909a8f --- /dev/null +++ b/app/dao/provider_rates_dao.py @@ -0,0 +1,9 @@ +from app.models import ProviderRates +from app import db +from app.dao.dao_utils import transactional + + +@transactional +def create_provider_rates(provider, valid_from, rate): + provider_rates = ProviderRates(provider=provider, valid_from=valid_from, rate=rate) + db.session.add(provider_rates) diff --git a/app/dao/provider_statistics_dao.py b/app/dao/provider_statistics_dao.py new file mode 100644 index 000000000..16aff7af9 --- /dev/null +++ b/app/dao/provider_statistics_dao.py @@ -0,0 +1,5 @@ +from app.models import ProviderStatistics + + +def get_provider_statistics(service, provider): + return ProviderStatistics.query.filter_by(service=service, provider=provider).one() diff --git a/app/models.py b/app/models.py index 3e1933ea9..fd6a264f5 100644 --- a/app/models.py +++ b/app/models.py @@ -167,6 +167,34 @@ class Template(db.Model): subject = db.Column(db.Text, index=False, unique=True, nullable=True) +MMG_PROVIDER = "mmg" +TWILIO_PROVIDER = "twilio" +FIRETEXT_PROVIDER = "firetext" +SES_PROVIDER = 'ses' + +PROVIDERS = [MMG_PROVIDER, TWILIO_PROVIDER, FIRETEXT_PROVIDER, SES_PROVIDER] + + +class ProviderStatistics(db.Model): + __tablename__ = 'provider_statistics' + + id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + day = db.Column(db.Date, nullable=False) + provider = db.Column(db.Enum(*PROVIDERS, name='providers'), nullable=False) + service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False) + service = db.relationship('Service', backref=db.backref('service_provider_stats', lazy='dynamic')) + unit_count = db.Column(db.BigInteger, nullable=False) + + +class ProviderRates(db.Model): + __tablename__ = 'provider_rates' + + id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + valid_from = db.Column(db.DateTime, nullable=False) + provider = db.Column(db.Enum(*PROVIDERS, name='providers'), nullable=False) + rate = db.Column(db.Numeric(), nullable=False) + + JOB_STATUS_TYPES = ['pending', 'in progress', 'finished', 'sending limits exceeded'] diff --git a/app/notifications/rest.py b/app/notifications/rest.py index 3d454489d..ec261a42d 100644 --- a/app/notifications/rest.py +++ b/app/notifications/rest.py @@ -149,7 +149,7 @@ def process_mmg_response(): success, errors = process_sms_client_response(status=str(data.get('status')), reference=data.get('CID'), - client_name='MMG') + client_name=client_name) if errors: [current_app.logger.info(e) for e in errors] return jsonify(result='error', message=errors), 400 diff --git a/application.py b/application.py index 0918ee3e1..fea2b6264 100644 --- a/application.py +++ b/application.py @@ -4,7 +4,7 @@ from __future__ import print_function import os from flask.ext.script import Manager, Server from flask.ext.migrate import Migrate, MigrateCommand -from app import (create_app, db) +from app import (create_app, db, commands) application = create_app() manager = Manager(application) @@ -13,6 +13,7 @@ manager.add_command("runserver", Server(host='0.0.0.0', port=port)) migrate = Migrate(application, db) manager.add_command('db', MigrateCommand) +manager.add_command('create_provider_rate', commands.CreateProviderRateCommand) @manager.command diff --git a/migrations/versions/0005_add_provider_stats.py b/migrations/versions/0005_add_provider_stats.py new file mode 100644 index 000000000..8db036ba6 --- /dev/null +++ b/migrations/versions/0005_add_provider_stats.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: 0005_add_provider_stats +Revises: 0003_add_service_history +Create Date: 2016-04-20 15:13:42.229197 + +""" + +# revision identifiers, used by Alembic. +revision = '0005_add_provider_stats' +down_revision = '0004_notification_stats_date' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('provider_rates', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('valid_from', sa.DateTime(), nullable=False), + sa.Column('provider', sa.Enum('mmg', 'twilio', 'firetext', 'ses', name='providers'), nullable=False), + sa.Column('rate', sa.Numeric(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('provider_statistics', + sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('day', sa.Date(), nullable=False), + sa.Column('provider', sa.Enum('mmg', 'twilio', 'firetext', 'ses', name='providers'), nullable=False), + sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False), + sa.Column('unit_count', sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_provider_statistics_service_id'), 'provider_statistics', ['service_id'], unique=False) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_provider_statistics_service_id'), table_name='provider_statistics') + op.drop_table('provider_statistics') + op.drop_table('provider_rates') + ### end Alembic commands ### diff --git a/tests/app/celery/test_tasks.py b/tests/app/celery/test_tasks.py index c814a055c..06b9c6b6a 100644 --- a/tests/app/celery/test_tasks.py +++ b/tests/app/celery/test_tasks.py @@ -285,7 +285,7 @@ def test_should_send_template_to_correct_sms_provider_and_persist(sample_templat } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms') - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") notification_id = uuid.uuid4() now = datetime.utcnow() @@ -310,7 +310,7 @@ def test_should_send_template_to_correct_sms_provider_and_persist(sample_templat assert persisted_notification.status == 'sending' assert persisted_notification.created_at == now assert persisted_notification.sent_at > now - assert persisted_notification.sent_by == 'MMG' + assert persisted_notification.sent_by == 'mmg' assert not persisted_notification.job_id @@ -321,7 +321,7 @@ def test_should_send_sms_without_personalisation(sample_template, mocker): } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms') - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") notification_id = uuid.uuid4() now = datetime.utcnow() @@ -350,7 +350,7 @@ def test_should_send_sms_if_restricted_service_and_valid_number(notify_db, notif } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms') - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") notification_id = uuid.uuid4() now = datetime.utcnow() @@ -379,7 +379,7 @@ def test_should_not_send_sms_if_restricted_service_and_invalid_number(notify_db, } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms') - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") notification_id = uuid.uuid4() now = datetime.utcnow() @@ -463,7 +463,7 @@ def test_should_send_template_to_correct_sms_provider_and_persist_with_job_id(sa } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms') - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") notification_id = uuid.uuid4() now = datetime.utcnow() @@ -486,7 +486,7 @@ def test_should_send_template_to_correct_sms_provider_and_persist_with_job_id(sa assert persisted_notification.status == 'sending' assert persisted_notification.sent_at > now assert persisted_notification.created_at == now - assert persisted_notification.sent_by == 'MMG' + assert persisted_notification.sent_by == 'mmg' def test_should_use_email_template_and_persist(sample_email_template_with_placeholders, mocker): @@ -624,7 +624,7 @@ def test_should_persist_notification_as_failed_if_sms_client_fails(sample_templa } mocker.patch('app.encryption.decrypt', return_value=notification) mocker.patch('app.mmg_client.send_sms', side_effect=MMGClientException(mmg_error)) - mocker.patch('app.mmg_client.get_name', return_value="MMG") + mocker.patch('app.mmg_client.get_name', return_value="mmg") now = datetime.utcnow() notification_id = uuid.uuid4() @@ -647,7 +647,7 @@ def test_should_persist_notification_as_failed_if_sms_client_fails(sample_templa assert persisted_notification.status == 'failed' assert persisted_notification.created_at == now assert persisted_notification.sent_at > now - assert persisted_notification.sent_by == 'MMG' + assert persisted_notification.sent_by == 'mmg' def test_should_persist_notification_as_failed_if_email_client_fails(sample_email_template, mocker): diff --git a/tests/app/conftest.py b/tests/app/conftest.py index f50fa13a1..d7fc38460 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -292,7 +292,10 @@ def sample_notification(notify_db, to_field=None, status='sending', reference=None, - created_at=datetime.utcnow()): + created_at=datetime.utcnow(), + provider_name=None, + content_char_count=160, + create=True): if service is None: service = sample_service(notify_db, notify_db_session) if template is None: @@ -302,6 +305,9 @@ def sample_notification(notify_db, notification_id = uuid.uuid4() + if provider_name is None: + provider_name = mmg_provider_name() if template.template_type == 'sms' else ses_provider_name() + if to_field: to = to_field else: @@ -316,10 +322,12 @@ def sample_notification(notify_db, 'template': template, 'status': status, 'reference': reference, - 'created_at': created_at + 'created_at': created_at, + 'content_char_count': content_char_count } notification = Notification(**data) - dao_create_notification(notification, template.template_type) + if create: + dao_create_notification(notification, template.template_type, provider_name) return notification @@ -431,3 +439,13 @@ def sample_service_permission(notify_db, @pytest.fixture(scope='function') def fake_uuid(): return "6ce466d0-fd6a-11e5-82f5-e0accb9d11a6" + + +@pytest.fixture(scope='function') +def ses_provider_name(): + return 'ses' + + +@pytest.fixture(scope='function') +def mmg_provider_name(): + return 'mmg' diff --git a/tests/app/dao/test_notification_dao.py b/tests/app/dao/test_notification_dao.py index 2331c8e77..e03ccaf2b 100644 --- a/tests/app/dao/test_notification_dao.py +++ b/tests/app/dao/test_notification_dao.py @@ -46,7 +46,7 @@ def test_should_by_able_to_update_reference_by_id(sample_notification): assert Notification.query.get(sample_notification.id).reference == 'reference' -def test_should_by_able_to_update_status_by_reference(sample_email_template): +def test_should_by_able_to_update_status_by_reference(sample_email_template, ses_provider_name): data = { 'to': '+44709123456', 'service': sample_email_template.service, @@ -57,7 +57,10 @@ def test_should_by_able_to_update_status_by_reference(sample_email_template): } notification = Notification(**data) - dao_create_notification(notification, sample_email_template.template_type) + dao_create_notification( + notification, + sample_email_template.template_type, + ses_provider_name) assert Notification.query.get(notification.id).status == "sending" update_notification_reference_by_id(notification.id, 'reference') @@ -106,7 +109,7 @@ def test_should_be_able_to_record_statistics_failure_for_sms(sample_notification ).one().sms_failed == 1 -def test_should_be_able_to_record_statistics_failure_for_email(sample_email_template): +def test_should_be_able_to_record_statistics_failure_for_email(sample_email_template, ses_provider_name): data = { 'to': '+44709123456', 'service': sample_email_template.service, @@ -117,7 +120,7 @@ def test_should_be_able_to_record_statistics_failure_for_email(sample_email_temp } notification = Notification(**data) - dao_create_notification(notification, sample_email_template.template_type) + dao_create_notification(notification, sample_email_template.template_type, ses_provider_name) update_notification_reference_by_id(notification.id, 'reference') count = update_notification_status_by_reference('reference', 'failed', 'failure') @@ -142,18 +145,19 @@ def test_should_return_zero_count_if_no_notification_with_reference(): assert update_notification_status_by_reference('something', 'delivered', 'delivered') == 0 -def test_should_be_able_to_get_statistics_for_a_service(sample_template): +def test_should_be_able_to_get_statistics_for_a_service(sample_template, mmg_provider_name): data = { 'to': '+44709123456', 'service': sample_template.service, 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) stats = dao_get_notification_statistics_for_service(sample_template.service.id) assert len(stats) == 1 @@ -168,7 +172,7 @@ def test_should_be_able_to_get_statistics_for_a_service(sample_template): assert stats[0].emails_failed == 0 -def test_should_be_able_to_get_statistics_for_a_service_for_a_day(sample_template): +def test_should_be_able_to_get_statistics_for_a_service_for_a_day(sample_template, mmg_provider_name): now = datetime.utcnow() data = { 'to': '+44709123456', @@ -176,11 +180,12 @@ def test_should_be_able_to_get_statistics_for_a_service_for_a_day(sample_templat 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': now + 'created_at': now, + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) stat = dao_get_notification_statistics_for_service_and_day( sample_template.service.id, now.date() ) @@ -194,7 +199,7 @@ def test_should_be_able_to_get_statistics_for_a_service_for_a_day(sample_templat assert stat.service_id == notification.service_id -def test_should_return_none_if_no_statistics_for_a_service_for_a_day(sample_template): +def test_should_return_none_if_no_statistics_for_a_service_for_a_day(sample_template, mmg_provider_name): now = datetime.utcnow() data = { 'to': '+44709123456', @@ -202,32 +207,34 @@ def test_should_return_none_if_no_statistics_for_a_service_for_a_day(sample_temp 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': now + 'created_at': now, + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert not dao_get_notification_statistics_for_service_and_day( sample_template.service.id, (datetime.utcnow() - timedelta(days=1)).date() ) -def test_should_be_able_to_get_all_statistics_for_a_service(sample_template): +def test_should_be_able_to_get_all_statistics_for_a_service(sample_template, mmg_provider_name): data = { 'to': '+44709123456', 'service': sample_template.service, 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) notification_2 = Notification(**data) notification_3 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_3, sample_template.template_type, mmg_provider_name) stats = dao_get_notification_statistics_for_service(sample_template.service.id) assert len(stats) == 1 @@ -235,13 +242,14 @@ def test_should_be_able_to_get_all_statistics_for_a_service(sample_template): assert stats[0].sms_requested == 3 -def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sample_template): +def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sample_template, mmg_provider_name): data = { 'to': '+44709123456', 'service': sample_template.service, 'service_id': sample_template.service.id, 'template': sample_template, - 'template_id': sample_template.id + 'template_id': sample_template.id, + 'content_char_count': 160 } today = datetime.utcnow() @@ -259,9 +267,9 @@ def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sam 'created_at': two_days_ago }) notification_3 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_3, sample_template.template_type, mmg_provider_name) stats = dao_get_notification_statistics_for_service(sample_template.service.id) assert len(stats) == 3 @@ -280,13 +288,15 @@ def test_should_be_empty_list_if_no_statistics_for_a_service(sample_service): assert len(dao_get_notification_statistics_for_service(sample_service.id)) == 0 -def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days_previous(sample_template): +def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days_previous(sample_template, + mmg_provider_name): data = { 'to': '+44709123456', 'service': sample_template.service, 'service_id': sample_template.service.id, 'template': sample_template, - 'template_id': sample_template.id + 'template_id': sample_template.id, + 'content_char_count': 160 } today = datetime.utcnow() @@ -304,9 +314,9 @@ def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days_pre 'created_at': eight_days_ago }) notification_3 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_3, sample_template.template_type, mmg_provider_name) stats = dao_get_notification_statistics_for_service_and_previous_days( sample_template.service.id, 7 @@ -320,7 +330,7 @@ def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days_pre assert stats[1].day == seven_days_ago.date() -def test_save_notification_creates_sms_and_template_stats(sample_template, sample_job): +def test_save_notification_creates_sms_and_template_stats(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 assert NotificationStatistics.query.count() == 0 assert TemplateStatistics.query.count() == 0 @@ -332,11 +342,12 @@ def test_save_notification_creates_sms_and_template_stats(sample_template, sampl 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -364,7 +375,7 @@ def test_save_notification_creates_sms_and_template_stats(sample_template, sampl assert template_stats.usage_count == 1 -def test_save_notification_and_create_email_and_template_stats(sample_email_template, sample_job): +def test_save_notification_and_create_email_and_template_stats(sample_email_template, sample_job, ses_provider_name): assert Notification.query.count() == 0 assert NotificationStatistics.query.count() == 0 @@ -377,11 +388,12 @@ def test_save_notification_and_create_email_and_template_stats(sample_email_temp 'service_id': sample_email_template.service.id, 'template': sample_email_template, 'template_id': sample_email_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_email_template.template_type) + dao_create_notification(notification, sample_email_template.template_type, ses_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -410,7 +422,7 @@ def test_save_notification_and_create_email_and_template_stats(sample_email_temp @freeze_time("2016-01-01 00:00:00.000000") -def test_save_notification_handles_midnight_properly(sample_template, sample_job): +def test_save_notification_handles_midnight_properly(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -419,11 +431,12 @@ def test_save_notification_handles_midnight_properly(sample_template, sample_job 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 @@ -435,7 +448,7 @@ def test_save_notification_handles_midnight_properly(sample_template, sample_job @freeze_time("2016-01-01 23:59:59.999999") -def test_save_notification_handles_just_before_midnight_properly(sample_template, sample_job): +def test_save_notification_handles_just_before_midnight_properly(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -444,11 +457,12 @@ def test_save_notification_handles_just_before_midnight_properly(sample_template 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 @@ -459,7 +473,7 @@ def test_save_notification_handles_just_before_midnight_properly(sample_template assert stats.day == date(2016, 1, 1) -def test_save_notification_and_increment_email_stats(sample_email_template, sample_job): +def test_save_notification_and_increment_email_stats(sample_email_template, sample_job, ses_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -468,12 +482,13 @@ def test_save_notification_and_increment_email_stats(sample_email_template, samp 'service_id': sample_email_template.service.id, 'template': sample_email_template, 'template_id': sample_email_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) notification_2 = Notification(**data) - dao_create_notification(notification_1, sample_email_template.template_type) + dao_create_notification(notification_1, sample_email_template.template_type, ses_provider_name) assert Notification.query.count() == 1 @@ -484,7 +499,7 @@ def test_save_notification_and_increment_email_stats(sample_email_template, samp assert stats1.emails_requested == 1 assert stats1.sms_requested == 0 - dao_create_notification(notification_2, sample_email_template.template_type) + dao_create_notification(notification_2, sample_email_template.template_type, ses_provider_name) assert Notification.query.count() == 2 @@ -496,7 +511,7 @@ def test_save_notification_and_increment_email_stats(sample_email_template, samp assert stats2.sms_requested == 0 -def test_save_notification_and_increment_sms_stats(sample_template, sample_job): +def test_save_notification_and_increment_sms_stats(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -505,12 +520,13 @@ def test_save_notification_and_increment_sms_stats(sample_template, sample_job): 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) notification_2 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 @@ -521,7 +537,7 @@ def test_save_notification_and_increment_sms_stats(sample_template, sample_job): assert stats1.emails_requested == 0 assert stats1.sms_requested == 1 - dao_create_notification(notification_2, sample_template.template_type) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 2 @@ -533,7 +549,7 @@ def test_save_notification_and_increment_sms_stats(sample_template, sample_job): assert stats2.sms_requested == 2 -def test_not_save_notification_and_not_create_stats_on_commit_error(sample_template, sample_job): +def test_not_save_notification_and_not_create_stats_on_commit_error(sample_template, sample_job, mmg_provider_name): random_id = str(uuid.uuid4()) assert Notification.query.count() == 0 @@ -544,12 +560,13 @@ def test_not_save_notification_and_not_create_stats_on_commit_error(sample_templ 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) with pytest.raises(SQLAlchemyError): - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 0 assert Job.query.get(sample_job.id).notifications_sent == 0 @@ -557,7 +574,7 @@ def test_not_save_notification_and_not_create_stats_on_commit_error(sample_templ assert TemplateStatistics.query.count() == 0 -def test_save_notification_and_increment_job(sample_template, sample_job): +def test_save_notification_and_increment_job(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -566,11 +583,12 @@ def test_save_notification_and_increment_job(sample_template, sample_job): 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -584,12 +602,12 @@ def test_save_notification_and_increment_job(sample_template, sample_job): assert Job.query.get(sample_job.id).notifications_sent == 1 notification_2 = Notification(**data) - dao_create_notification(notification_2, sample_template.template_type) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 2 assert Job.query.get(sample_job.id).notifications_sent == 2 -def test_should_not_increment_job_if_notification_fails_to_persist(sample_template, sample_job): +def test_should_not_increment_job_if_notification_fails_to_persist(sample_template, sample_job, mmg_provider_name): random_id = str(uuid.uuid4()) assert Notification.query.count() == 0 @@ -601,11 +619,12 @@ def test_should_not_increment_job_if_notification_fails_to_persist(sample_templa 'service': sample_template.service, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 assert Job.query.get(sample_job.id).notifications_sent == 1 @@ -613,14 +632,14 @@ def test_should_not_increment_job_if_notification_fails_to_persist(sample_templa notification_2 = Notification(**data) with pytest.raises(SQLAlchemyError): - dao_create_notification(notification_2, sample_template.template_type) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 assert Job.query.get(sample_job.id).notifications_sent == 1 assert Job.query.get(sample_job.id).updated_at == job_last_updated_at -def test_save_notification_and_increment_correct_job(notify_db, notify_db_session, sample_template): +def test_save_notification_and_increment_correct_job(notify_db, notify_db_session, sample_template, mmg_provider_name): job_1 = sample_job(notify_db, notify_db_session, sample_template.service) job_2 = sample_job(notify_db, notify_db_session, sample_template.service) @@ -632,11 +651,12 @@ def test_save_notification_and_increment_correct_job(notify_db, notify_db_sessio 'service': sample_template.service, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -651,7 +671,7 @@ def test_save_notification_and_increment_correct_job(notify_db, notify_db_sessio assert Job.query.get(job_2.id).notifications_sent == 0 -def test_save_notification_with_no_job(sample_template): +def test_save_notification_with_no_job(sample_template, mmg_provider_name): assert Notification.query.count() == 0 data = { 'to': '+44709123456', @@ -659,11 +679,12 @@ def test_save_notification_with_no_job(sample_template): 'service': sample_template.service, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -682,7 +703,7 @@ def test_get_notification(sample_notification): assert sample_notification == notifcation_from_db -def test_save_notification_no_job_id(sample_template): +def test_save_notification_no_job_id(sample_template, mmg_provider_name): assert Notification.query.count() == 0 to = '+44709123456' data = { @@ -691,11 +712,12 @@ def test_save_notification_no_job_id(sample_template): 'service': sample_template.service, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert Notification.query.count() == 1 notification_from_db = Notification.query.all()[0] @@ -786,7 +808,7 @@ def test_should_not_delete_failed_notifications_before_seven_days(notify_db, not @freeze_time("2016-03-30") -def test_save_new_notification_creates_template_stats(sample_template, sample_job): +def test_save_new_notification_creates_template_stats(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 assert TemplateStatistics.query.count() == 0 data = { @@ -796,11 +818,12 @@ def test_save_new_notification_creates_template_stats(sample_template, sample_jo 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert TemplateStatistics.query.count() == 1 template_stats = TemplateStatistics.query.filter(TemplateStatistics.service_id == sample_template.service.id, @@ -812,7 +835,7 @@ def test_save_new_notification_creates_template_stats(sample_template, sample_jo @freeze_time("2016-03-30") -def test_save_new_notification_creates_template_stats_per_day(sample_template, sample_job): +def test_save_new_notification_creates_template_stats_per_day(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 assert TemplateStatistics.query.count() == 0 data = { @@ -822,11 +845,12 @@ def test_save_new_notification_creates_template_stats_per_day(sample_template, s 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) assert TemplateStatistics.query.count() == 1 template_stats = TemplateStatistics.query.filter(TemplateStatistics.service_id == sample_template.service.id, @@ -840,7 +864,7 @@ def test_save_new_notification_creates_template_stats_per_day(sample_template, s with freeze_time('2016-03-31'): assert TemplateStatistics.query.count() == 1 new_notification = Notification(**data) - dao_create_notification(new_notification, sample_template.template_type) + dao_create_notification(new_notification, sample_template.template_type, mmg_provider_name) assert TemplateStatistics.query.count() == 2 first_stats = TemplateStatistics.query.filter(TemplateStatistics.day == datetime(2016, 3, 30)).first() @@ -856,7 +880,7 @@ def test_save_new_notification_creates_template_stats_per_day(sample_template, s assert second_stats.usage_count == 1 -def test_save_another_notification_increments_template_stats(sample_template, sample_job): +def test_save_another_notification_increments_template_stats(sample_template, sample_job, mmg_provider_name): assert Notification.query.count() == 0 assert TemplateStatistics.query.count() == 0 data = { @@ -866,12 +890,13 @@ def test_save_another_notification_increments_template_stats(sample_template, sa 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) notification_2 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) assert TemplateStatistics.query.count() == 1 template_stats = TemplateStatistics.query.filter(TemplateStatistics.service_id == sample_template.service.id, @@ -880,7 +905,7 @@ def test_save_another_notification_increments_template_stats(sample_template, sa assert template_stats.template_id == sample_template.id assert template_stats.usage_count == 1 - dao_create_notification(notification_2, sample_template.template_type) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) assert TemplateStatistics.query.count() == 1 template_stats = TemplateStatistics.query.filter(TemplateStatistics.service_id == sample_template.service.id, @@ -889,7 +914,8 @@ def test_save_another_notification_increments_template_stats(sample_template, sa def test_successful_notification_inserts_followed_by_failure_does_not_increment_template_stats(sample_template, - sample_job): + sample_job, + mmg_provider_name): assert Notification.query.count() == 0 assert NotificationStatistics.query.count() == 0 assert TemplateStatistics.query.count() == 0 @@ -901,15 +927,16 @@ def test_successful_notification_inserts_followed_by_failure_does_not_increment_ 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification_1 = Notification(**data) notification_2 = Notification(**data) notification_3 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) + dao_create_notification(notification_1, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_2, sample_template.template_type, mmg_provider_name) + dao_create_notification(notification_3, sample_template.template_type, mmg_provider_name) assert NotificationStatistics.query.count() == 1 notication_stats = NotificationStatistics.query.filter( @@ -928,7 +955,7 @@ def test_successful_notification_inserts_followed_by_failure_does_not_increment_ try: # Mess up db in really bad way db.session.execute('DROP TABLE TEMPLATE_STATISTICS') - dao_create_notification(failing_notification, sample_template.template_type) + dao_create_notification(failing_notification, sample_template.template_type, mmg_provider_name) except Exception as e: # There should be no additional notification stats or counts assert NotificationStatistics.query.count() == 1 @@ -939,7 +966,9 @@ def test_successful_notification_inserts_followed_by_failure_does_not_increment_ @freeze_time("2016-03-30") -def test_get_template_stats_for_service_returns_stats_in_reverse_date_order(sample_template, sample_job): +def test_get_template_stats_for_service_returns_stats_in_reverse_date_order(sample_template, + sample_job, + mmg_provider_name): template_stats = dao_get_template_statistics_for_service(sample_template.service.id) assert len(template_stats) == 0 @@ -950,21 +979,22 @@ def test_get_template_stats_for_service_returns_stats_in_reverse_date_order(samp 'service_id': sample_template.service.id, 'template': sample_template, 'template_id': sample_template.id, - 'created_at': datetime.utcnow() + 'created_at': datetime.utcnow(), + 'content_char_count': 160 } notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) + dao_create_notification(notification, sample_template.template_type, mmg_provider_name) # move on one day with freeze_time('2016-03-31'): new_notification = Notification(**data) - dao_create_notification(new_notification, sample_template.template_type) + dao_create_notification(new_notification, sample_template.template_type, mmg_provider_name) # move on one more day with freeze_time('2016-04-01'): new_notification = Notification(**data) - dao_create_notification(new_notification, sample_template.template_type) + dao_create_notification(new_notification, sample_template.template_type, mmg_provider_name) template_stats = dao_get_template_statistics_for_service(sample_template.service_id) assert len(template_stats) == 3 diff --git a/tests/app/dao/test_notifications_dao_provider_statistics.py b/tests/app/dao/test_notifications_dao_provider_statistics.py new file mode 100644 index 000000000..78626b7a1 --- /dev/null +++ b/tests/app/dao/test_notifications_dao_provider_statistics.py @@ -0,0 +1,73 @@ +from datetime import datetime +from app.models import ProviderStatistics + +from app.dao.provider_statistics_dao import get_provider_statistics +from app.models import Notification +from tests.app.conftest import sample_notification as create_sample_notification + + +def test_should_update_provider_statistics_sms(notify_db, + notify_db_session, + sample_template, + mmg_provider_name): + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_template) + provider_stats = get_provider_statistics(sample_template.service, mmg_provider_name) + assert provider_stats.unit_count == 1 + + +def test_should_update_provider_statistics_email(notify_db, + notify_db_session, + sample_email_template, + ses_provider_name): + notification = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template) + provider_stats = get_provider_statistics(sample_email_template.service, ses_provider_name) + assert provider_stats.unit_count == 1 + + +def test_should_update_provider_statistics_sms_multi(notify_db, + notify_db_session, + sample_template, + mmg_provider_name): + notification1 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_template, + content_char_count=160) + notification1 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_template, + content_char_count=161) + notification1 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_template, + content_char_count=307) + provider_stats = get_provider_statistics(sample_template.service, mmg_provider_name) + assert provider_stats.unit_count == 6 + + +def test_should_update_provider_statistics_email_multi(notify_db, + notify_db_session, + sample_email_template, + ses_provider_name): + notification1 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template) + notification2 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template) + notification3 = create_sample_notification( + notify_db, + notify_db_session, + template=sample_email_template) + provider_stats = get_provider_statistics(sample_email_template.service, ses_provider_name) + assert provider_stats.unit_count == 3 diff --git a/tests/app/dao/test_provider_rates_dao.py b/tests/app/dao/test_provider_rates_dao.py new file mode 100644 index 000000000..c49584f2c --- /dev/null +++ b/tests/app/dao/test_provider_rates_dao.py @@ -0,0 +1,14 @@ +from datetime import datetime +from decimal import Decimal +from app.dao.provider_rates_dao import create_provider_rates +from app.models import ProviderRates + + +def test_create_provider_rates(notify_db, notify_db_session, mmg_provider_name): + now = datetime.now() + rate = Decimal("1.00000") + create_provider_rates(mmg_provider_name, now, rate) + assert ProviderRates.query.count() == 1 + assert ProviderRates.query.first().rate == rate + assert ProviderRates.query.first().valid_from == now + assert ProviderRates.query.first().provider == mmg_provider_name