Add endpoint for cancelling letters

This commit is contained in:
Katie Smith
2018-11-22 11:53:32 +00:00
parent 365c462e93
commit 1d67b55b16
4 changed files with 179 additions and 2 deletions

View File

@@ -162,3 +162,16 @@ def _move_s3_object(source_bucket, source_filename, target_bucket, target_filena
current_app.logger.info("Moved letter PDF: {}/{} to {}/{}".format(
source_bucket, source_filename, target_bucket, target_filename))
def letter_print_day(created_at):
bst_print_datetime = convert_utc_to_bst(created_at) + timedelta(hours=6, minutes=30)
bst_print_date = bst_print_datetime.date()
current_bst_date = convert_utc_to_bst(datetime.utcnow()).date()
if bst_print_date >= current_bst_date:
return 'today'
else:
print_date = bst_print_datetime.strftime('%d %B').lstrip('0')
return 'on {}'.format(print_date)

View File

@@ -7,6 +7,8 @@ from flask import (
current_app,
Blueprint
)
from notifications_utils.letter_timings import letter_can_be_cancelled
from notifications_utils.timezones import convert_utc_to_bst
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import NoResultFound
@@ -80,7 +82,8 @@ from app.errors import (
InvalidRequest,
register_errors
)
from app.models import Service, EmailBranding
from app.letters.utils import letter_print_day
from app.models import LETTER_TYPE, NOTIFICATION_CANCELLED, Service, EmailBranding
from app.schema_validation import validate
from app.service import statistics
from app.service.service_data_retention_schema import (
@@ -103,7 +106,7 @@ from app.schemas import (
notifications_filter_schema,
detailed_service_schema
)
from app.utils import pagination_links, convert_utc_to_bst
from app.utils import pagination_links
service_blueprint = Blueprint('service', __name__)
@@ -384,6 +387,31 @@ def get_notification_for_service(service_id, notification_id):
), 200
@service_blueprint.route('/<uuid:service_id>/notifications/<uuid:notification_id>/cancel', methods=['POST'])
def cancel_notification_for_service(service_id, notification_id):
notification = notifications_dao.get_notification_by_id(notification_id, service_id)
if not notification:
raise InvalidRequest('Notification not found', status_code=404)
elif notification.notification_type != LETTER_TYPE:
raise InvalidRequest('Notification cannot be cancelled - only letters can be cancelled', status_code=400)
elif not letter_can_be_cancelled(notification.status, notification.created_at):
print_day = letter_print_day(notification.created_at)
raise InvalidRequest(
"Its too late to cancel this letter. Printing started {} at 5.30pm".format(print_day),
status_code=400)
updated_notification = notifications_dao.update_notification_status_by_id(
notification_id,
NOTIFICATION_CANCELLED,
)
return jsonify(
notification_with_template_schema.dump(updated_notification).data
), 200
def search_for_notification_by_to_field(service_id, search_term, statuses, notification_type):
results = notifications_dao.dao_get_notifications_by_to_field(
service_id=service_id,

View File

@@ -10,6 +10,7 @@ from app.letters.utils import (
get_bucket_name_and_prefix_for_notification,
get_letter_pdf_filename,
get_letter_pdf,
letter_print_day,
upload_letter_pdf,
ScanErrorType, move_failed_pdf, get_folder_name
)
@@ -274,3 +275,27 @@ def test_get_folder_name_in_british_summer_time(notify_api, freeze_date, expecte
def test_get_folder_name_returns_empty_string_for_test_letter():
assert '' == get_folder_name(datetime.utcnow(), is_test_or_scan_letter=True)
@freeze_time('2017-07-07 20:00:00')
def test_letter_print_day_returns_today_if_letter_was_printed_after_1730_yesterday():
created_at = datetime(2017, 7, 6, 17, 30)
assert letter_print_day(created_at) == 'today'
@freeze_time('2017-07-07 16:30:00')
def test_letter_print_day_returns_today_if_letter_was_printed_today():
created_at = datetime(2017, 7, 7, 12, 0)
assert letter_print_day(created_at) == 'today'
@pytest.mark.parametrize('created_at, formatted_date', [
(datetime(2017, 7, 5, 16, 30), 'on 6 July'),
(datetime(2017, 7, 6, 16, 29), 'on 6 July'),
(datetime(2016, 8, 8, 10, 00), 'on 8 August'),
(datetime(2016, 12, 12, 17, 29), 'on 12 December'),
(datetime(2016, 12, 12, 17, 30), 'on 13 December'),
])
@freeze_time('2017-07-07 16:30:00')
def test_letter_print_day_returns_formatted_date_if_letter_printed_before_1730_yesterday(created_at, formatted_date):
assert letter_print_day(created_at) == formatted_date

View File

@@ -2790,3 +2790,114 @@ def test_get_organisation_for_service_id_return_empty_dict_if_service_not_in_org
service_id=fake_uuid
)
assert response == {}
def test_cancel_notification_for_service_raises_invalid_request_when_notification_is_not_found(
admin_request,
sample_service,
fake_uuid,
):
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_service.id,
notification_id=fake_uuid,
_expected_status=404
)
assert response['message'] == 'Notification not found'
assert response['result'] == 'error'
def test_cancel_notification_for_service_raises_invalid_request_when_notification_is_not_a_letter(
admin_request,
sample_notification,
):
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_notification.service_id,
notification_id=sample_notification.id,
_expected_status=400
)
assert response['message'] == 'Notification cannot be cancelled - only letters can be cancelled'
assert response['result'] == 'error'
@pytest.mark.parametrize('notification_status', [
'cancelled',
'sending',
'sent',
'delivered',
'pending',
'failed',
'technical-failure',
'temporary-failure',
'permanent-failure',
'validation-failed',
'virus-scan-failed',
'returned-letter',
])
@freeze_time('2018-07-07 12:00:00')
def test_cancel_notification_for_service_raises_invalid_request_when_letter_is_in_wrong_state_to_be_cancelled(
admin_request,
sample_letter_notification,
notification_status,
):
sample_letter_notification.status = notification_status
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_letter_notification.service_id,
notification_id=sample_letter_notification.id,
_expected_status=400
)
assert response['message'] == 'Its too late to cancel this letter. Printing started today at 5.30pm'
assert response['result'] == 'error'
@pytest.mark.parametrize('notification_status', ['created', 'pending-virus-check'])
@freeze_time('2018-07-07 16:00:00')
def test_cancel_notification_for_service_updates_letter_if_letter_is_in_cancellable_state(
admin_request,
sample_letter_notification,
notification_status,
):
sample_letter_notification.status = notification_status
sample_letter_notification.created_at = datetime.now()
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_letter_notification.service_id,
notification_id=sample_letter_notification.id,
)
assert response['status'] == 'cancelled'
@freeze_time('2017-12-12 17:30:00')
def test_cancel_notification_for_service_raises_error_if_its_too_late_to_cancel(
admin_request,
sample_letter_notification,
):
sample_letter_notification.created_at = datetime(2017, 12, 11, 17, 0)
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_letter_notification.service_id,
notification_id=sample_letter_notification.id,
_expected_status=400
)
assert response['message'] == 'Its too late to cancel this letter. Printing started on 11 December at 5.30pm'
assert response['result'] == 'error'
@freeze_time('2018-7-7 16:00:00')
def test_cancel_notification_for_service_updates_letter_if_still_time_to_cancel(
admin_request,
sample_letter_notification,
):
sample_letter_notification.created_at = datetime(2018, 7, 7, 10, 0)
response = admin_request.post(
'service.cancel_notification_for_service',
service_id=sample_letter_notification.service_id,
notification_id=sample_letter_notification.id,
)
assert response['status'] == 'cancelled'