diff --git a/app/main/forms.py b/app/main/forms.py index ea89af61d..9901cb89a 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -839,3 +839,13 @@ class SetSenderForm(StripWhitespaceForm): self.sender.label.text = kwargs['sender_label'] sender = RadioField() + + +class SetTemplateSenderForm(StripWhitespaceForm): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.sender.choices = kwargs['sender_choices'] + self.sender.label.text = 'Select your sender' + + sender = RadioField() diff --git a/app/main/views/notifications.py b/app/main/views/notifications.py index d222059e4..aa7e82ccc 100644 --- a/app/main/views/notifications.py +++ b/app/main/views/notifications.py @@ -31,6 +31,8 @@ from app.utils import ( @user_has_permissions('view_activity', admin_override=True) def view_notification(service_id, notification_id): notification = notification_api_client.get_notification(service_id, str(notification_id)) + notification['template'].update({'reply_to_text': notification['reply_to_text']}) + template = get_template( notification['template'], current_service, @@ -82,6 +84,7 @@ def view_letter_notification_as_preview(service_id, notification_id, filetype): abort(404) notification = notification_api_client.get_notification(service_id, notification_id) + notification['template'].update({'reply_to_text': notification['reply_to_text']}) template = get_template( notification['template'], diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 7631b0f39..e1e11373b 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -553,6 +553,10 @@ def service_add_letter_contact(service_id): contact_block=form.letter_contact_block.data.replace('\r', '') or None, is_default=first_contact_block if first_contact_block else form.is_default.data ) + if request.args.get('from_template'): + return redirect( + url_for('.set_template_sender', service_id=service_id, template_id=request.args.get('from_template')) + ) return redirect(url_for('.service_letter_contact_details', service_id=service_id)) return render_template( 'views/service-settings/letter-contact/add.html', diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 840483f0f..90f7d6b82 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -12,7 +12,9 @@ from flask import ( ) from flask_login import login_required, current_user from dateutil.parser import parse +from markupsafe import Markup +from notifications_utils.formatters import nl2br from notifications_utils.recipients import first_column_headings from notifications_utils.template import LetterDVLATemplate from notifications_python_client.errors import HTTPError @@ -26,8 +28,9 @@ from app.main.forms import ( EmailTemplateForm, LetterTemplateForm, SearchTemplatesForm, + SetTemplateSenderForm, ) -from app.main.views.send import get_example_csv_rows +from app.main.views.send import get_example_csv_rows, get_sender_details from app import service_api_client, current_service, template_statistics_client @@ -181,6 +184,7 @@ def view_letter_template_preview(service_id, template_id, filetype): abort(404) db_template = service_api_client.get_service_template(service_id, template_id)['data'] + return TemplatePreview.from_database_object(db_template, filetype, page=request.args.get('page')) @@ -373,7 +377,8 @@ def edit_service_template(service_id, template_id): 'subject': subject, 'template_type': template['template_type'], 'id': template['id'], - 'process_type': form.process_type.data + 'process_type': form.process_type.data, + 'reply_to_text': template['reply_to_text'] }, current_service) template_change = get_template(template, current_service).compare_to(new_template) if template_change.placeholders_added and not request.form.get('confirm'): @@ -564,6 +569,65 @@ def view_template_versions(service_id, template_id): ) +@main.route('/services//templates//set-template-sender', methods=['GET', 'POST']) +@login_required +@user_has_permissions('manage_templates', admin_override=True) +def set_template_sender(service_id, template_id): + template = service_api_client.get_service_template(service_id, template_id)['data'] + sender_details = get_template_sender_form_dict(service_id, template) + no_senders = sender_details.get('no_senders', False) + + form = SetTemplateSenderForm( + sender=sender_details['current_choice'], + sender_choices=sender_details['value_and_label'], + ) + option_hints = {sender_details['default_sender']: '(Default)'} + + if form.validate_on_submit(): + service_api_client.update_service_template_sender( + service_id, + template_id, + form.sender.data if form.sender.data else None, + ) + return redirect(url_for('.view_template', service_id=service_id, template_id=template_id)) + + return render_template( + 'views/templates/set-template-sender.html', + form=form, + template_id=template_id, + no_senders=no_senders, + option_hints=option_hints + ) + + +def get_template_sender_form_dict(service_id, template): + context = { + 'email': { + 'field_name': 'email_address' + }, + 'letter': { + 'field_name': 'contact_block' + }, + 'sms': { + 'field_name': 'sms_sender' + } + }[template['template_type']] + + sender_format = context['field_name'] + service_senders = get_sender_details(service_id, template['template_type']) + context['default_sender'] = next( + (x['id'] for x in service_senders if x['is_default']), "Not set" + ) + if not service_senders: + context['no_senders'] = True + + context['value_and_label'] = [(sender['id'], Markup(nl2br(sender[sender_format]))) for sender in service_senders] + context['value_and_label'].insert(0, ('', 'Blank')) # Add blank option to start of list + + context['current_choice'] = template['service_letter_contact'] if template['service_letter_contact'] else '' + return context + + def get_last_use_message(template_name, template_statistics): try: most_recent_use = max( diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index 2b7b6ead4..848da40b4 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -187,6 +187,16 @@ class ServiceAPIClient(NotifyAdminAPIClient): ), ) + def update_service_template_sender(self, service_id, template_id, reply_to): + data = { + 'reply_to': reply_to, + } + data = _attach_current_user(data) + return self.post( + "/service/{0}/template/{1}".format(service_id, template_id), + data + ) + def get_service_template(self, service_id, template_id, version=None, *params): """ Retrieve a service template. diff --git a/app/template_previews.py b/app/template_previews.py index a3f5970e8..e4493f3a4 100644 --- a/app/template_previews.py +++ b/app/template_previews.py @@ -8,7 +8,7 @@ class TemplatePreview: @classmethod def from_database_object(cls, template, filetype, values=None, page=None): data = { - 'letter_contact_block': current_service['letter_contact_block'], + 'letter_contact_block': template.get('reply_to_text', ''), 'template': template, 'values': values, 'dvla_org_id': current_service['dvla_organisation'], diff --git a/app/templates/views/templates/_template.html b/app/templates/views/templates/_template.html index 1dd6a41ab..70bce8a26 100644 --- a/app/templates/views/templates/_template.html +++ b/app/templates/views/templates/_template.html @@ -35,11 +35,7 @@
{% if current_user.has_permissions(permissions=['manage_templates'], admin_override=True) and template.template_type == 'letter' %} Edit - {% if default_letter_contact_block_id %} - Edit - {% else %} - Edit - {% endif %} + Edit {% endif %} {{ template|string }}
diff --git a/app/templates/views/templates/set-template-sender.html b/app/templates/views/templates/set-template-sender.html new file mode 100644 index 000000000..c25ea1789 --- /dev/null +++ b/app/templates/views/templates/set-template-sender.html @@ -0,0 +1,32 @@ +{% extends "withnav_template.html" %} +{% from "components/radios.html" import radios, branding_radios %} +{% from "components/page-footer.html" import page_footer %} + +{% block service_page_title %} + Set letter contact block +{% endblock %} + +{% block maincolumn_content %} + +

Set letter contact block

+
+
+
+ {{ radios( + form.sender, + option_hints=option_hints, + hide_legend=True + ) }} + {{ page_footer( + 'Continue', + back_link=url_for('.view_template', service_id=current_service.id, template_id=template_id), + back_link_text='Back to template' + ) }} + {% if no_senders %} + Add new sender + {% endif %} +
+
+
+ +{% endblock %} diff --git a/app/utils.py b/app/utils.py index ba19eddc7..bbeebb691 100644 --- a/app/utils.py +++ b/app/utils.py @@ -300,11 +300,12 @@ def get_template( template, image_url=letter_preview_url, page_count=int(page_count), + contact_block=template['reply_to_text'] ) else: return LetterPreviewTemplate( template, - contact_block=service['letter_contact_block'], + contact_block=template['reply_to_text'], admin_base_url=current_app.config['ADMIN_BASE_URL'], redact_missing_personalisation=redact_missing_personalisation, ) diff --git a/tests/__init__.py b/tests/__init__.py index b5ba0350d..6fef78218 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -100,6 +100,9 @@ def template_json(service_id, archived=False, process_type='normal', redact_personalisation=None, + service_letter_contact=None, + reply_to=None, + reply_to_text=None, ): template = { 'id': id_, @@ -111,6 +114,9 @@ def template_json(service_id, 'updated_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'), 'archived': archived, 'process_type': process_type, + 'service_letter_contact': service_letter_contact, + 'reply_to': reply_to, + 'reply_to_text': reply_to_text, } if content is None: template['content'] = "template content" @@ -236,6 +242,7 @@ def notification_json( rows=5, personalisation=None, template_type=None, + reply_to_text=None ): if template is None: template = template_json(service_id, str(generate_uuid()), type_=template_type) @@ -283,6 +290,7 @@ def notification_json( 'template_version': template['version'], 'personalisation': personalisation or {}, 'notification_type': template_type, + 'reply_to_text': reply_to_text, } for i in range(rows)], 'total': rows, 'page_size': 50, diff --git a/tests/app/main/views/test_api_integration.py b/tests/app/main/views/test_api_integration.py new file mode 100644 index 000000000..aa2ec3e46 --- /dev/null +++ b/tests/app/main/views/test_api_integration.py @@ -0,0 +1,639 @@ +import uuid +from collections import OrderedDict + +import pytest +from flask import url_for +from bs4 import BeautifulSoup +from unittest.mock import call + +from tests import validate_route_permission +from tests.conftest import ( + mock_get_service, + mock_get_live_service, + mock_get_service_with_letters, + normalize_spaces, + SERVICE_ONE_ID, + mock_get_valid_service_callback_api, + mock_get_valid_service_inbound_api, +) + + +def test_should_show_api_page( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_get_notifications +): + response = logged_in_client.get(url_for('main.api_integration', service_id=str(uuid.uuid4()))) + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert page.h1.string.strip() == 'API integration' + rows = page.find_all('details') + assert len(rows) == 5 + for index, row in enumerate(rows): + assert row.find('h3').string.strip() == '07123456789' + + +def test_should_show_api_page_with_lots_of_notifications( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_get_notifications_with_previous_next +): + response = logged_in_client.get(url_for('main.api_integration', service_id=str(uuid.uuid4()))) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + rows = page.find_all('div', {'class': 'api-notifications-item'}) + assert ' '.join(rows[len(rows) - 1].text.split()) == ( + 'Only showing the first 50 messages. Notify deletes messages after 7 days.' + ) + + +def test_should_show_api_page_with_no_notifications( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_get_notifications_with_no_notifications +): + response = logged_in_client.get(url_for('main.api_integration', service_id=str(uuid.uuid4()))) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + rows = page.find_all('div', {'class': 'api-notifications-item'}) + assert 'When you send messages via the API they’ll appear here.' in rows[len(rows) - 1].text.strip() + + +def test_should_show_api_page_for_live_service( + logged_in_client, + mock_login, + api_user_active, + mock_get_live_service, + mock_has_permissions +): + response = logged_in_client.get(url_for('main.api_integration', service_id=str(uuid.uuid4()))) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert 'Your service is in trial mode' not in page.find('main').text + + +def test_api_documentation_page_should_redirect( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions +): + response = logged_in_client.get(url_for('main.api_documentation', service_id=str(uuid.uuid4()))) + assert response.status_code == 301 + assert response.location == url_for( + 'main.documentation', + _external=True + ) + + +def test_should_show_empty_api_keys_page( + client, + api_user_pending, + mock_login, + mock_get_no_api_keys, + mock_get_service, + mock_has_permissions, +): + client.login(api_user_pending) + service_id = str(uuid.uuid4()) + response = client.get(url_for('main.api_keys', service_id=service_id)) + + assert response.status_code == 200 + assert 'You haven’t created any API keys yet' in response.get_data(as_text=True) + assert 'Create an API key' in response.get_data(as_text=True) + mock_get_no_api_keys.assert_called_once_with(service_id=service_id) + + +def test_should_show_api_keys_page( + logged_in_client, + api_user_active, + mock_login, + mock_get_api_keys, + mock_get_service, + mock_has_permissions, + fake_uuid, +): + response = logged_in_client.get(url_for('main.api_keys', service_id=fake_uuid)) + + assert response.status_code == 200 + resp_data = response.get_data(as_text=True) + assert 'some key name' in resp_data + assert 'another key name' in resp_data + assert 'Revoked 1 January at 1:00am' in resp_data + mock_get_api_keys.assert_called_once_with(service_id=fake_uuid) + + +@pytest.mark.parametrize('service_mock, expected_options', [ + (mock_get_service, [ + ( + 'Live – sends to anyone ' + 'Not available because your service is in trial mode' + ), + 'Team and whitelist – limits who you can send to', + 'Test – pretends to send messages', + ]), + (mock_get_live_service, [ + 'Live – sends to anyone', + 'Team and whitelist – limits who you can send to', + 'Test – pretends to send messages', + ]), + (mock_get_service_with_letters, [ + 'Live – sends to anyone', + ( + 'Team and whitelist – limits who you can send to ' + 'Can’t be used to send letters' + ), + 'Test – pretends to send messages', + ]), +]) +def test_should_show_create_api_key_page( + client_request, + mocker, + api_user_active, + mock_get_api_keys, + service_mock, + expected_options, +): + service_mock(mocker, api_user_active) + + page = client_request.get('main.create_api_key', service_id=SERVICE_ONE_ID) + + for index, option in enumerate(expected_options): + assert normalize_spaces(page.select('.block-label')[index].text) == option + + +def test_should_create_api_key_with_type_normal( + logged_in_client, + api_user_active, + mock_login, + mock_get_api_keys, + mock_get_live_service, + mock_has_permissions, + fake_uuid, + mocker, +): + post = mocker.patch('app.notify_client.api_key_api_client.ApiKeyApiClient.post', return_value={'data': fake_uuid}) + service_id = str(uuid.uuid4()) + + response = logged_in_client.post( + url_for('main.create_api_key', service_id=service_id), + data={ + 'key_name': 'Some default key name 1/2', + 'key_type': 'normal' + } + ) + + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + keys = page.find_all('span', {'class': 'api-key-key'}) + for index, key in enumerate([ + 'some_default_key_name_12-{}-{}'.format(service_id, fake_uuid), + service_id, + fake_uuid + ]): + assert keys[index].text.strip() == key + + post.assert_called_once_with(url='/service/{}/api-key'.format(service_id), data={ + 'name': 'Some default key name 1/2', + 'key_type': 'normal', + 'created_by': api_user_active.id + }) + + +def test_cant_create_normal_api_key_in_trial_mode( + logged_in_client, + api_user_active, + mock_login, + mock_get_api_keys, + mock_get_service, + mock_has_permissions, + fake_uuid, + mocker, +): + mock_post = mocker.patch('app.notify_client.api_key_api_client.ApiKeyApiClient.post') + + response = logged_in_client.post( + url_for('main.create_api_key', service_id=uuid.uuid4()), + data={ + 'key_name': 'some default key name', + 'key_type': 'normal' + } + ) + assert response.status_code == 400 + mock_post.assert_not_called() + + +def test_should_show_confirm_revoke_api_key( + client_request, + mock_get_api_keys, + fake_uuid, +): + page = client_request.get( + 'main.revoke_api_key', service_id=SERVICE_ONE_ID, key_id=fake_uuid, + _test_page_title=False, + ) + assert normalize_spaces(page.select('.banner-dangerous')[0].text) == ( + 'Are you sure you want to revoke this API key? ' + '‘some key name’ will no longer let you connect to GOV.UK Notify.' + ) + assert mock_get_api_keys.call_args_list == [ + call( + key_id=fake_uuid, + service_id='596364a0-858e-42c8-9062-a8fe822260eb', + ), + call( + service_id='596364a0-858e-42c8-9062-a8fe822260eb' + ), + ] + + +def test_should_redirect_after_revoking_api_key( + logged_in_client, + api_user_active, + mock_login, + mock_revoke_api_key, + mock_get_api_keys, + mock_get_service, + mock_has_permissions, + fake_uuid, +): + response = logged_in_client.post(url_for('main.revoke_api_key', service_id=fake_uuid, key_id=fake_uuid)) + + assert response.status_code == 302 + assert response.location == url_for('.api_keys', service_id=fake_uuid, _external=True) + mock_revoke_api_key.assert_called_once_with(service_id=fake_uuid, key_id=fake_uuid) + mock_get_api_keys.assert_called_once_with(service_id=fake_uuid, key_id=fake_uuid) + + +@pytest.mark.parametrize('route', [ + 'main.api_keys', + 'main.create_api_key', + 'main.revoke_api_key' +]) +def test_route_permissions( + mocker, + app_, + api_user_active, + service_one, + mock_get_api_keys, + route, +): + with app_.test_request_context(): + validate_route_permission( + mocker, + app_, + "GET", + 200, + url_for(route, service_id=service_one['id'], key_id=123), + ['manage_api_keys'], + api_user_active, + service_one) + + +@pytest.mark.parametrize('route', [ + 'main.api_keys', + 'main.create_api_key', + 'main.revoke_api_key' +]) +def test_route_invalid_permissions( + mocker, + app_, + api_user_active, + service_one, + mock_get_api_keys, + route, +): + with app_.test_request_context(): + validate_route_permission( + mocker, + app_, + "GET", + 403, + url_for(route, service_id=service_one['id'], key_id=123), + ['view_activity'], + api_user_active, + service_one) + + +def test_should_show_whitelist_page( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_get_whitelist, +): + response = logged_in_client.get(url_for('main.whitelist', service_id=str(uuid.uuid4()))) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + textboxes = page.find_all('input', {'type': 'text'}) + for index, value in enumerate( + ['test@example.com'] + [''] * 4 + ['07900900000'] + [''] * 4 + ): + assert textboxes[index]['value'] == value + + +def test_should_update_whitelist( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_update_whitelist +): + service_id = str(uuid.uuid4()) + data = OrderedDict([ + ('email_addresses-1', 'test@example.com'), + ('email_addresses-3', 'test@example.com'), + ('phone_numbers-0', '07900900000'), + ('phone_numbers-2', '+1800-555-555'), + ]) + + logged_in_client.post( + url_for('main.whitelist', service_id=service_id), + data=data + ) + + mock_update_whitelist.assert_called_once_with(service_id, { + 'email_addresses': ['test@example.com', 'test@example.com'], + 'phone_numbers': ['07900900000', '+1800-555-555']}) + + +def test_should_validate_whitelist_items( + logged_in_client, + mock_login, + api_user_active, + mock_get_service, + mock_has_permissions, + mock_update_whitelist +): + + response = logged_in_client.post( + url_for('main.whitelist', service_id=str(uuid.uuid4())), + data=OrderedDict([ + ('email_addresses-1', 'abc'), + ('phone_numbers-0', '123') + ]) + ) + + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert page.h1.string.strip() == 'There was a problem with your whitelist' + jump_links = page.select('.banner-dangerous a') + + assert jump_links[0].string.strip() == 'Enter valid email addresses' + assert jump_links[0]['href'] == '#email_addresses' + + assert jump_links[1].string.strip() == 'Enter valid phone numbers' + assert jump_links[1]['href'] == '#phone_numbers' + + mock_update_whitelist.assert_not_called() + + +@pytest.mark.parametrize('endpoint', [ + ('main.delivery_status_callback'), + ('main.received_text_messages_callback'), +]) +@pytest.mark.parametrize('url, bearer_token, expected_errors', [ + ("", "", "Can’t be empty Can’t be empty"), + ("http://not_https.com", "1234567890", "Must be a valid https URL"), + ("https://test.com", "123456789", "Must be at least 10 characters"), +]) +def test_callback_forms_validation( + client_request, + service_one, + endpoint, + url, + bearer_token, + expected_errors +): + if endpoint == 'main.received_text_messages_callback': + service_one['permissions'] = ['inbound_sms'] + + data = { + "url": url, + "bearer_token": bearer_token, + } + + response = client_request.post( + endpoint, + service_id=service_one['id'], + _data=data, + _expected_status=200 + ) + error_msgs = ' '.join(msg.text.strip() for msg in response.select(".error-message")) + + assert error_msgs == expected_errors + + +@pytest.mark.parametrize('has_inbound_sms, expected_link', [ + (True, 'main.api_callbacks'), + (False, 'main.delivery_status_callback'), +]) +def test_callbacks_button_links_straight_to_delivery_status_if_service_has_no_inbound_sms( + client_request, + service_one, + mocker, + mock_get_notifications, + has_inbound_sms, + expected_link +): + if has_inbound_sms: + service_one['permissions'] = ['inbound_sms'] + + page = client_request.get( + 'main.api_integration', + service_id=service_one['id'], + ) + + assert page.select('.pill-separate-item')[2]['href'] == url_for( + expected_link, service_id=service_one['id'] + ) + + +def test_callbacks_page_redirects_to_delivery_status_if_service_has_no_inbound_sms( + client_request, + service_one, + mocker +): + page = client_request.get( + 'main.api_callbacks', + service_id=service_one['id'], + _follow_redirects=True, + ) + + assert normalize_spaces(page.select_one('h1').text) == "Callbacks for delivery receipts" + + +@pytest.mark.parametrize('has_inbound_sms, expected_link', [ + (True, 'main.api_callbacks'), + (False, 'main.api_integration'), +]) +def test_back_link_directs_to_api_integration_from_delivery_callback_if_no_inbound_sms( + client_request, + service_one, + mocker, + has_inbound_sms, + expected_link +): + if has_inbound_sms: + service_one['permissions'] = ['inbound_sms'] + + page = client_request.get( + 'main.delivery_status_callback', + service_id=service_one['id'], + _follow_redirects=True, + ) + + assert page.select_one('.page-footer-back-link')['href'] == url_for( + expected_link, service_id=service_one['id'] + ) + + +@pytest.mark.parametrize('endpoint', [ + ('main.delivery_status_callback'), + ('main.received_text_messages_callback'), +]) +def test_create_delivery_status_and_receive_text_message_callbacks( + client_request, + service_one, + mocker, + mock_get_notifications, + mock_create_service_inbound_api, + mock_create_service_callback_api, + endpoint, + fake_uuid, +): + if endpoint == 'main.received_text_messages_callback': + service_one['permissions'] = ['inbound_sms'] + + data = { + 'url': "https://test.url.com/", + 'bearer_token': '1234567890', + 'user_id': fake_uuid + } + + client_request.post( + endpoint, + service_id=service_one['id'], + _data=data, + ) + + if endpoint == 'main.received_text_messages_callback': + mock_create_service_inbound_api.assert_called_once_with( + service_one['id'], + url="https://test.url.com/", + bearer_token="1234567890", + user_id=fake_uuid, + ) + else: + mock_create_service_callback_api.assert_called_once_with( + service_one['id'], + url="https://test.url.com/", + bearer_token="1234567890", + user_id=fake_uuid, + ) + + +@pytest.mark.parametrize('endpoint, fixture', [ + ('main.delivery_status_callback', mock_get_valid_service_callback_api), + ('main.received_text_messages_callback', mock_get_valid_service_inbound_api), +]) +def test_update_delivery_status_and_receive_text_message_callbacks( + client_request, + service_one, + mocker, + mock_get_notifications, + mock_update_service_inbound_api, + mock_update_service_callback_api, + endpoint, + fixture, + fake_uuid, +): + if endpoint == 'main.received_text_messages_callback': + service_one['inbound_api'] = [fake_uuid] + service_one['permissions'] = ['inbound_sms'] + else: + service_one['service_callback_api'] = [fake_uuid] + + fixture(mocker) + + data = { + 'url': "https://test.url.com/", + 'bearer_token': '1234567890', + 'user_id': fake_uuid + } + + client_request.post( + endpoint, + service_id=service_one['id'], + _data=data, + ) + + if endpoint == 'main.received_text_messages_callback': + mock_update_service_inbound_api.assert_called_once_with( + service_one['id'], + url="https://test.url.com/", + bearer_token="1234567890", + user_id=fake_uuid, + inbound_api_id=fake_uuid, + ) + else: + mock_update_service_callback_api.assert_called_once_with( + service_one['id'], + url="https://test.url.com/", + bearer_token="1234567890", + user_id=fake_uuid, + callback_api_id=fake_uuid + ) + + +@pytest.mark.parametrize('endpoint, data, fixture', [ + ( + 'main.delivery_status_callback', + {"url": "https://hello2.gov.uk", "bearer_token": "bearer_token_set"}, + mock_get_valid_service_callback_api + ), + ( + 'main.received_text_messages_callback', + {"url": "https://hello3.gov.uk", "bearer_token": "bearer_token_set"}, + mock_get_valid_service_inbound_api + ), +]) +def test_update_delivery_status_and_receive_text_message_callbacks_without_changes_do_not_update( + client_request, + service_one, + mocker, + mock_get_notifications, + mock_update_service_callback_api, + mock_update_service_inbound_api, + data, + fixture, + endpoint, + fake_uuid, +): + if endpoint == 'main.received_text_messages_callback': + service_one['inbound_api'] = [fake_uuid] + service_one['permissions'] = ['inbound_sms'] + else: + service_one['service_callback_api'] = [fake_uuid] + + fixture(mocker) + + data['user_id'] = fake_uuid + + client_request.post( + endpoint, + service_id=service_one['id'], + _data=data, + ) + + if endpoint == 'main.received_text_messages_callback': + assert mock_update_service_inbound_api.called is False + else: + assert mock_update_service_callback_api.called is False diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 02d22db84..fb8cb7d76 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -908,6 +908,34 @@ def test_add_letter_contact( ) +def test_add_letter_contact_when_coming_from_template( + no_letter_contact_blocks, + client_request, + mock_add_letter_contact, + fake_uuid, + mock_get_service_letter_template, +): + data = { + 'letter_contact_block': "1 Example Street" + } + + page = client_request.post( + 'main.service_add_letter_contact', + service_id=SERVICE_ONE_ID, + _data=data, + from_template=fake_uuid, + _follow_redirects=True + ) + + mock_add_letter_contact.assert_called_once_with( + SERVICE_ONE_ID, + contact_block="1 Example Street", + is_default=True + ) + + assert page.find('h1').text == 'Set letter contact block' + + @pytest.mark.parametrize('fixture, data, api_default_args', [ (no_sms_senders, {}, True), (multiple_sms_senders, {}, False), diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index eb5ae48e0..d33982af2 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -11,6 +11,8 @@ from tests.conftest import ( mock_get_service_email_template, mock_get_service_letter_template, mock_get_service_template, + no_letter_contact_blocks, + single_letter_contact_block, normalize_spaces, SERVICE_ONE_ID, active_user_with_permissions, @@ -198,31 +200,7 @@ def test_should_show_sms_template_with_downgraded_unicode_characters( assert rendered_msg in response.get_data(as_text=True) -def test_should_let_letter_contact_block_be_edited_if_a_letter_contact_block_exists( - mocker, - mock_get_service_letter_template, - single_letter_contact_block, - client_request, - service_one, - fake_uuid, -): - service_one['permissions'].append('letter') - mocker.patch('app.main.views.templates.get_page_count_for_letter', return_value=1) - - page = client_request.get( - 'main.view_template', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid - ) - - assert page.find('a', {'class': 'edit-template-link-letter-contact'})['href'] == url_for( - '.service_edit_letter_contact', - service_id=service_one['id'], - letter_contact_id='1234', - from_template=fake_uuid) - - -def test_should_let_letter_contact_block_be_added_if_no_letter_contact_blocks_exist( +def test_should_let_letter_contact_block_be_changed_for_the_template( mocker, mock_get_service_letter_template, no_letter_contact_blocks, @@ -240,9 +218,10 @@ def test_should_let_letter_contact_block_be_added_if_no_letter_contact_blocks_ex ) assert page.find('a', {'class': 'edit-template-link-letter-contact'})['href'] == url_for( - '.service_add_letter_contact', - service_id=service_one['id'], - from_template=fake_uuid) + 'main.set_template_sender', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + ) def test_should_show_page_template_with_priority_select_if_platform_admin( @@ -1286,3 +1265,51 @@ def test_should_show_letter_template_as_dvla_markup( )) assert response.status_code == expected_response_code + + +def test_set_template_sender( + client_request, + fake_uuid, + mock_update_service_template_sender, + mock_get_service_letter_template, + single_letter_contact_block +): + data = { + 'sender': '1234', + } + + client_request.post( + 'main.set_template_sender', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + _data=data, + ) + + mock_update_service_template_sender.assert_called_once_with( + SERVICE_ONE_ID, + fake_uuid, + '1234', + ) + + +@pytest.mark.parametrize('fixture, add_button_is_on_page', [ + (no_letter_contact_blocks, True), + (single_letter_contact_block, False), +]) +def test_add_sender_link_only_appears_on_services_with_no_senders( + client_request, + fake_uuid, + mocker, + fixture, + add_button_is_on_page, + mock_get_service_letter_template, + no_letter_contact_blocks +): + fixture(mocker) + page = client_request.get( + 'main.set_template_sender', + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + ) + + assert (page.select_one('.column-three-quarters form > a') is not None) == add_button_is_on_page diff --git a/tests/app/test_template_previews.py b/tests/app/test_template_previews.py index e1088bbdf..933371a95 100644 --- a/tests/app/test_template_previews.py +++ b/tests/app/test_template_previews.py @@ -45,20 +45,22 @@ def test_from_database_object_makes_request( client, partial_call, expected_url, + mock_get_service_letter_template ): resp = Mock(content='a', status_code='b', headers={'c': 'd'}) request_mock = mocker.patch('app.template_previews.requests.post', return_value=resp) mocker.patch('app.template_previews.current_service', __getitem__=Mock(return_value='123')) + template = mock_get_service_letter_template('123', '456')['data'] - ret = partial_call(template='foo') + ret = partial_call(template=template) assert ret[0] == 'a' assert ret[1] == 'b' assert list(ret[2]) == [('c', 'd')] data = { - 'letter_contact_block': '123', - 'template': 'foo', + 'letter_contact_block': None, + 'template': template, 'values': None, 'dvla_org_id': '123', } diff --git a/tests/conftest.py b/tests/conftest.py index 3e9c5c466..b6bd4030b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1029,6 +1029,17 @@ def mock_redact_template(mocker): return mocker.patch('app.service_api_client.redact_service_template') +@pytest.fixture(scope='function') +def mock_update_service_template_sender(mocker): + def _update(service_id, template_id, reply_to): + return + + return mocker.patch( + 'app.service_api_client.update_service_template_sender', + side_effect=_update + ) + + @pytest.fixture(scope='function') def api_user_pending(fake_uuid): from app.notify_client.user_api_client import User