Merge branch 'master' of https://github.com/alphagov/notifications-api into sms_whitelist

This commit is contained in:
venusbb
2017-07-25 17:14:59 +01:00
56 changed files with 1113 additions and 418 deletions

View File

@@ -25,8 +25,8 @@ from app.celery.scheduled_tasks import (
send_scheduled_notifications,
switch_current_sms_provider_on_slow_delivery,
timeout_job_statistics,
timeout_notifications
)
timeout_notifications,
populate_monthly_billing)
from app.clients.performance_platform.performance_platform_client import PerformancePlatformClient
from app.dao.jobs_dao import dao_get_job_by_id
from app.dao.notifications_dao import dao_get_scheduled_notifications
@@ -36,10 +36,10 @@ from app.dao.provider_details_dao import (
)
from app.models import (
Service, Template,
SMS_TYPE, LETTER_TYPE
)
SMS_TYPE, LETTER_TYPE,
MonthlyBilling)
from app.utils import get_london_midnight_in_utc
from tests.app.db import create_notification, create_service, create_template, create_job
from tests.app.db import create_notification, create_service, create_template, create_job, create_rate
from tests.app.conftest import (
sample_job as create_sample_job,
sample_notification_history as create_notification_history,
@@ -98,6 +98,8 @@ def test_should_have_decorated_tasks_functions():
'remove_transformed_dvla_files'
assert delete_dvla_response_files_older_than_seven_days.__wrapped__.__name__ == \
'delete_dvla_response_files_older_than_seven_days'
assert populate_monthly_billing.__wrapped__.__name__ == \
'populate_monthly_billing'
@pytest.fixture(scope='function')
@@ -468,7 +470,7 @@ def test_should_send_all_scheduled_notifications_to_deliver_queue(sample_templat
send_scheduled_notifications()
mocked.apply_async.assert_called_once_with([str(message_to_deliver.id)], queue='send-tasks')
mocked.apply_async.assert_called_once_with([str(message_to_deliver.id)], queue='send-sms-tasks')
scheduled_notifications = dao_get_scheduled_notifications()
assert not scheduled_notifications
@@ -607,3 +609,30 @@ def test_delete_dvla_response_files_older_than_seven_days_does_not_remove_files(
delete_dvla_response_files_older_than_seven_days()
remove_s3_mock.assert_not_called()
@freeze_time("2017-07-12 02:00:00")
def test_populate_monthly_billing(sample_template):
yesterday = datetime(2017, 7, 11, 13, 30)
create_rate(datetime(2016, 1, 1), 0.0123, 'sms')
create_notification(template=sample_template, status='delivered', created_at=yesterday)
create_notification(template=sample_template, status='delivered', created_at=yesterday - timedelta(days=1))
create_notification(template=sample_template, status='delivered', created_at=yesterday + timedelta(days=1))
# not included in billing
create_notification(template=sample_template, status='delivered', created_at=yesterday - timedelta(days=30))
assert len(MonthlyBilling.query.all()) == 0
populate_monthly_billing()
monthly_billing = MonthlyBilling.query.all()
assert len(monthly_billing) == 1
assert monthly_billing[0].service_id == sample_template.service_id
assert monthly_billing[0].year == 2017
assert monthly_billing[0].month == 'July'
assert monthly_billing[0].notification_type == 'sms'
assert len(monthly_billing[0].monthly_totals) == 1
assert sorted(monthly_billing[0].monthly_totals[0]) == sorted({'international': False,
'rate_multiplier': 1,
'billing_units': 3,
'rate': 0.0123,
'total_cost': 0.0369})

View File

@@ -1,21 +1,14 @@
import pytest
from sqlalchemy.exc import SQLAlchemyError
from app import create_uuid
from app.celery.statistics_tasks import (
record_initial_job_statistics,
record_outcome_job_statistics,
create_initial_notification_statistic_tasks,
create_outcome_notification_statistic_tasks)
from app.models import (
NOTIFICATION_STATUS_TYPES_COMPLETED,
NOTIFICATION_SENDING,
NOTIFICATION_PENDING,
NOTIFICATION_CREATED,
NOTIFICATION_DELIVERED
)
from sqlalchemy.exc import SQLAlchemyError
from app import create_uuid
from tests.app.conftest import sample_notification
from app.models import NOTIFICATION_STATUS_TYPES_COMPLETED, NOTIFICATION_SENT, NOTIFICATION_SENDING, \
NOTIFICATION_PENDING, NOTIFICATION_CREATED, NOTIFICATION_DELIVERED
def test_should_create_initial_job_task_if_notification_is_related_to_a_job(

View File

@@ -422,7 +422,7 @@ def test_should_send_template_to_correct_sms_task_and_persist(sample_template_wi
assert persisted_notification.notification_type == 'sms'
mocked_deliver_sms.assert_called_once_with(
[str(persisted_notification.id)],
queue="send-tasks"
queue="send-sms-tasks"
)
@@ -483,7 +483,7 @@ def test_should_send_sms_if_restricted_service_and_valid_number(notify_db, notif
assert persisted_notification.notification_type == 'sms'
provider_tasks.deliver_sms.apply_async.assert_called_once_with(
[str(persisted_notification.id)],
queue="send-tasks"
queue="send-sms-tasks"
)
@@ -509,7 +509,7 @@ def test_should_send_sms_if_restricted_service_and_non_team_number_with_test_key
persisted_notification = Notification.query.one()
mocked_deliver_sms.assert_called_once_with(
[str(persisted_notification.id)],
queue="send-tasks"
queue="send-sms-tasks"
)
@@ -537,7 +537,7 @@ def test_should_send_email_if_restricted_service_and_non_team_email_address_with
persisted_notification = Notification.query.one()
mocked_deliver_email.assert_called_once_with(
[str(persisted_notification.id)],
queue="send-tasks"
queue="send-email-tasks"
)
@@ -641,7 +641,7 @@ def test_should_send_sms_template_to_and_persist_with_job_id(sample_job, sample_
provider_tasks.deliver_sms.apply_async.assert_called_once_with(
[str(persisted_notification.id)],
queue="send-tasks"
queue="send-sms-tasks"
)
@@ -738,7 +738,7 @@ def test_should_use_email_template_and_persist(sample_email_template_with_placeh
assert persisted_notification.notification_type == 'email'
provider_tasks.deliver_email.apply_async.assert_called_once_with(
[str(persisted_notification.id)], queue='send-tasks')
[str(persisted_notification.id)], queue='send-email-tasks')
def test_send_email_should_use_template_version_from_job_not_latest(sample_email_template, mocker):
@@ -769,7 +769,7 @@ def test_send_email_should_use_template_version_from_job_not_latest(sample_email
assert not persisted_notification.sent_by
assert persisted_notification.notification_type == 'email'
provider_tasks.deliver_email.apply_async.assert_called_once_with([str(persisted_notification.id)],
queue='send-tasks')
queue='send-email-tasks')
def test_should_use_email_template_subject_placeholders(sample_email_template_with_placeholders, mocker):
@@ -795,7 +795,7 @@ def test_should_use_email_template_subject_placeholders(sample_email_template_wi
assert not persisted_notification.reference
assert persisted_notification.notification_type == 'email'
provider_tasks.deliver_email.apply_async.assert_called_once_with(
[str(persisted_notification.id)], queue='send-tasks'
[str(persisted_notification.id)], queue='send-email-tasks'
)
@@ -823,7 +823,7 @@ def test_should_use_email_template_and_persist_without_personalisation(sample_em
assert not persisted_notification.reference
assert persisted_notification.notification_type == 'email'
provider_tasks.deliver_email.apply_async.assert_called_once_with([str(persisted_notification.id)],
queue='send-tasks')
queue='send-email-tasks')
def test_send_sms_should_go_to_retry_queue_if_database_errors(sample_template, mocker):

View File

@@ -1,4 +1,8 @@
from app.dao.date_util import get_financial_year, get_april_fools
from datetime import datetime
import pytest
from app.dao.date_util import get_financial_year, get_april_fools, get_month_start_end_date
def test_get_financial_year():
@@ -11,3 +15,16 @@ def test_get_april_fools():
april_fools = get_april_fools(2016)
assert str(april_fools) == '2016-03-31 23:00:00'
assert april_fools.tzinfo is None
@pytest.mark.parametrize("month, year, expected_end",
[(7, 2017, 31),
(2, 2016, 29),
(2, 2017, 28),
(9, 2018, 30),
(12, 2019, 31)])
def test_get_month_start_end_date(month, year, expected_end):
month_year = datetime(year, month, 10, 13, 30, 00)
result = get_month_start_end_date(month_year)
assert result[0] == datetime(year, month, 1, 0, 0, 0, 0)
assert result[1] == datetime(year, month, expected_end, 23, 59, 59, 99999)

View File

@@ -0,0 +1,127 @@
from datetime import datetime
from app.dao.monthly_billing_dao import (
create_or_update_monthly_billing_sms,
get_monthly_billing_sms,
get_service_ids_that_need_sms_billing_populated
)
from app.models import MonthlyBilling
from tests.app.db import create_notification, create_rate, create_service, create_template
def test_add_monthly_billing(sample_template):
jan = datetime(2017, 1, 1)
feb = datetime(2017, 2, 15)
create_rate(start_date=jan, value=0.0158, notification_type='sms')
create_rate(start_date=datetime(2017, 3, 31, 23, 00, 00), value=0.123, notification_type='sms')
create_notification(template=sample_template, created_at=jan, billable_units=1, status='delivered')
create_notification(template=sample_template, created_at=feb, billable_units=2, status='delivered')
create_or_update_monthly_billing_sms(service_id=sample_template.service_id,
billing_month=jan)
create_or_update_monthly_billing_sms(service_id=sample_template.service_id,
billing_month=feb)
monthly_billing = MonthlyBilling.query.all()
assert len(monthly_billing) == 2
assert monthly_billing[0].month == 'January'
assert monthly_billing[1].month == 'February'
january = get_monthly_billing_sms(service_id=sample_template.service_id, billing_month=jan)
expected_jan = {"billing_units": 1,
"rate_multiplier": 1,
"international": False,
"rate": 0.0158,
"total_cost": 1 * 0.0158}
assert_monthly_billing(january, 2017, "January", sample_template.service_id, 1, expected_jan)
february = get_monthly_billing_sms(service_id=sample_template.service_id, billing_month=feb)
expected_feb = {"billing_units": 2,
"rate_multiplier": 1,
"international": False,
"rate": 0.0158,
"total_cost": 2 * 0.0158}
assert_monthly_billing(february, 2017, "February", sample_template.service_id, 1, expected_feb)
def test_add_monthly_billing_multiple_rates_in_a_month(sample_template):
rate_1 = datetime(2016, 12, 1)
rate_2 = datetime(2017, 1, 15)
create_rate(start_date=rate_1, value=0.0158, notification_type='sms')
create_rate(start_date=rate_2, value=0.0124, notification_type='sms')
create_notification(template=sample_template, created_at=datetime(2017, 1, 1), billable_units=1, status='delivered')
create_notification(template=sample_template, created_at=datetime(2017, 1, 14, 23, 59), billable_units=1,
status='delivered')
create_notification(template=sample_template, created_at=datetime(2017, 1, 15), billable_units=2,
status='delivered')
create_notification(template=sample_template, created_at=datetime(2017, 1, 17, 13, 30, 57), billable_units=4,
status='delivered')
create_or_update_monthly_billing_sms(service_id=sample_template.service_id,
billing_month=rate_2)
monthly_billing = MonthlyBilling.query.all()
assert len(monthly_billing) == 1
assert monthly_billing[0].month == 'January'
january = get_monthly_billing_sms(service_id=sample_template.service_id, billing_month=rate_2)
first_row = {"billing_units": 2,
"rate_multiplier": 1,
"international": False,
"rate": 0.0158,
"total_cost": 3 * 0.0158}
assert_monthly_billing(january, 2017, "January", sample_template.service_id, 2, first_row)
second_row = {"billing_units": 6,
"rate_multiplier": 1,
"international": False,
"rate": 0.0124,
"total_cost": 1 * 0.0124}
assert sorted(january.monthly_totals[1]) == sorted(second_row)
def test_update_monthly_billing_overwrites_old_totals(sample_template):
july = datetime(2017, 7, 1)
create_rate(july, 0.123, 'sms')
create_notification(template=sample_template, created_at=datetime(2017, 7, 2), billable_units=1, status='delivered')
create_or_update_monthly_billing_sms(sample_template.service_id, july)
first_update = get_monthly_billing_sms(sample_template.service_id, july)
expected = {"billing_units": 1,
"rate_multiplier": 1,
"international": False,
"rate": 0.123,
"total_cost": 1 * 0.123}
assert_monthly_billing(first_update, 2017, "July", sample_template.service_id, 1, expected)
create_notification(template=sample_template, created_at=datetime(2017, 7, 5), billable_units=2, status='delivered')
create_or_update_monthly_billing_sms(sample_template.service_id, july)
second_update = get_monthly_billing_sms(sample_template.service_id, july)
expected_update = {"billing_units": 3,
"rate_multiplier": 1,
"international": False,
"rate": 0.123,
"total_cost": 3 * 0.123}
assert_monthly_billing(second_update, 2017, "July", sample_template.service_id, 1, expected_update)
def assert_monthly_billing(monthly_billing, year, month, service_id, expected_len, first_row):
assert monthly_billing.year == year
assert monthly_billing.month == month
assert monthly_billing.service_id == service_id
assert len(monthly_billing.monthly_totals) == expected_len
assert sorted(monthly_billing.monthly_totals[0]) == sorted(first_row)
def test_get_service_id(notify_db_session):
service_1 = create_service(service_name="Service One")
template_1 = create_template(service=service_1)
service_2 = create_service(service_name="Service Two")
template_2 = create_template(service=service_2)
create_notification(template=template_1, created_at=datetime(2017, 6, 30, 13, 30), status='delivered')
create_notification(template=template_1, created_at=datetime(2017, 7, 1, 14, 30), status='delivered')
create_notification(template=template_2, created_at=datetime(2017, 7, 15, 13, 30))
create_notification(template=template_2, created_at=datetime(2017, 7, 31, 13, 30))
services = get_service_ids_that_need_sms_billing_populated(start_date=datetime(2017, 7, 1),
end_date=datetime(2017, 7, 16))
expected_services = [service_1.id, service_2.id]
assert sorted([x.service_id for x in services]) == sorted(expected_services)

View File

@@ -6,7 +6,7 @@ from flask import current_app
from app.dao.date_util import get_financial_year
from app.dao.notification_usage_dao import (
get_rates_for_year,
get_rates_for_daterange,
get_yearly_billing_data,
get_monthly_billing_data,
get_total_billable_units_for_sent_sms_notifications_in_date_range,
@@ -24,22 +24,22 @@ from freezegun import freeze_time
from tests.conftest import set_config
def test_get_rates_for_year(notify_db, notify_db_session):
def test_get_rates_for_daterange(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 5, 18), 0.016)
set_up_rate(notify_db, datetime(2017, 3, 31, 23), 0.0158)
start_date, end_date = get_financial_year(2017)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates) == 1
assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2017-03-31 23:00:00"
assert rates[0].rate == 0.0158
def test_get_rates_for_year_multiple_result_per_year(notify_db, notify_db_session):
def test_get_rates_for_daterange_multiple_result_per_year(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.015)
set_up_rate(notify_db, datetime(2016, 5, 18), 0.016)
set_up_rate(notify_db, datetime(2017, 4, 1), 0.0158)
start_date, end_date = get_financial_year(2016)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates) == 2
assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2016-04-01 00:00:00"
assert rates[0].rate == 0.015
@@ -47,12 +47,12 @@ def test_get_rates_for_year_multiple_result_per_year(notify_db, notify_db_sessio
assert rates[1].rate == 0.016
def test_get_rates_for_year_returns_correct_rates(notify_db, notify_db_session):
def test_get_rates_for_daterange_returns_correct_rates(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.015)
set_up_rate(notify_db, datetime(2016, 9, 1), 0.016)
set_up_rate(notify_db, datetime(2017, 6, 1), 0.0175)
start_date, end_date = get_financial_year(2017)
rates_2017 = get_rates_for_year(start_date, end_date, 'sms')
rates_2017 = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates_2017) == 2
assert datetime.strftime(rates_2017[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2016-09-01 00:00:00"
assert rates_2017[0].rate == 0.016
@@ -60,43 +60,56 @@ def test_get_rates_for_year_returns_correct_rates(notify_db, notify_db_session):
assert rates_2017[1].rate == 0.0175
def test_get_rates_for_year_in_the_future(notify_db, notify_db_session):
def test_get_rates_for_daterange_in_the_future(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.015)
set_up_rate(notify_db, datetime(2017, 6, 1), 0.0175)
start_date, end_date = get_financial_year(2018)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2017-06-01 00:00:00"
assert rates[0].rate == 0.0175
def test_get_rates_for_year_returns_empty_list_if_year_is_before_earliest_rate(notify_db, notify_db_session):
def test_get_rates_for_daterange_returns_empty_list_if_year_is_before_earliest_rate(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.015)
set_up_rate(notify_db, datetime(2017, 6, 1), 0.0175)
start_date, end_date = get_financial_year(2015)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert rates == []
def test_get_rates_for_year_early_rate(notify_db, notify_db_session):
def test_get_rates_for_daterange_early_rate(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2015, 6, 1), 0.014)
set_up_rate(notify_db, datetime(2016, 6, 1), 0.015)
set_up_rate(notify_db, datetime(2016, 9, 1), 0.016)
set_up_rate(notify_db, datetime(2017, 6, 1), 0.0175)
start_date, end_date = get_financial_year(2016)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates) == 3
def test_get_rates_for_year_edge_case(notify_db, notify_db_session):
def test_get_rates_for_daterange_edge_case(notify_db, notify_db_session):
set_up_rate(notify_db, datetime(2016, 3, 31, 23, 00), 0.015)
set_up_rate(notify_db, datetime(2017, 3, 31, 23, 00), 0.0175)
start_date, end_date = get_financial_year(2016)
rates = get_rates_for_year(start_date, end_date, 'sms')
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates) == 1
assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2016-03-31 23:00:00"
assert rates[0].rate == 0.015
def test_get_rates_for_daterange_where_daterange_is_one_month_that_falls_between_rate_valid_from(
notify_db, notify_db_session
):
set_up_rate(notify_db, datetime(2017, 1, 1), 0.175)
set_up_rate(notify_db, datetime(2017, 3, 31), 0.123)
start_date = datetime(2017, 2, 1, 00, 00, 00)
end_date = datetime(2017, 2, 28, 23, 59, 59, 99999)
rates = get_rates_for_daterange(start_date, end_date, 'sms')
assert len(rates) == 1
assert datetime.strftime(rates[0].valid_from, '%Y-%m-%d %H:%M:%S') == "2017-01-01 00:00:00"
assert rates[0].rate == 0.175
def test_get_yearly_billing_data(notify_db, notify_db_session, sample_template, sample_email_template):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.014)
set_up_rate(notify_db, datetime(2016, 6, 1), 0.0158)
@@ -254,8 +267,7 @@ def test_get_monthly_billing_data_with_multiple_rates(notify_db, notify_db_sessi
assert results[3] == ('June', 4, 1, False, 'sms', 0.0175)
def test_get_monthly_billing_data_with_no_notifications_for_year(notify_db, notify_db_session, sample_template,
sample_email_template):
def test_get_monthly_billing_data_with_no_notifications_for_daterange(notify_db, notify_db_session, sample_template):
set_up_rate(notify_db, datetime(2016, 4, 1), 0.014)
results = get_monthly_billing_data(sample_template.service_id, 2016)
assert len(results) == 0

View File

@@ -1,3 +1,4 @@
import uuid
from datetime import datetime
from decimal import Decimal
from app.dao.provider_rates_dao import create_provider_rates

View File

@@ -1,7 +1,7 @@
from datetime import datetime
import uuid
from app import db
from app.dao.jobs_dao import dao_create_job
from app.dao.service_inbound_api_dao import save_service_inbound_api
from app.models import (
@@ -11,6 +11,7 @@ from app.models import (
Notification,
ScheduledNotification,
ServicePermission,
Rate,
Job,
InboundSms,
Organisation,
@@ -239,3 +240,10 @@ def create_organisation(colour='blue', logo='test_x2.png', name='test_org_1'):
dao_create_organisation(organisation)
return organisation
def create_rate(start_date, value, notification_type):
rate = Rate(id=uuid.uuid4(), valid_from=start_date, rate=value, notification_type=notification_type)
db.session.add(rate)
db.session.commit()
return rate

View File

@@ -78,7 +78,7 @@ def test_should_call_deliver_sms_task_if_send_sms_to_provider_fails(notify_api,
)
app.delivery.send_to_providers.send_sms_to_provider.assert_called_with(sample_notification)
app.celery.provider_tasks.deliver_sms.apply_async.assert_called_with(
(str(sample_notification.id)), queue='send-tasks'
(str(sample_notification.id)), queue='send-sms-tasks'
)
assert response.status_code == 204
@@ -100,6 +100,6 @@ def test_should_call_deliver_email_task_if_send_email_to_provider_fails(
)
app.delivery.send_to_providers.send_email_to_provider.assert_called_with(sample_email_notification)
app.celery.provider_tasks.deliver_email.apply_async.assert_called_with(
(str(sample_email_notification.id)), queue='send-tasks'
(str(sample_email_notification.id)), queue='send-email-tasks'
)
assert response.status_code == 204

View File

@@ -132,7 +132,7 @@ def test_send_notification_with_placeholders_replaced(notify_api, sample_email_t
mocked.assert_called_once_with(
[notification_id],
queue="send-tasks"
queue="send-email-tasks"
)
assert response.status_code == 201
assert response_data['body'] == u'Hello Jo\nThis is an email from GOV.\u200BUK'
@@ -342,7 +342,7 @@ def test_should_allow_valid_sms_notification(notify_api, sample_template, mocker
response_data = json.loads(response.data)['data']
notification_id = response_data['notification']['id']
mocked.assert_called_once_with([notification_id], queue='send-tasks')
mocked.assert_called_once_with([notification_id], queue='send-sms-tasks')
assert response.status_code == 201
assert notification_id
assert 'subject' not in response_data
@@ -395,7 +395,7 @@ def test_should_allow_valid_email_notification(notify_api, sample_email_template
notification_id = response_data['notification']['id']
app.celery.provider_tasks.deliver_email.apply_async.assert_called_once_with(
[notification_id],
queue="send-tasks"
queue="send-email-tasks"
)
assert response.status_code == 201
@@ -593,7 +593,10 @@ def test_should_send_email_if_team_api_key_and_a_service_user(notify_api, sample
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), ('Authorization', 'Bearer {}'.format(auth_header))])
app.celery.provider_tasks.deliver_email.apply_async.assert_called_once_with([fake_uuid], queue='send-tasks')
app.celery.provider_tasks.deliver_email.apply_async.assert_called_once_with(
[fake_uuid],
queue='send-email-tasks'
)
assert response.status_code == 201
@@ -689,57 +692,67 @@ def test_should_send_sms_if_team_api_key_and_a_service_user(notify_api, sample_t
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), ('Authorization', 'Bearer {}'.format(auth_header))])
app.celery.provider_tasks.deliver_sms.apply_async.assert_called_once_with([fake_uuid], queue='send-tasks')
app.celery.provider_tasks.deliver_sms.apply_async.assert_called_once_with([fake_uuid], queue='send-sms-tasks')
assert response.status_code == 201
@pytest.mark.parametrize('template_type',
[SMS_TYPE, EMAIL_TYPE])
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_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))
@pytest.mark.parametrize('template_type,queue_name', [
(SMS_TYPE, 'send-sms-tasks'),
(EMAIL_TYPE, 'send-email-tasks')
])
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.dao.notifications_dao.create_uuid', 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))])
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-tasks')
assert response.status_code == 201
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
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
@pytest.mark.parametrize('template_type',
[SMS_TYPE, EMAIL_TYPE])
@pytest.mark.parametrize('template_type,queue_name', [
(SMS_TYPE, 'send-sms-tasks'),
(EMAIL_TYPE, 'send-email-tasks')
])
def test_should_delete_notification_and_return_error_if_sqs_fails(
client,
sample_email_template,
sample_template,
fake_uuid,
mocker,
template_type):
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")
@@ -768,7 +781,7 @@ def test_should_delete_notification_and_return_error_if_sqs_fails(
)
assert str(e.value) == 'failed to talk to SQS'
mocked.assert_called_once_with([fake_uuid], queue='send-tasks')
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)
@@ -1119,7 +1132,7 @@ def test_should_allow_store_original_number_on_sms_notification(client, sample_t
response_data = json.loads(response.data)['data']
notification_id = response_data['notification']['id']
mocked.assert_called_once_with([notification_id], queue='send-tasks')
mocked.assert_called_once_with([notification_id], queue='send-sms-tasks')
assert response.status_code == 201
assert notification_id
notifications = Notification.query.all()

View File

@@ -51,10 +51,18 @@ def test_persist_notification_creates_and_save_to_db(sample_template, sample_api
assert Notification.query.count() == 0
assert NotificationHistory.query.count() == 0
notification = persist_notification(sample_template.id, sample_template.version, '+447111111111',
sample_template.service, {}, 'sms', sample_api_key.id,
sample_api_key.key_type, job_id=sample_job.id,
job_row_number=100, reference="ref")
notification = persist_notification(
template_id=sample_template.id,
template_version=sample_template.version,
recipient='+447111111111',
service=sample_template.service,
personalisation={},
notification_type='sms',
api_key_id=sample_api_key.id,
key_type=sample_api_key.key_type,
job_id=sample_job.id,
job_row_number=100,
reference="ref")
assert Notification.query.get(notification.id) is not None
assert NotificationHistory.query.get(notification.id) is not None
@@ -127,14 +135,14 @@ def test_persist_notification_does_not_increment_cache_if_test_key(
assert Notification.query.count() == 0
assert NotificationHistory.query.count() == 0
persist_notification(
sample_template.id,
sample_template.version,
'+447111111111',
sample_template.service,
{},
'sms',
api_key.id,
api_key.key_type,
template_id=sample_template.id,
template_version=sample_template.version,
recipient='+447111111111',
service=sample_template.service,
personalisation={},
notification_type='sms',
api_key_id=api_key.id,
key_type=api_key.key_type,
job_id=sample_job.id,
job_row_number=100,
reference="ref",
@@ -193,18 +201,33 @@ def test_persist_notification_increments_cache_if_key_exists(sample_template, sa
mock_incr = mocker.patch('app.notifications.process_notifications.redis_store.incr')
mock_incr_hash_value = mocker.patch('app.notifications.process_notifications.redis_store.increment_hash_value')
persist_notification(sample_template.id, sample_template.version, '+447111111111',
sample_template.service, {}, 'sms', sample_api_key.id,
sample_api_key.key_type, reference="ref")
persist_notification(
template_id=sample_template.id,
template_version=sample_template.version,
recipient='+447111111111',
service=sample_template.service,
personalisation={},
notification_type='sms',
api_key_id=sample_api_key.id,
key_type=sample_api_key.key_type,
reference="ref"
)
mock_incr.assert_not_called()
mock_incr_hash_value.assert_not_called()
mocker.patch('app.notifications.process_notifications.redis_store.get', return_value=1)
mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash',
return_value={sample_template.id, 1})
persist_notification(sample_template.id, sample_template.version, '+447111111122',
sample_template.service, {}, 'sms', sample_api_key.id,
sample_api_key.key_type, reference="ref2")
persist_notification(
template_id=sample_template.id,
template_version=sample_template.version,
recipient='+447111111122',
service=sample_template.service,
personalisation={},
notification_type='sms',
api_key_id=sample_api_key.id,
key_type=sample_api_key.key_type,
reference="ref2")
mock_incr.assert_called_once_with(str(sample_template.service_id) + "-2016-01-01-count", )
mock_incr_hash_value.assert_called_once_with(cache_key_for_service_template_counter(sample_template.service_id),
sample_template.id)
@@ -214,9 +237,9 @@ def test_persist_notification_increments_cache_if_key_exists(sample_template, sa
[(True, None, 'research-mode-tasks', 'sms', 'normal'),
(True, None, 'research-mode-tasks', 'email', 'normal'),
(True, None, 'research-mode-tasks', 'email', 'team'),
(False, None, 'send-tasks', 'sms', 'normal'),
(False, None, 'send-tasks', 'email', 'normal'),
(False, None, 'send-tasks', 'sms', 'team'),
(False, None, 'send-sms-tasks', 'sms', 'normal'),
(False, None, 'send-email-tasks', 'email', 'normal'),
(False, None, 'send-sms-tasks', 'sms', 'team'),
(False, None, 'research-mode-tasks', 'sms', 'test'),
(True, 'notify-internal-tasks', 'research-mode-tasks', 'email', 'normal'),
(False, 'notify-internal-tasks', 'notify-internal-tasks', 'sms', 'normal'),

View File

@@ -5,7 +5,7 @@ import pytest
from notifications_utils.recipients import InvalidPhoneError
from app.v2.errors import BadRequestError, TooManyRequestsError
from app.celery import QueueNames
from app.config import QueueNames
from app.service.send_notification import send_one_off_notification
from app.models import KEY_TYPE_NORMAL, PRIORITY, SMS_TYPE

View File

@@ -0,0 +1,166 @@
import uuid
from flask import url_for, json
import pytest
from app.models import Job, Notification, SMS_TYPE, EMAIL_TYPE, LETTER_TYPE
from app.v2.errors import RateLimitError
from tests import create_authorization_header
from tests.app.db import create_service, create_template
pytestmark = pytest.mark.skip('Leters not currently implemented')
def letter_request(client, data, service_id, _expected_status=201):
resp = client.post(
url_for('v2_notifications.post_notification', notification_type='letter'),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header(service_id=service_id)]
)
json_resp = json.loads(resp.get_data(as_text=True))
assert resp.status_code == _expected_status, json_resp
return json_resp
@pytest.mark.parametrize('reference', [None, 'reference_from_client'])
def test_post_letter_notification_returns_201(client, sample_letter_template, mocker, reference):
mocked = mocker.patch('app.celery.tasks.build_dvla_file.apply_async')
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {
'address_line_1': 'Her Royal Highness Queen Elizabeth II',
'address_line_2': 'Buckingham Palace',
'address_line_3': 'London',
'postcode': 'SW1 1AA',
'name': 'Lizzie'
}
}
if reference:
data.update({'reference': reference})
resp_json = letter_request(client, data, service_id=sample_letter_template.service_id)
job = Job.query.one()
notification = Notification.query.all()
notification_id = notification.id
assert resp_json['id'] == str(notification_id)
assert resp_json['reference'] == reference
assert resp_json['content']['subject'] == sample_letter_template.subject
assert resp_json['content']['body'] == sample_letter_template.content
assert 'v2/notifications/{}'.format(notification_id) in resp_json['uri']
assert resp_json['template']['id'] == str(sample_letter_template.id)
assert resp_json['template']['version'] == sample_letter_template.version
assert (
'services/{}/templates/{}'.format(
sample_letter_template.service_id,
sample_letter_template.id
) in resp_json['template']['uri']
)
assert not resp_json['scheduled_for']
mocked.assert_called_once_with((str(job.id), ), queue='job-tasks')
def test_post_letter_notification_returns_400_and_missing_template(
client,
sample_service
):
data = {
'template_id': str(uuid.uuid4()),
'personalisation': {'address_line_1': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=sample_service.id, _expected_status=400)
assert error_json['status_code'] == 400
assert error_json['errors'] == [{'error': 'BadRequestError', 'message': 'Template not found'}]
def test_post_notification_returns_403_and_well_formed_auth_error(
client,
sample_letter_template
):
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {'address_line_1': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=sample_letter_template.service_id, _expected_status=401)
assert error_json['status_code'] == 401
assert error_json['errors'] == [{
'error': 'AuthError',
'message': 'Unauthorized, authentication token must be provided'
}]
def test_notification_returns_400_for_schema_problems(
client,
sample_service
):
data = {
'personalisation': {'address_line_1': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=sample_service.id, _expected_status=400)
assert error_json['status_code'] == 400
assert error_json['errors'] == [{
'error': 'ValidationError',
'message': 'template_id is a required property'
}]
def test_returns_a_429_limit_exceeded_if_rate_limit_exceeded(
client,
sample_letter_template,
mocker
):
persist_mock = mocker.patch('app.v2.notifications.post_notifications.persist_notification')
mocker.patch(
'app.v2.notifications.post_notifications.check_rate_limiting',
side_effect=RateLimitError('LIMIT', 'INTERVAL', 'TYPE')
)
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {'address_line_1': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=sample_letter_template.service_id, _expected_status=429)
assert error_json['status_code'] == 429
assert error_json['errors'] == [{
'error': 'RateLimitError',
'message': 'Exceeded rate limit for key type TYPE of LIMIT requests per INTERVAL seconds'
}]
assert not persist_mock.called
@pytest.mark.parametrize('service_args', [
{'service_permissions': [EMAIL_TYPE, SMS_TYPE]},
{'restricted': True}
])
def test_post_letter_notification_returns_403_if_not_allowed_to_send_notification(
client,
notify_db_session,
service_args
):
service = create_service(**service_args)
template = create_template(service, template_type=LETTER_TYPE)
data = {
'template_id': str(template.id),
'personalisation': {'address_line_1': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=service.id, _expected_status=400)
assert error_json['status_code'] == 403
assert error_json['errors'] == [
{'error': 'BadRequestError', 'message': 'Cannot send letters'}
]

View File

@@ -58,8 +58,8 @@ def test_post_sms_notification_returns_201(client, sample_template_with_placehol
@pytest.mark.parametrize("notification_type, key_send_to, send_to",
[("sms", "phone_number", "+447700900855"),
("email", "email_address", "sample@email.com")])
def test_post_sms_notification_returns_400_and_missing_template(client, sample_service,
notification_type, key_send_to, send_to):
def test_post_notification_returns_400_and_missing_template(client, sample_service,
notification_type, key_send_to, send_to):
data = {
key_send_to: send_to,
'template_id': str(uuid.uuid4())
@@ -201,6 +201,7 @@ def test_should_not_persist_or_send_notification_if_simulated_recipient(
assert response.status_code == 201
apply_async.assert_not_called()
assert json.loads(response.get_data(as_text=True))["id"]
assert Notification.query.count() == 0
@@ -433,3 +434,15 @@ def test_post_notification_raises_bad_request_if_service_not_invited_to_schedule
error_json = json.loads(response.get_data(as_text=True))
assert error_json['errors'] == [
{"error": "BadRequestError", "message": 'Cannot schedule notifications (this feature is invite-only)'}]
def test_post_notification_raises_bad_request_if_not_valid_notification_type(client, sample_service):
auth_header = create_authorization_header(service_id=sample_service.id)
response = client.post(
'/v2/notifications/foo',
data='{}',
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 404
error_json = json.loads(response.get_data(as_text=True))
assert 'The requested URL was not found on the server.' in error_json['message']