diff --git a/app/celery/tasks.py b/app/celery/tasks.py index 3f749650d..98a44c453 100644 --- a/app/celery/tasks.py +++ b/app/celery/tasks.py @@ -38,7 +38,8 @@ from app.dao.notifications_dao import ( dao_update_notification, delete_notifications_created_more_than_a_week_ago, dao_get_notification_statistics_for_service_and_day, - update_notification_reference_by_id + update_notification_reference_by_id, + get_character_count_of_content ) from app.dao.jobs_dao import ( @@ -240,6 +241,9 @@ def send_sms(service_id, notification_id, encrypted_notification, created_at): 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) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 9fbb8bf3e..6ca113a68 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -1,3 +1,4 @@ +import math from sqlalchemy import ( desc, func @@ -47,6 +48,14 @@ def transactional(func): return commit_or_rollback +def get_character_count_of_content(content, encoding='utf-8'): + return len(content.encode(encoding)) + + +def get_sms_message_count(char_count): + return 1 if char_count <= 160 else math.ceil(float(char_count) / 153) + + def dao_get_notification_statistics_for_service(service_id): return NotificationStatistics.query.filter_by( service_id=service_id diff --git a/app/models.py b/app/models.py index 583e4c6dc..775c6db86 100644 --- a/app/models.py +++ b/app/models.py @@ -64,9 +64,7 @@ user_to_service = db.Table( db.Model.metadata, db.Column('user_id', UUID(as_uuid=True), db.ForeignKey('users.id')), db.Column('service_id', UUID(as_uuid=True), db.ForeignKey('services.id')), - UniqueConstraint('user_id', 'service_id', name='uix_user_to_service') - ) @@ -249,6 +247,7 @@ class Notification(db.Model): service = db.relationship('Service') template_id = db.Column(UUID(as_uuid=True), db.ForeignKey('templates.id'), index=True, unique=False) template = db.relationship('Template') + content_char_count = db.Column(db.Integer, nullable=True) created_at = db.Column( db.DateTime, index=False, diff --git a/migrations/versions/0002_add_content_char_count.py b/migrations/versions/0002_add_content_char_count.py new file mode 100644 index 000000000..95d515401 --- /dev/null +++ b/migrations/versions/0002_add_content_char_count.py @@ -0,0 +1,27 @@ +"""empty message + +Revision ID: 0002_add_content_char_count +Revises: 0001_restart_migrations +Create Date: 2016-04-15 12:12:46.383782 + +""" + +# revision identifiers, used by Alembic. +revision = '0002_add_content_char_count' +down_revision = '0001_restart_migrations' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import table, column + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('notifications', sa.Column('content_char_count', sa.Integer(), nullable=True)) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('notifications', 'content_char_count') + ### end Alembic commands ### diff --git a/tests/app/dao/test_notification_dao.py b/tests/app/dao/test_notification_dao.py index b9ffc73d4..265855486 100644 --- a/tests/app/dao/test_notification_dao.py +++ b/tests/app/dao/test_notification_dao.py @@ -29,7 +29,9 @@ from app.dao.notifications_dao import ( update_notification_status_by_id, update_notification_reference_by_id, update_notification_status_by_reference, - dao_get_template_statistics_for_service + dao_get_template_statistics_for_service, + get_character_count_of_content, + get_sms_message_count ) from tests.app.conftest import sample_job @@ -238,7 +240,7 @@ def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sam 'service': sample_template.service, 'service_id': sample_template.service.id, 'template': sample_template, - 'template_id': sample_template.id, + 'template_id': sample_template.id } today = datetime.utcnow() @@ -1000,3 +1002,31 @@ def test_get_template_stats_for_service_returns_no_result_if_no_usage_within_lim def test_get_template_stats_for_service_with_limit_if_no_records_returns_empty_list(sample_template): template_stats = dao_get_template_statistics_for_service(sample_template.service.id, limit_days=7) assert len(template_stats) == 0 + + +@pytest.mark.parametrize( + "content,encoding,expected_length", + [ + ("The quick brown fox jumped over the lazy dog", "utf-8", 44), + ("深", "utf-8", 3), + ("'First line.\n", 'utf-8', 13), + ("\t\n\r", 'utf-8', 3) + ]) +def test_get_character_count_of_content(content, encoding, expected_length): + assert get_character_count_of_content(content, encoding=encoding) == expected_length + + +@pytest.mark.parametrize( + "char_count, expected_sms_fragment_count", + [ + (159, 1), + (160, 1), + (161, 2), + (306, 2), + (307, 3), + (459, 3), + (460, 4), + (461, 4) + ]) +def test_sms_fragment_count(char_count, expected_sms_fragment_count): + assert get_sms_message_count(char_count) == expected_sms_fragment_count