Allow uploaded letters to be sent if valid

Added a send button which only appears on the page if the query string
indicates that the PDF is valid. Before actually sending, we check that
the service has the right permissions and that the metadata for the
letter confirms the letter is valid (because the query string can be
changed).
This commit is contained in:
Katie Smith
2019-09-09 12:22:52 +01:00
parent 7368245c9a
commit 79053dec93
6 changed files with 138 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ import uuid
from io import BytesIO
from flask import (
abort,
current_app,
flash,
redirect,
@@ -13,7 +14,7 @@ from notifications_utils.pdf import pdf_page_count
from PyPDF2.utils import PdfReadError
from requests import RequestException
from app import current_service, service_api_client
from app import current_service, notification_api_client, service_api_client
from app.extensions import antivirus_client
from app.main import main
from app.main.forms import PDFUploadForm
@@ -115,6 +116,7 @@ def uploaded_letter_preview(service_id, file_id):
original_filename=original_filename,
template=template,
status=status,
file_id=file_id,
)
@@ -130,3 +132,27 @@ def view_letter_upload_as_preview(service_id, file_id):
return TemplatePreview.from_invalid_pdf_file(pdf_file, page)
else:
return TemplatePreview.from_valid_pdf_file(pdf_file, page)
@main.route("/services/<service_id>/upload-letter/send", methods=['POST'])
@user_has_permissions('send_messages', restrict_admin_usage=True)
def send_uploaded_letter(service_id):
filename = request.form['filename']
file_id = request.form['file_id']
if not (current_service.has_permission('letter') and current_service.has_permission('upload_letters')):
abort(403)
file_location = get_transient_letter_file_location(service_id, file_id)
_, metadata = get_letter_pdf_and_metadata(file_location)
if metadata.get('status') != 'valid':
abort(403)
notification_api_client.send_precompiled_letter(service_id, filename, file_id)
return redirect(url_for(
'.view_notification',
service_id=service_id,
notification_id=file_id,
))

View File

@@ -244,6 +244,7 @@ class HeaderNavigation(Navigation):
'send_test',
'send_test_preview',
'send_test_step',
'send_uploaded_letter',
'service_add_email_reply_to',
'service_add_letter_contact',
'service_add_sms_sender',
@@ -556,6 +557,7 @@ class MainNavigation(Navigation):
'robots',
'security',
'send_notification',
'send_uploaded_letter',
'service_dashboard_updates',
'service_delete_email_reply_to',
'service_delete_letter_contact',
@@ -792,6 +794,7 @@ class CaseworkNavigation(Navigation):
'send_messages',
'send_notification',
'send_test_preview',
'send_uploaded_letter',
'service_add_email_reply_to',
'service_add_letter_contact',
'service_add_sms_sender',
@@ -1074,6 +1077,7 @@ class OrgNavigation(Navigation):
'send_test',
'send_test_preview',
'send_test_step',
'send_uploaded_letter',
'service_add_email_reply_to',
'service_add_letter_contact',
'service_add_sms_sender',

View File

@@ -59,6 +59,14 @@ class NotificationApiClient(NotifyAdminAPIClient):
data = _attach_current_user(data)
return self.post(url='/service/{}/send-notification'.format(service_id), data=data)
def send_precompiled_letter(self, service_id, filename, file_id):
data = {
'filename': filename,
'file_id': file_id,
}
data = _attach_current_user(data)
return self.post(url='/service/{}/send-pdf-letter'.format(service_id), data=data)
def get_notification(self, service_id, notification_id):
return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id))

View File

@@ -20,4 +20,18 @@
<div class="letter-sent">
{{ template|string }}
</div>
{% if status == 'valid' %}
<div class="js-stick-at-bottom-when-scrolling">
<form method="post" enctype="multipart/form-data" action="{{url_for(
'main.send_uploaded_letter',
service_id=current_service.id,
)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="hidden" name="filename" value="{{ original_filename }}" />
<input type="hidden" name="file_id" value="{{ file_id }}" />
<button type="submit" class="button">Send 1 letter</button>
</form>
</div>
{% endif %}
{% endblock %}

View File

@@ -50,6 +50,10 @@ def test_post_upload_letter_redirects_for_valid_file(mocker, client_request):
assert page.find('h1').text == 'tests/test_pdf_files/one_page_pdf.pdf'
assert not page.find(id='validation-error-message')
assert page.find('input', {'type': 'hidden', 'name': 'filename', 'value': 'tests/test_pdf_files/one_page_pdf.pdf'})
assert page.find('input', {'type': 'hidden', 'name': 'file_id', 'value': 'fake-uuid'})
assert page.find('button', {'type': 'submit'}).text == 'Send 1 letter'
def test_post_upload_letter_shows_letter_preview_for_valid_file(mocker, client_request):
letter_template = {'template_type': 'letter',
@@ -182,6 +186,7 @@ def test_post_upload_letter_with_invalid_file(mocker, client_request):
assert normalize_spaces(
page.find(id='validation-error-message').text
) == 'Validation failed'
assert not page.find('button', {'type': 'submit'})
def test_post_upload_letter_shows_letter_preview_for_invalid_file(mocker, client_request):
@@ -254,3 +259,66 @@ def test_uploaded_letter_preview(mocker, client_request):
assert page.find('h1').text == 'my_letter.pdf'
assert page.find('div', class_='letter-sent')
def test_send_uploaded_letter_sends_letter_and_redirects_to_notification_page(mocker, service_one, client_request):
mocker.patch('app.main.views.uploads.get_letter_pdf_and_metadata', return_value=('file', {'status': 'valid'}))
mock_send = mocker.patch('app.main.views.uploads.notification_api_client.send_precompiled_letter')
service_one['permissions'] = ['letter', 'upload_letters']
file_id = 'abcd-1234'
client_request.post(
'main.send_uploaded_letter',
service_id=SERVICE_ONE_ID,
_data={'filename': 'my_file.pdf', 'file_id': file_id},
_expected_redirect=url_for(
'main.view_notification',
service_id=SERVICE_ONE_ID,
notification_id=file_id,
_external=True
)
)
mock_send.assert_called_once_with(SERVICE_ONE_ID, 'my_file.pdf', file_id)
@pytest.mark.parametrize('permissions', [
['email'],
['letter'],
['upload_letters'],
])
def test_send_uploaded_letter_when_service_does_not_have_correct_permissions(
mocker,
service_one,
client_request,
permissions,
):
mocker.patch('app.main.views.uploads.get_letter_pdf_and_metadata', return_value=('file', {'status': 'valid'}))
mock_send = mocker.patch('app.main.views.uploads.notification_api_client.send_precompiled_letter')
service_one['permissions'] = permissions
file_id = 'abcd-1234'
client_request.post(
'main.send_uploaded_letter',
service_id=SERVICE_ONE_ID,
_data={'filename': 'my_file.pdf', 'file_id': file_id},
_expected_status=403
)
assert not mock_send.called
def test_send_uploaded_letter_when_metadata_states_pdf_is_invalid(mocker, service_one, client_request):
mocker.patch('app.main.views.uploads.get_letter_pdf_and_metadata', return_value=('file', {'status': 'invalid'}))
mock_send = mocker.patch('app.main.views.uploads.notification_api_client.send_precompiled_letter')
service_one['permissions'] = ['letter', 'upload_letters']
file_id = 'abcd-1234'
client_request.post(
'main.send_uploaded_letter',
service_id=SERVICE_ONE_ID,
_data={'filename': 'my_file.pdf', 'file_id': file_id},
_expected_status=403
)
assert not mock_send.called

View File

@@ -59,6 +59,23 @@ def test_send_notification(mocker, logged_in_client, active_user_with_permission
)
def test_send_precompiled_letter(mocker, logged_in_client, active_user_with_permissions):
mock_post = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.post')
NotificationApiClient().send_precompiled_letter(
'abcd-1234',
'my_file.pdf',
'file-ID'
)
mock_post.assert_called_once_with(
url='/service/abcd-1234/send-pdf-letter',
data={
'filename': 'my_file.pdf',
'file_id': 'file-ID',
'created_by': active_user_with_permissions['id']
}
)
def test_get_notification(mocker):
mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get')
NotificationApiClient().get_notification('foo', 'bar')