Remove letters-related code (#175)

This deletes a big ol' chunk of code related to letters. It's not everything—there are still a few things that might be tied to sms/email—but it's the the heart of letters function. SMS and email function should be untouched by this.

Areas affected:

- Things obviously about letters
- PDF tasks, used for precompiling letters
- Virus scanning, used for those PDFs
- FTP, used to send letters to the printer
- Postage stuff
This commit is contained in:
Steven Reilly
2023-03-02 20:20:31 -05:00
committed by GitHub
parent b07b95f795
commit ff4190a8eb
141 changed files with 1108 additions and 12083 deletions

View File

@@ -1,5 +1,3 @@
import datetime
import pytest
from flask import json, url_for
@@ -67,7 +65,6 @@ def test_get_notification_by_id_returns_200(
'sent_at': sample_notification.sent_at,
'completed_at': sample_notification.completed_at(),
'scheduled_for': None,
'postage': None,
'provider_response': None
}
@@ -120,7 +117,6 @@ def test_get_notification_by_id_with_placeholders_returns_200(
'sent_at': sample_notification.sent_at,
'completed_at': sample_notification.completed_at(),
'scheduled_for': None,
'postage': None,
'provider_response': None
}
@@ -216,35 +212,6 @@ def test_get_notification_by_id_invalid_id(client, sample_notification, id):
"status_code": 400}
@pytest.mark.parametrize('created_at_month, postage, estimated_delivery', [
(12, 'second', '2000-12-06T16:00:00.000000Z'), # 4pm GMT in winter
(6, 'second', '2000-06-05T15:00:00.000000Z'), # 4pm BST in summer
(12, 'first', '2000-12-05T16:00:00.000000Z'), # 4pm GMT in winter
(6, 'first', '2000-06-03T15:00:00.000000Z'), # 4pm BST in summer (two days before 2nd class due to weekends)
])
def test_get_notification_adds_delivery_estimate_for_letters(
client,
sample_letter_notification,
created_at_month,
postage,
estimated_delivery,
):
sample_letter_notification.created_at = datetime.date(2000, created_at_month, 1)
sample_letter_notification.postage = postage
auth_header = create_service_authorization_header(service_id=sample_letter_notification.service_id)
response = client.get(
path='/v2/notifications/{}'.format(sample_letter_notification.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
json_response = json.loads(response.get_data(as_text=True))
assert response.status_code == 200
assert json_response['postage'] == postage
assert json_response['estimated_delivery'] == estimated_delivery
@pytest.mark.parametrize('template_type', ['sms', 'email'])
def test_get_notification_doesnt_have_delivery_estimate_for_non_letters(client, sample_service, template_type):
template = create_template(service=sample_service, template_type=template_type)
@@ -376,7 +343,7 @@ def test_get_all_notifications_filter_by_template_type_invalid_template_type(cli
assert json_response['status_code'] == 400
assert len(json_response['errors']) == 1
assert json_response['errors'][0]['message'] == "template_type orange is not one of [sms, email, letter]"
assert json_response['errors'][0]['message'] == "template_type orange is not one of [sms, email]"
def test_get_all_notifications_filter_by_single_status(client, sample_template):
@@ -415,7 +382,7 @@ def test_get_all_notifications_filter_by_status_invalid_status(client, sample_no
assert len(json_response['errors']) == 1
assert json_response['errors'][0]['message'] == "status elephant is not one of [cancelled, created, sending, " \
"sent, delivered, pending, failed, technical-failure, temporary-failure, permanent-failure, " \
"pending-virus-check, validation-failed, virus-scan-failed, returned-letter, accepted, received]"
"pending-virus-check, validation-failed, virus-scan-failed]"
def test_get_all_notifications_filter_by_multiple_statuses(client, sample_template):
@@ -583,11 +550,10 @@ def test_get_all_notifications_filter_multiple_query_parameters(client, sample_e
def test_get_all_notifications_renames_letter_statuses(
client,
sample_letter_notification,
sample_notification,
sample_email_notification,
):
auth_header = create_service_authorization_header(service_id=sample_letter_notification.service_id)
auth_header = create_service_authorization_header(service_id=sample_email_notification.service_id)
response = client.get(
path=url_for('v2_notifications.get_notifications'),
headers=[('Content-Type', 'application/json'), auth_header]
@@ -599,127 +565,5 @@ def test_get_all_notifications_renames_letter_statuses(
for noti in json_response['notifications']:
if noti['type'] == 'sms' or noti['type'] == 'email':
assert noti['status'] == 'created'
elif noti['type'] == 'letter':
assert noti['status'] == 'accepted'
else:
pytest.fail()
@pytest.mark.parametrize('db_status,expected_status', [
('created', 'accepted'),
('sending', 'accepted'),
('delivered', 'received'),
('pending', 'pending'),
('technical-failure', 'technical-failure')
])
def test_get_notifications_renames_letter_statuses(client, sample_letter_template, db_status, expected_status):
letter_noti = create_notification(
sample_letter_template,
status=db_status,
personalisation={'address_line_1': 'Mr Foo', 'address_line_2': '1 Bar Street', 'postcode': 'N1'}
)
auth_header = create_service_authorization_header(service_id=letter_noti.service_id)
response = client.get(
path=url_for('v2_notifications.get_notification_by_id', notification_id=letter_noti.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
json_response = json.loads(response.get_data(as_text=True))
assert response.status_code == 200
assert json_response['status'] == expected_status
def test_get_pdf_for_notification_returns_pdf_content(
client,
sample_letter_notification,
mocker,
):
mock_get_letter_pdf = mocker.patch(
'app.v2.notifications.get_notifications.get_letter_pdf_and_metadata', return_value=(b'foo', {
"message": "",
"invalid_pages": "",
"page_count": "1"
})
)
sample_letter_notification.status = 'created'
auth_header = create_service_authorization_header(service_id=sample_letter_notification.service_id)
response = client.get(
path=url_for('v2_notifications.get_pdf_for_notification', notification_id=sample_letter_notification.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 200
assert response.get_data() == b'foo'
mock_get_letter_pdf.assert_called_once_with(sample_letter_notification)
def test_get_pdf_for_notification_returns_400_if_pdf_not_found(
client,
sample_letter_notification,
mocker,
):
# if no files are returned get_letter_pdf throws StopIteration as the iterator runs out
mock_get_letter_pdf = mocker.patch(
'app.v2.notifications.get_notifications.get_letter_pdf_and_metadata',
side_effect=(StopIteration, {})
)
sample_letter_notification.status = 'created'
auth_header = create_service_authorization_header(service_id=sample_letter_notification.service_id)
response = client.get(
path=url_for('v2_notifications.get_pdf_for_notification', notification_id=sample_letter_notification.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 400
assert response.json['errors'] == [{
'error': 'PDFNotReadyError',
'message': 'PDF not available yet, try again later'
}]
mock_get_letter_pdf.assert_called_once_with(sample_letter_notification)
@pytest.mark.parametrize('status, expected_message', [
('virus-scan-failed', 'File did not pass the virus scan'),
('technical-failure', 'PDF not available for letters in status technical-failure'),
])
def test_get_pdf_for_notification_only_returns_pdf_content_if_right_status(
client,
sample_letter_notification,
mocker,
status,
expected_message
):
mock_get_letter_pdf = mocker.patch(
'app.v2.notifications.get_notifications.get_letter_pdf_and_metadata', return_value=(b'foo', {
"message": "",
"invalid_pages": "",
"page_count": "1"
})
)
sample_letter_notification.status = status
auth_header = create_service_authorization_header(service_id=sample_letter_notification.service_id)
response = client.get(
path=url_for('v2_notifications.get_pdf_for_notification', notification_id=sample_letter_notification.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 400
assert response.json['errors'] == [{
'error': 'BadRequestError',
'message': expected_message
}]
assert mock_get_letter_pdf.called is False
def test_get_pdf_for_notification_fails_for_non_letters(client, sample_notification):
auth_header = create_service_authorization_header(service_id=sample_notification.service_id)
response = client.get(
path=url_for('v2_notifications.get_pdf_for_notification', notification_id=sample_notification.id),
headers=[('Content-Type', 'application/json'), auth_header]
)
assert response.status_code == 400
assert response.json['errors'] == [{'error': 'BadRequestError', 'message': 'Notification is not a letter'}]

View File

@@ -45,7 +45,7 @@ def test_get_notifications_request_invalid_statuses(
partial_error_status = "is not one of " \
"[cancelled, created, sending, sent, delivered, pending, failed, " \
"technical-failure, temporary-failure, permanent-failure, pending-virus-check, " \
"validation-failed, virus-scan-failed, returned-letter, accepted, received]"
"validation-failed, virus-scan-failed]"
with pytest.raises(ValidationError) as e:
validate({'status': invalid_statuses + valid_statuses}, get_notifications_request)
@@ -67,7 +67,7 @@ def test_get_notifications_request_invalid_statuses(
def test_get_notifications_request_invalid_template_types(
invalid_template_types, valid_template_types
):
partial_error_template_type = "is not one of [sms, email, letter]"
partial_error_template_type = "is not one of [sms, email]"
with pytest.raises(ValidationError) as e:
validate({'template_type': invalid_template_types + valid_template_types}, get_notifications_request)
@@ -93,12 +93,12 @@ def test_get_notifications_request_invalid_statuses_and_template_types():
for invalid_status in ["elephant", "giraffe"]:
assert "status {} is not one of [cancelled, created, sending, sent, delivered, " \
"pending, failed, technical-failure, temporary-failure, permanent-failure, " \
"pending-virus-check, validation-failed, virus-scan-failed, returned-letter, accepted, received]".format(
"pending-virus-check, validation-failed, virus-scan-failed]".format(
invalid_status
) in error_messages
for invalid_template_type in ["orange", "avocado"]:
assert "template_type {} is not one of [sms, email, letter]" \
assert "template_type {} is not one of [sms, email]" \
.format(invalid_template_type) in error_messages

View File

@@ -1,740 +0,0 @@
import uuid
from unittest.mock import ANY
import pytest
from flask import json, url_for
from app.config import QueueNames
from app.models import (
EMAIL_TYPE,
INTERNATIONAL_LETTERS,
KEY_TYPE_NORMAL,
KEY_TYPE_TEAM,
KEY_TYPE_TEST,
LETTER_TYPE,
NOTIFICATION_CREATED,
NOTIFICATION_DELIVERED,
NOTIFICATION_PENDING_VIRUS_CHECK,
NOTIFICATION_SENDING,
SMS_TYPE,
Job,
Notification,
)
from app.notifications.process_letter_notifications import (
create_letter_notification,
)
from app.schema_validation import validate
from app.v2.errors import RateLimitError
from app.v2.notifications.notification_schemas import post_letter_response
from tests import create_service_authorization_header
from tests.app.db import create_letter_contact, create_service, create_template
from tests.conftest import set_config_values
test_address = {
'address_line_1': 'test 1',
'address_line_2': 'test 2',
'postcode': 'test pc'
}
def letter_request(client, data, service_id, key_type=KEY_TYPE_NORMAL, _expected_status=201, precompiled=False):
if precompiled:
url = url_for('v2_notifications.post_precompiled_letter_notification')
else:
url = url_for('v2_notifications.post_notification', notification_type=LETTER_TYPE)
resp = client.post(
url,
data=json.dumps(data),
headers=[
('Content-Type', 'application/json'),
create_service_authorization_header(service_id=service_id, key_type=key_type)
]
)
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):
mock = mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.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)
assert validate(resp_json, post_letter_response) == resp_json
assert Job.query.count() == 0
notification = Notification.query.one()
assert notification.status == NOTIFICATION_CREATED
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']
assert not notification.reply_to_text
mock.assert_called_once_with([str(notification.id)], queue=QueueNames.CREATE_LETTERS_PDF)
def test_post_letter_notification_sets_postage(
client, notify_db_session, mocker
):
service = create_service(service_permissions=[LETTER_TYPE])
template = create_template(service, template_type="letter", postage="first")
mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(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'
}
}
resp_json = letter_request(client, data, service_id=service.id)
assert validate(resp_json, post_letter_response) == resp_json
notification = Notification.query.one()
assert notification.postage == "first"
def test_post_letter_notification_formats_postcode(
client, notify_db_session, mocker
):
service = create_service(service_permissions=[LETTER_TYPE])
template = create_template(service, template_type="letter")
mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(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'
}
}
resp_json = letter_request(client, data, service_id=service.id)
assert validate(resp_json, post_letter_response) == resp_json
notification = Notification.query.one()
# We store what the client gives us, and only reformat it when
# generating the PDF
assert notification.personalisation["postcode"] == ' Sw1 1aa '
def test_post_letter_notification_stores_country(
client, notify_db_session, mocker
):
service = create_service(service_permissions=[LETTER_TYPE, INTERNATIONAL_LETTERS])
template = create_template(service, template_type="letter")
mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(template.id),
'personalisation': {
'address_line_1': 'Kaiser Wilhelm II',
'address_line_2': 'Kronprinzenpalais',
'address_line_5': ' deutschland ',
}
}
resp_json = letter_request(client, data, service_id=service.id)
assert validate(resp_json, post_letter_response) == resp_json
notification = Notification.query.one()
# In the personalisation we store what the client gives us
assert notification.personalisation["address_line_1"] == 'Kaiser Wilhelm II'
assert notification.personalisation["address_line_2"] == 'Kronprinzenpalais'
assert notification.personalisation["address_line_5"] == ' deutschland '
# In the to field we store the whole address with the canonical country
assert notification.to == (
'Kaiser Wilhelm II\n'
'Kronprinzenpalais\n'
'Germany'
)
assert notification.postage == 'europe'
assert notification.international
def test_post_letter_notification_international_sets_rest_of_world(
client, notify_db_session, mocker
):
service = create_service(service_permissions=[LETTER_TYPE, INTERNATIONAL_LETTERS])
template = create_template(service, template_type="letter")
mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(template.id),
'personalisation': {
'address_line_1': 'Prince Harry',
'address_line_2': 'Toronto',
'address_line_5': 'Canada',
}
}
resp_json = letter_request(client, data, service_id=service.id)
assert validate(resp_json, post_letter_response) == resp_json
notification = Notification.query.one()
assert notification.postage == 'rest-of-world'
@pytest.mark.parametrize('permissions, personalisation, expected_error', (
(
[LETTER_TYPE],
{
'address_line_1': 'Her Royal Highness Queen Elizabeth II',
'address_line_2': 'Buckingham Palace',
'address_line_3': 'London',
'postcode': 'not a real postcode',
'name': 'Lizzie'
},
'Must be a real UK postcode',
),
(
[LETTER_TYPE],
{
'address_line_1': 'Her Royal Highness Queen Elizabeth II',
'address_line_2': ']Buckingham Palace',
'postcode': 'SW1A 1AA',
'name': 'Lizzie'
},
'Address lines must not start with any of the following characters: @ ( ) = [ ] ” \\ / , < >',
),
(
[LETTER_TYPE, INTERNATIONAL_LETTERS],
{
'address_line_1': 'Her Royal Highness Queen Elizabeth II',
'address_line_2': 'Buckingham Palace',
'address_line_3': 'London',
'postcode': 'not a real postcode',
'name': 'Lizzie'
},
'Last line of address must be a real UK postcode or another country',
),
))
def test_post_letter_notification_throws_error_for_bad_address(
client, notify_db_session, mocker, permissions, personalisation, expected_error
):
service = create_service(service_permissions=permissions)
template = create_template(service, template_type="letter", postage="first")
mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(template.id),
'personalisation': personalisation
}
error_json = letter_request(client, data, service_id=service.id, _expected_status=400)
assert error_json['status_code'] == 400
assert error_json['errors'] == [{
'error': 'ValidationError',
'message': expected_error
}]
@pytest.mark.parametrize('env', [
'staging',
'live',
])
def test_post_letter_notification_with_test_key_creates_pdf_and_sets_status_to_delivered(
notify_api, client, sample_letter_template, mocker, env):
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'
},
'reference': 'foo'
}
fake_create_letter_task = mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
fake_create_dvla_response_task = mocker.patch(
'app.celery.research_mode_tasks.create_fake_letter_response_file.apply_async')
with set_config_values(notify_api, {
'NOTIFY_ENVIRONMENT': env
}):
letter_request(client, data, service_id=sample_letter_template.service_id, key_type=KEY_TYPE_TEST)
notification = Notification.query.one()
fake_create_letter_task.assert_called_once_with([str(notification.id)], queue='research-mode-tasks')
assert not fake_create_dvla_response_task.called
assert notification.status == NOTIFICATION_DELIVERED
assert notification.updated_at is not None
@pytest.mark.parametrize('env', [
'development',
'preview',
])
def test_post_letter_notification_with_test_key_creates_pdf_and_sets_status_to_sending_and_sends_fake_response_file(
notify_api, client, sample_letter_template, mocker, env):
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'
},
'reference': 'foo'
}
fake_create_letter_task = mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
fake_create_dvla_response_task = mocker.patch(
'app.celery.research_mode_tasks.create_fake_letter_response_file.apply_async')
with set_config_values(notify_api, {
'NOTIFY_ENVIRONMENT': env
}):
letter_request(client, data, service_id=sample_letter_template.service_id, key_type=KEY_TYPE_TEST)
notification = Notification.query.one()
fake_create_letter_task.assert_called_once_with([str(notification.id)], queue='research-mode-tasks')
assert fake_create_dvla_response_task.called
assert notification.status == NOTIFICATION_SENDING
def test_post_letter_notification_returns_400_and_missing_template(
client,
sample_service_full_permissions
):
data = {
'template_id': str(uuid.uuid4()),
'personalisation': test_address
}
error_json = letter_request(client, data, service_id=sample_service_full_permissions.id, _expected_status=400)
assert error_json['status_code'] == 400
assert error_json['errors'] == [{'error': 'BadRequestError', 'message': 'Template not found'}]
def test_post_letter_notification_returns_400_for_empty_personalisation(
client,
sample_service_full_permissions,
sample_letter_template
):
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {'address_line_1': '', 'address_line_2': '', 'postcode': ''}
}
error_json = letter_request(client, data, service_id=sample_service_full_permissions.id, _expected_status=400)
assert error_json['status_code'] == 400
assert all([e['error'] == 'ValidationError' for e in error_json['errors']])
assert set([e['message'] for e in error_json['errors']]) == {
'Address must be at least 3 lines',
}
def test_post_notification_returns_400_for_missing_letter_contact_block_personalisation(
client,
sample_service,
):
letter_contact_block = create_letter_contact(
service=sample_service, contact_block='((contact block))', is_default=True
)
template = create_template(
service=sample_service,
template_type='letter',
reply_to=letter_contact_block.id,
)
data = {
'template_id': str(template.id),
'personalisation': {
'address_line_1': 'Line 1',
'address_line_2': 'Line 2',
'postcode': 'SW1A 1AA',
},
}
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': 'Missing personalisation: contact block'
}]
def test_notification_returns_400_for_missing_template_field(
client,
sample_service_full_permissions
):
data = {
'personalisation': test_address
}
error_json = letter_request(client, data, service_id=sample_service_full_permissions.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_notification_returns_400_if_address_doesnt_have_underscores(
client,
sample_letter_template
):
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {
'address line 1': 'Her Royal Highness Queen Elizabeth II',
'address-line-2': 'Buckingham Palace',
'postcode': 'SW1 1AA',
}
}
error_json = letter_request(client, data, service_id=sample_letter_template.service_id, _expected_status=400)
assert error_json['status_code'] == 400
assert error_json['errors'] == [
{
'error': 'ValidationError',
'message': 'Address must be at least 3 lines'
}
]
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': test_address
}
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, expected_status, expected_message', [
(
{'service_permissions': [EMAIL_TYPE, SMS_TYPE]},
400,
'Service is not allowed to send letters',
),
(
{'restricted': True},
403,
'Cannot send letters when service is in trial mode',
)
])
def test_post_letter_notification_returns_403_if_not_allowed_to_send_notification(
client,
notify_db_session,
service_args,
expected_status,
expected_message,
):
service = create_service(**service_args)
template = create_template(service, template_type=LETTER_TYPE)
data = {
'template_id': str(template.id),
'personalisation': test_address
}
error_json = letter_request(client, data, service_id=service.id, _expected_status=expected_status)
assert error_json['status_code'] == expected_status
assert error_json['errors'] == [
{'error': 'BadRequestError', 'message': expected_message}
]
def test_post_letter_notification_doesnt_accept_team_key(client, sample_letter_template, mocker):
mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(sample_letter_template.id),
'personalisation': {'address_line_1': 'Foo', 'address_line_2': 'Bar', 'postcode': 'Baz'}
}
error_json = letter_request(
client,
data,
sample_letter_template.service_id,
key_type=KEY_TYPE_TEAM,
_expected_status=403
)
assert error_json['status_code'] == 403
assert error_json['errors'] == [{'error': 'BadRequestError', 'message': 'Cannot send letters with a team api key'}]
def test_post_letter_notification_doesnt_send_in_trial(client, sample_trial_letter_template, mocker):
mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
'template_id': str(sample_trial_letter_template.id),
'personalisation': {'address_line_1': 'Foo', 'address_line_2': 'Bar', 'postcode': 'Baz'}
}
error_json = letter_request(
client,
data,
sample_trial_letter_template.service_id,
_expected_status=403
)
assert error_json['status_code'] == 403
assert error_json['errors'] == [
{'error': 'BadRequestError', 'message': 'Cannot send letters when service is in trial mode'}]
def test_post_letter_notification_is_delivered_but_still_creates_pdf_if_in_trial_mode_and_using_test_key(
client,
sample_trial_letter_template,
mocker
):
fake_create_letter_task = mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
data = {
"template_id": sample_trial_letter_template.id,
"personalisation": {'address_line_1': 'Foo', 'address_line_2': 'Bar', 'postcode': 'BA5 5AB'}
}
letter_request(client, data=data, service_id=sample_trial_letter_template.service_id, key_type=KEY_TYPE_TEST)
notification = Notification.query.one()
assert notification.status == NOTIFICATION_DELIVERED
fake_create_letter_task.assert_called_once_with([str(notification.id)], queue='research-mode-tasks')
def test_post_letter_notification_is_delivered_and_has_pdf_uploaded_to_test_letters_bucket_using_test_key(
client,
notify_user,
mocker
):
sample_letter_service = create_service(service_permissions=['letter'])
mocker.patch('app.celery.letters_pdf_tasks.notify_celery.send_task')
s3mock = mocker.patch('app.v2.notifications.post_notifications.upload_letter_pdf', return_value='test.pdf')
data = {
"reference": "letter-reference",
"content": "bGV0dGVyLWNvbnRlbnQ="
}
letter_request(
client,
data=data,
service_id=str(sample_letter_service.id),
key_type=KEY_TYPE_TEST,
precompiled=True)
notification = Notification.query.one()
assert notification.status == NOTIFICATION_PENDING_VIRUS_CHECK
s3mock.assert_called_once_with(ANY, b'letter-content', precompiled=True)
def test_post_letter_notification_ignores_reply_to_text_for_service(
client, notify_db_session, mocker
):
mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
service = create_service(service_permissions=[LETTER_TYPE])
create_letter_contact(service=service, contact_block='ignored', is_default=True)
template = create_template(service=service, template_type='letter')
data = {
"template_id": template.id,
"personalisation": {'address_line_1': 'Foo', 'address_line_2': 'Bar', 'postcode': 'BA5 5AB'}
}
letter_request(client, data=data, service_id=service.id, key_type=KEY_TYPE_NORMAL)
notifications = Notification.query.all()
assert len(notifications) == 1
assert notifications[0].reply_to_text is None
def test_post_letter_notification_persists_notification_reply_to_text_for_template(
client, notify_db_session, mocker
):
mocker.patch('app.celery.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
service = create_service(service_permissions=[LETTER_TYPE])
create_letter_contact(service=service, contact_block='the default', is_default=True)
template_letter_contact = create_letter_contact(service=service, contact_block='not the default', is_default=False)
template = create_template(service=service, template_type='letter', reply_to=template_letter_contact.id)
data = {
"template_id": template.id,
"personalisation": {'address_line_1': 'Foo', 'address_line_2': 'Bar', 'postcode': 'BA5 5AB'}
}
letter_request(client, data=data, service_id=service.id, key_type=KEY_TYPE_NORMAL)
notifications = Notification.query.all()
assert len(notifications) == 1
assert notifications[0].reply_to_text == 'not the default'
def test_post_precompiled_letter_with_invalid_base64(client, notify_user, mocker):
sample_service = create_service(service_permissions=['letter'])
mocker.patch('app.v2.notifications.post_notifications.upload_letter_pdf')
data = {
"reference": "letter-reference",
"content": "hi"
}
auth_header = create_service_authorization_header(service_id=sample_service.id)
response = client.post(
path="v2/notifications/letter",
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 400, response.get_data(as_text=True)
resp_json = json.loads(response.get_data(as_text=True))
assert resp_json['errors'][0]['message'] == 'Cannot decode letter content (invalid base64 encoding)'
assert not Notification.query.first()
@pytest.mark.parametrize('notification_postage, expected_postage', [
('second', 'second'),
('first', 'first'),
(None, 'second')
])
def test_post_precompiled_letter_notification_returns_201(
client, notify_user, mocker, notification_postage, expected_postage
):
sample_service = create_service(service_permissions=['letter'])
s3mock = mocker.patch('app.v2.notifications.post_notifications.upload_letter_pdf')
mocker.patch('app.celery.letters_pdf_tasks.notify_celery.send_task')
data = {
"reference": "letter-reference",
"content": "bGV0dGVyLWNvbnRlbnQ="
}
if notification_postage:
data["postage"] = notification_postage
auth_header = create_service_authorization_header(service_id=sample_service.id)
response = client.post(
path="v2/notifications/letter",
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 201, response.get_data(as_text=True)
s3mock.assert_called_once_with(ANY, b'letter-content', precompiled=True)
notification = Notification.query.one()
assert notification.billable_units == 0
assert notification.status == NOTIFICATION_PENDING_VIRUS_CHECK
assert notification.postage == expected_postage
resp_json = json.loads(response.get_data(as_text=True))
assert resp_json == {'id': str(notification.id), 'reference': 'letter-reference', 'postage': expected_postage}
def test_post_precompiled_letter_notification_if_s3_upload_fails_notification_is_not_persisted(
client, notify_user, mocker
):
sample_service = create_service(service_permissions=['letter'])
persist_letter_mock = mocker.patch('app.v2.notifications.post_notifications.create_letter_notification',
side_effect=create_letter_notification)
s3mock = mocker.patch('app.v2.notifications.post_notifications.upload_letter_pdf', side_effect=Exception())
mocker.patch('app.celery.letters_pdf_tasks.notify_celery.send_task')
data = {
"reference": "letter-reference",
"content": "bGV0dGVyLWNvbnRlbnQ="
}
auth_header = create_service_authorization_header(service_id=sample_service.id)
with pytest.raises(expected_exception=Exception):
client.post(
path="v2/notifications/letter",
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert s3mock.called
assert persist_letter_mock.called
assert Notification.query.count() == 0
def test_post_letter_notification_throws_error_for_invalid_postage(client, notify_user, mocker):
sample_service = create_service(service_permissions=['letter'])
data = {
"reference": "letter-reference",
"content": "bGV0dGVyLWNvbnRlbnQ=",
"postage": "space unicorn"
}
auth_header = create_service_authorization_header(service_id=sample_service.id)
response = client.post(
path="v2/notifications/letter",
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), auth_header])
assert response.status_code == 400, response.get_data(as_text=True)
resp_json = json.loads(response.get_data(as_text=True))
assert resp_json['errors'][0]['message'] == "postage invalid. It must be first, second, europe or rest-of-world."
assert not Notification.query.first()
@pytest.mark.parametrize('content_type',
['application/json', 'application/text'])
def test_post_letter_notification_when_payload_is_invalid_json_returns_400(
client, sample_service, content_type):
auth_header = create_service_authorization_header(service_id=sample_service.id)
payload_not_json = {
"template_id": "dont-convert-to-json",
}
response = client.post(
path='/v2/notifications/letter',
data=payload_not_json,
headers=[('Content-Type', content_type), auth_header],
)
assert response.status_code == 400
error_msg = json.loads(response.get_data(as_text=True))["errors"][0]["message"]
assert error_msg == 'Invalid JSON supplied in POST data'

View File

@@ -57,7 +57,6 @@ def test_post_sms_notification_returns_201(client, sample_template_with_placehol
assert len(notifications) == 1
assert notifications[0].status == NOTIFICATION_CREATED
notification_id = notifications[0].id
assert notifications[0].postage is None
assert notifications[0].document_download_count is None
assert resp_json['id'] == str(notification_id)
assert resp_json['reference'] == reference
@@ -358,7 +357,6 @@ def test_post_notification_returns_400_and_missing_template(client, sample_servi
@pytest.mark.parametrize("notification_type, key_send_to, send_to", [
("sms", "phone_number", "+447700900855"),
("email", "email_address", "sample@email.com"),
("letter", "personalisation", {"address_line_1": "The queen", "postcode": "SW1 1AA"})
])
def test_post_notification_returns_401_and_well_formed_auth_error(client, sample_template,
notification_type, key_send_to, send_to):
@@ -429,7 +427,6 @@ def test_post_email_notification_returns_201(client, sample_email_template_with_
assert validate(resp_json, post_email_response) == resp_json
notification = Notification.query.one()
assert notification.status == NOTIFICATION_CREATED
assert notification.postage is None
assert resp_json['id'] == str(notification.id)
assert resp_json['reference'] == reference
assert notification.reference is None
@@ -1156,32 +1153,3 @@ def test_post_notifications_doesnt_use_save_queue_for_test_notifications(
assert mock_send_task.called
assert not save_task.called
assert len(Notification.query.all()) == 1
def test_post_notification_does_not_use_save_queue_for_letters(client, sample_letter_template, mocker):
mock_save = mocker.patch("app.v2.notifications.post_notifications.save_email_or_sms_to_queue")
mock_create_pdf_task = mocker.patch('app.celery.tasks.letters_pdf_tasks.get_pdf_for_templated_letter.apply_async')
with set_config_values(current_app, {
'HIGH_VOLUME_SERVICE': [str(sample_letter_template.service_id)],
}):
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',
}
}
response = client.post(
path='/v2/notifications/letter',
data=json.dumps(data),
headers=[('Content-Type', 'application/json'),
create_service_authorization_header(service_id=sample_letter_template.service_id)]
)
assert response.status_code == 201
json_resp = response.get_json()
assert not mock_save.called
mock_create_pdf_task.assert_called_once_with([str(json_resp['id'])], queue='create-letters-pdf-tasks')

View File

@@ -1,29 +1,23 @@
import pytest
from flask import json
from app.models import EMAIL_TYPE, LETTER_TYPE, SMS_TYPE, TEMPLATE_TYPES
from app.models import EMAIL_TYPE, SMS_TYPE, TEMPLATE_TYPES
from app.utils import DATETIME_FORMAT
from tests import create_service_authorization_header
from tests.app.db import create_letter_contact, create_template
from tests.app.db import create_template
valid_version_params = [None, 1]
@pytest.mark.parametrize("tmp_type, expected_name, expected_subject,postage", [
(SMS_TYPE, 'sms Template Name', None, None),
(EMAIL_TYPE, 'email Template Name', 'Template subject', None),
(LETTER_TYPE, 'letter Template Name', 'Template subject', "second")
@pytest.mark.parametrize("tmp_type, expected_name, expected_subject", [
(SMS_TYPE, 'sms Template Name', None),
(EMAIL_TYPE, 'email Template Name', 'Template subject'),
])
@pytest.mark.parametrize("version", valid_version_params)
def test_get_template_by_id_returns_200(
client, sample_service, tmp_type, expected_name, expected_subject, version, postage
client, sample_service, tmp_type, expected_name, expected_subject, version
):
letter_contact_block_id = None
if tmp_type == 'letter':
letter_contact_block = create_letter_contact(sample_service, "Buckingham Palace, London, SW1A 1AA")
letter_contact_block_id = letter_contact_block.id
template = create_template(sample_service, template_type=tmp_type, contact_block_id=(letter_contact_block_id))
template = create_template(sample_service, template_type=tmp_type)
auth_header = create_service_authorization_header(service_id=sample_service.id)
version_path = '/version/{}'.format(version) if version else ''
@@ -47,8 +41,6 @@ def test_get_template_by_id_returns_200(
"subject": expected_subject,
'name': expected_name,
'personalisation': {},
'postage': postage,
'letter_contact_block': letter_contact_block.contact_block if letter_contact_block_id else None,
}
assert json_response == expected_response
@@ -105,44 +97,6 @@ def test_get_template_by_id_returns_placeholders(
assert json_response['personalisation'] == expected_personalisation
@pytest.mark.parametrize("version", valid_version_params)
def test_get_letter_template_by_id_returns_placeholders(
client,
sample_service,
version,
):
contact_block = create_letter_contact(
service=sample_service,
contact_block='((contact block))',
)
template = create_template(
sample_service,
template_type=LETTER_TYPE,
subject="((letterSubject))",
content="((letter_content))",
reply_to=contact_block.id,
)
auth_header = create_service_authorization_header(service_id=sample_service.id)
version_path = '/version/{}'.format(version) if version else ''
response = client.get(path='/v2/template/{}{}'.format(template.id, version_path),
headers=[('Content-Type', 'application/json'), auth_header])
json_response = json.loads(response.get_data(as_text=True))
assert json_response['personalisation'] == {
"letterSubject": {
"required": True,
},
"letter_content": {
"required": True,
},
"contact block": {
"required": True,
},
}
def test_get_template_with_non_existent_template_id_returns_404(client, fake_uuid, sample_service):
auth_header = create_service_authorization_header(service_id=sample_service.id)

View File

@@ -1,7 +1,7 @@
import pytest
from flask import json
from app.models import EMAIL_TYPE, LETTER_TYPE, TEMPLATE_TYPES
from app.models import EMAIL_TYPE, TEMPLATE_TYPES
from tests import create_service_authorization_header
from tests.app.db import create_template
@@ -96,10 +96,8 @@ def test_valid_post_template_returns_200(
assert resp_json['id'] == str(template.id)
if tmp_type in {EMAIL_TYPE, LETTER_TYPE}:
assert expected_subject in resp_json['subject']
if tmp_type == EMAIL_TYPE:
assert expected_subject in resp_json['subject']
assert resp_json['html'] == expected_html
else:
assert resp_json['html'] is None
@@ -107,15 +105,13 @@ def test_valid_post_template_returns_200(
assert expected_content in resp_json['body']
@pytest.mark.parametrize("template_type", (EMAIL_TYPE, LETTER_TYPE))
def test_email_and_letter_templates_not_rendered_into_content(
def test_email_templates_not_rendered_into_content(
client,
sample_service,
template_type,
sample_service
):
template = create_template(
sample_service,
template_type=template_type,
template_type=EMAIL_TYPE,
subject='Test',
content=(
'Hello\n'

View File

@@ -34,7 +34,6 @@ valid_json_get_response_with_optionals = {
'body': 'some body',
'subject': "some subject",
'name': 'some name',
'postage': 'first',
}
valid_request_args = [{"id": str(uuid.uuid4()), "version": 1}, {"id": str(uuid.uuid4())}]
@@ -80,7 +79,6 @@ valid_json_post_response_with_optionals = {
'version': 1,
'body': "some body",
'subject': 'some subject',
'postage': 'second',
'html': '<p>some body</p>',
}

View File

@@ -112,7 +112,7 @@ def test_get_all_templates_for_invalid_type_returns_400(client, sample_service):
'status_code': 400,
'errors': [
{
'message': 'type coconut is not one of [sms, email, letter]',
'message': 'type coconut is not one of [sms, email]',
'error': 'ValidationError'
}
]

View File

@@ -241,7 +241,7 @@ def test_get_all_template_request_schema_against_invalid_args_is_invalid(templat
assert errors['status_code'] == 400
assert len(errors['errors']) == 1
assert errors['errors'][0]['message'] == 'type unknown is not one of [sms, email, letter]'
assert errors['errors'][0]['message'] == 'type unknown is not one of [sms, email]'
@pytest.mark.parametrize("response", valid_json_get_all_response)