mirror of
https://github.com/GSA/notifications-api.git
synced 2026-07-26 02:49:42 -04:00
Add endpoint for cancelling letters
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
"It’s 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'] == 'It’s 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'] == 'It’s 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'
|
||||
|
||||
Reference in New Issue
Block a user