Files
notifications-api/tests/app/celery/test_ftp_update_tasks.py
Katie Smith 09e8ac9644 Fix assertions when we catch an error in the tests
Code that is within a `with Python.raises(...)` context manager but
comes after the line that raises the exception doesn't get evaluated.
We had some assertions that we never being tested because of this, so
this ensures that they will always get run and fixes them where
necessary.
2019-09-18 11:04:24 +01:00

320 lines
13 KiB
Python

from collections import namedtuple, defaultdict
from datetime import datetime, date
import pytest
from freezegun import freeze_time
from flask import current_app
from app.exceptions import DVLAException, NotificationTechnicalFailureException
from app.models import (
NotificationHistory,
NOTIFICATION_CREATED,
NOTIFICATION_DELIVERED,
NOTIFICATION_SENDING,
NOTIFICATION_TEMPORARY_FAILURE,
NOTIFICATION_TECHNICAL_FAILURE,
DailySortedLetter
)
from app.celery.tasks import (
check_billable_units,
get_billing_date_in_bst_from_filename,
persist_daily_sorted_letter_counts,
process_updates_from_file,
update_letter_notifications_statuses,
update_letter_notifications_to_error,
update_letter_notifications_to_sent_to_dvla,
record_daily_sorted_counts
)
from app.dao.daily_sorted_letter_dao import dao_get_daily_sorted_letter_by_billing_day
from tests.app.db import create_notification, create_service_callback_api, create_notification_history
from tests.conftest import set_config
@pytest.fixture
def notification_update():
"""
Returns a namedtuple to use as the argument for the check_billable_units function
"""
NotificationUpdate = namedtuple('NotificationUpdate', ['reference', 'status', 'page_count', 'cost_threshold'])
return NotificationUpdate('REFERENCE_ABC', 'sent', '1', 'cost')
def test_update_letter_notifications_statuses_raises_for_invalid_format(notify_api, mocker):
invalid_file = 'ref-foo|Sent|1|Unsorted\nref-bar|Sent|2'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=invalid_file)
with pytest.raises(DVLAException) as e:
update_letter_notifications_statuses(filename='NOTIFY-20170823160812-RSP.TXT')
assert 'DVLA response file: {} has an invalid format'.format('NOTIFY-20170823160812-RSP.TXT') in str(e)
def test_update_letter_notification_statuses_when_notification_does_not_exist_updates_notification_history(
sample_letter_template,
mocker
):
valid_file = 'ref-foo|Sent|1|Unsorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
notification = create_notification_history(sample_letter_template, reference='ref-foo', status=NOTIFICATION_SENDING,
billable_units=1)
update_letter_notifications_statuses(filename="NOTIFY-20170823160812-RSP.TXT")
updated_history = NotificationHistory.query.filter_by(id=notification.id).one()
assert updated_history.status == NOTIFICATION_DELIVERED
def test_update_letter_notifications_statuses_raises_dvla_exception(notify_api, mocker, sample_letter_template):
valid_file = 'ref-foo|Failed|1|Unsorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
create_notification(sample_letter_template, reference='ref-foo', status=NOTIFICATION_SENDING,
billable_units=0)
with pytest.raises(DVLAException) as e:
update_letter_notifications_statuses(filename="failed.txt")
failed = ["ref-foo"]
assert "DVLA response file: {filename} has failed letters with notification.reference {failures}".format(
filename="failed.txt", failures=failed
) in str(e)
def test_update_letter_notifications_statuses_calls_with_correct_bucket_location(notify_api, mocker):
s3_mock = mocker.patch('app.celery.tasks.s3.get_s3_object')
with set_config(notify_api, 'NOTIFY_EMAIL_DOMAIN', 'foo.bar'):
update_letter_notifications_statuses(filename='NOTIFY-20170823160812-RSP.TXT')
s3_mock.assert_called_with('{}-ftp'.format(
current_app.config['NOTIFY_EMAIL_DOMAIN']),
'NOTIFY-20170823160812-RSP.TXT'
)
def test_update_letter_notifications_statuses_builds_updates_from_content(notify_api, mocker):
valid_file = 'ref-foo|Sent|1|Unsorted\nref-bar|Sent|2|Sorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
update_mock = mocker.patch('app.celery.tasks.process_updates_from_file')
update_letter_notifications_statuses(filename='NOTIFY-20170823160812-RSP.TXT')
update_mock.assert_called_with('ref-foo|Sent|1|Unsorted\nref-bar|Sent|2|Sorted')
def test_update_letter_notifications_statuses_builds_updates_list(notify_api, mocker):
valid_file = 'ref-foo|Sent|1|Unsorted\nref-bar|Sent|2|Sorted'
updates = process_updates_from_file(valid_file)
assert len(updates) == 2
assert updates[0].reference == 'ref-foo'
assert updates[0].status == 'Sent'
assert updates[0].page_count == '1'
assert updates[0].cost_threshold == 'Unsorted'
assert updates[1].reference == 'ref-bar'
assert updates[1].status == 'Sent'
assert updates[1].page_count == '2'
assert updates[1].cost_threshold == 'Sorted'
def test_update_letter_notifications_statuses_persisted(notify_api, mocker, sample_letter_template):
sent_letter = create_notification(sample_letter_template, reference='ref-foo', status=NOTIFICATION_SENDING,
billable_units=1)
failed_letter = create_notification(sample_letter_template, reference='ref-bar', status=NOTIFICATION_SENDING,
billable_units=2)
create_service_callback_api(service=sample_letter_template.service, url="https://original_url.com")
valid_file = '{}|Sent|1|Unsorted\n{}|Failed|2|Sorted'.format(
sent_letter.reference, failed_letter.reference)
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
with pytest.raises(expected_exception=DVLAException) as e:
update_letter_notifications_statuses(filename='NOTIFY-20170823160812-RSP.TXT')
assert sent_letter.status == NOTIFICATION_DELIVERED
assert sent_letter.billable_units == 1
assert sent_letter.updated_at
assert failed_letter.status == NOTIFICATION_TEMPORARY_FAILURE
assert failed_letter.billable_units == 2
assert failed_letter.updated_at
assert "DVLA response file: {filename} has failed letters with notification.reference {failures}".format(
filename="NOTIFY-20170823160812-RSP.TXT", failures=[format(failed_letter.reference)]) in str(e)
def test_update_letter_notifications_does_not_call_send_callback_if_no_db_entry(notify_api, mocker,
sample_letter_template):
sent_letter = create_notification(sample_letter_template, reference='ref-foo', status=NOTIFICATION_SENDING,
billable_units=0)
valid_file = '{}|Sent|1|Unsorted\n'.format(sent_letter.reference)
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
send_mock = mocker.patch(
'app.celery.service_callback_tasks.send_delivery_status_to_service.apply_async'
)
update_letter_notifications_statuses(filename='NOTIFY-20170823160812-RSP.TXT')
send_mock.assert_not_called()
def test_update_letter_notifications_to_sent_to_dvla_updates_based_on_notification_references(
client,
sample_letter_template
):
first = create_notification(sample_letter_template, reference='first ref')
second = create_notification(sample_letter_template, reference='second ref')
dt = datetime.utcnow()
with freeze_time(dt):
update_letter_notifications_to_sent_to_dvla([first.reference])
assert first.status == NOTIFICATION_SENDING
assert first.sent_by == 'dvla'
assert first.sent_at == dt
assert first.updated_at == dt
assert second.status == NOTIFICATION_CREATED
def test_update_letter_notifications_to_error_updates_based_on_notification_references(
sample_letter_template
):
first = create_notification(sample_letter_template, reference='first ref')
second = create_notification(sample_letter_template, reference='second ref')
create_service_callback_api(service=sample_letter_template.service, url="https://original_url.com")
dt = datetime.utcnow()
with freeze_time(dt):
with pytest.raises(NotificationTechnicalFailureException) as e:
update_letter_notifications_to_error([first.reference])
assert first.reference in str(e.value)
assert first.status == NOTIFICATION_TECHNICAL_FAILURE
assert first.sent_by is None
assert first.sent_at is None
assert first.updated_at == dt
assert second.status == NOTIFICATION_CREATED
def test_check_billable_units_when_billable_units_matches_page_count(
client,
sample_letter_template,
mocker,
notification_update
):
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.error')
create_notification(sample_letter_template, reference='REFERENCE_ABC', billable_units=1)
check_billable_units(notification_update)
mock_logger.assert_not_called()
def test_check_billable_units_when_billable_units_does_not_match_page_count(
client,
sample_letter_template,
mocker,
notification_update
):
mock_logger = mocker.patch('app.celery.tasks.current_app.logger.exception')
notification = create_notification(sample_letter_template, reference='REFERENCE_ABC', billable_units=3)
check_billable_units(notification_update)
mock_logger.assert_called_once_with(
'Notification with id {} has 3 billable_units but DVLA says page count is 1'.format(notification.id)
)
@pytest.mark.parametrize('filename_date, billing_date', [
('20170820230000', date(2017, 8, 21)),
('20170120230000', date(2017, 1, 20))
])
def test_get_billing_date_in_bst_from_filename(filename_date, billing_date):
filename = 'NOTIFY-{}-RSP.TXT'.format(filename_date)
result = get_billing_date_in_bst_from_filename(filename)
assert result == billing_date
@freeze_time("2018-01-11 09:00:00")
def test_persist_daily_sorted_letter_counts_saves_sorted_and_unsorted_values(client, notify_db_session):
letter_counts = defaultdict(int, **{'unsorted': 5, 'sorted': 1})
persist_daily_sorted_letter_counts(date.today(), "test.txt", letter_counts)
day = dao_get_daily_sorted_letter_by_billing_day(date.today())
assert day.unsorted_count == 5
assert day.sorted_count == 1
def test_record_daily_sorted_counts_persists_daily_sorted_letter_count(
notify_api,
notify_db_session,
mocker,
):
valid_file = 'Letter1|Sent|1|uNsOrTeD\nLetter2|Sent|2|SORTED\nLetter3|Sent|2|Sorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
assert DailySortedLetter.query.count() == 0
record_daily_sorted_counts(filename='NOTIFY-20170823160812-RSP.TXT')
daily_sorted_counts = DailySortedLetter.query.all()
assert len(daily_sorted_counts) == 1
assert daily_sorted_counts[0].sorted_count == 2
assert daily_sorted_counts[0].unsorted_count == 1
def test_record_daily_sorted_counts_raises_dvla_exception_with_unknown_sorted_status(
notify_api,
mocker,
):
file_contents = 'ref-foo|Failed|1|invalid\nrow_2|Failed|1|MM'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=file_contents)
filename = "failed.txt"
with pytest.raises(DVLAException) as e:
record_daily_sorted_counts(filename=filename)
assert "DVLA response file: {} contains unknown Sorted status".format(filename) in e.value.message
assert "'mm'" in e.value.message
assert "'invalid'" in e.value.message
def test_record_daily_sorted_counts_persists_daily_sorted_letter_count_with_no_sorted_values(
notify_api,
mocker,
notify_db_session
):
valid_file = 'Letter1|Sent|1|Unsorted\nLetter2|Sent|2|Unsorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
record_daily_sorted_counts(filename='NOTIFY-20170823160812-RSP.TXT')
daily_sorted_letter = dao_get_daily_sorted_letter_by_billing_day(date(2017, 8, 23))
assert daily_sorted_letter.unsorted_count == 2
assert daily_sorted_letter.sorted_count == 0
def test_record_daily_sorted_counts_can_run_twice_for_same_file(
notify_api,
mocker,
notify_db_session
):
valid_file = 'Letter1|Sent|1|sorted\nLetter2|Sent|2|Unsorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=valid_file)
record_daily_sorted_counts(filename='NOTIFY-20170823160812-RSP.TXT')
daily_sorted_letter = dao_get_daily_sorted_letter_by_billing_day(date(2017, 8, 23))
assert daily_sorted_letter.unsorted_count == 1
assert daily_sorted_letter.sorted_count == 1
updated_file = 'Letter1|Sent|1|sorted\nLetter2|Sent|2|Unsorted\nLetter3|Sent|2|Unsorted'
mocker.patch('app.celery.tasks.s3.get_s3_file', return_value=updated_file)
record_daily_sorted_counts(filename='NOTIFY-20170823160812-RSP.TXT')
daily_sorted_letter = dao_get_daily_sorted_letter_by_billing_day(date(2017, 8, 23))
assert daily_sorted_letter.unsorted_count == 2
assert daily_sorted_letter.sorted_count == 1