From effe24893e0e0a4a33bd2f225104c5ea6484f958 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 9 Jul 2020 10:33:50 +0100 Subject: [PATCH] Make the broadcast flow talk to the API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit removes the code the puts areas into the session and instead creates and then updates a draft broadcast in the database. This is so we can avoid session-related bugs, and potentially having a large session when we start adding personalisation etc. Once a broadcast is ready to go it is set to `broadcasting` straight away with no approval. We’ll revisit this as we learn more about how users might want to manage who can create and approve broadcasts. The tests are a bit light in terms of checking what’s on the page, but clicking through the pages is probably good enough for now. --- app/__init__.py | 4 + app/main/views/broadcast.py | 116 +++++++++--------- app/models/broadcast_message.py | 111 +++++++++++++++++ .../broadcast_message_api_client.py | 46 +++++++ app/templates/views/broadcast/areas.html | 2 +- app/templates/views/broadcast/libraries.html | 4 +- .../views/broadcast/preview-areas.html | 21 ++-- .../views/broadcast/preview-message.html | 19 +-- app/templates/views/templates/_template.html | 2 +- tests/app/main/views/test_broadcast.py | 105 ++++++++++++++-- tests/app/main/views/test_templates.py | 1 + tests/conftest.py | 94 ++++++++++++++ 12 files changed, 436 insertions(+), 89 deletions(-) create mode 100644 app/models/broadcast_message.py create mode 100644 app/notify_client/broadcast_message_api_client.py diff --git a/app/__init__.py b/app/__init__.py index 61237c7dc..9785d5847 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -59,6 +59,9 @@ from app.navigation import ( from app.notify_client import InviteTokenError from app.notify_client.api_key_api_client import api_key_api_client from app.notify_client.billing_api_client import billing_api_client +from app.notify_client.broadcast_message_api_client import ( + broadcast_message_api_client, +) from app.notify_client.complaint_api_client import complaint_api_client from app.notify_client.contact_list_api_client import contact_list_api_client from app.notify_client.email_branding_client import email_branding_client @@ -143,6 +146,7 @@ def create_app(application): # API clients api_key_api_client, billing_api_client, + broadcast_message_api_client, contact_list_api_client, complaint_api_client, email_branding_client, diff --git a/app/main/views/broadcast.py b/app/main/views/broadcast.py index fbcd10189..6a795d4b8 100644 --- a/app/main/views/broadcast.py +++ b/app/main/views/broadcast.py @@ -1,11 +1,9 @@ -from flask import redirect, render_template, request, session, url_for -from notifications_utils.broadcast_areas import broadcast_area_libraries -from notifications_utils.template import BroadcastPreviewTemplate -from orderedset import OrderedSet +from flask import redirect, render_template, request, url_for from app import current_service from app.main import main from app.main.forms import BroadcastAreaForm, SearchByNameForm +from app.models.broadcast_message import BroadcastMessage from app.utils import service_has_permission, user_has_permissions @@ -18,63 +16,63 @@ def broadcast_dashboard(service_id): ) -@main.route('/services//broadcast') +@main.route('/services//broadcast/') @user_has_permissions('send_messages') @service_has_permission('broadcast') -def broadcast(service_id): - if 'broadcast_areas' in session: - session.pop('broadcast_areas') +def broadcast(service_id, template_id): return redirect(url_for( '.preview_broadcast_areas', service_id=current_service.id, + broadcast_message_id=BroadcastMessage.create( + service_id=service_id, + template_id=template_id, + ).id, )) -@main.route('/services//broadcast/areas') +@main.route('/services//broadcast//areas') @user_has_permissions('send_messages') @service_has_permission('broadcast') -def preview_broadcast_areas(service_id): - selected_areas_ids = session.get('broadcast_areas', []) +def preview_broadcast_areas(service_id, broadcast_message_id): return render_template( 'views/broadcast/preview-areas.html', - selected=list(broadcast_area_libraries.get_areas( - *selected_areas_ids - )), - area_polygons=broadcast_area_libraries.get_polygons_for_areas_lat_long( - *selected_areas_ids - ) - ) - - -@main.route('/services//broadcast/libraries') -@user_has_permissions('send_messages') -@service_has_permission('broadcast') -def choose_broadcast_library(service_id): - return render_template( - 'views/broadcast/libraries.html', - libraries=broadcast_area_libraries, - selected=broadcast_area_libraries.get_areas( - *session.get('broadcast_areas', []) + broadcast_message=BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, ), ) -@main.route('/services//broadcast/libraries/', methods=['GET', 'POST']) +@main.route('/services//broadcast//libraries') @user_has_permissions('send_messages') @service_has_permission('broadcast') -def choose_broadcast_area(service_id, library_slug): - library = broadcast_area_libraries.get(library_slug) +def choose_broadcast_library(service_id, broadcast_message_id): + return render_template( + 'views/broadcast/libraries.html', + libraries=BroadcastMessage.libraries, + broadcast_message_id=broadcast_message_id, + ) + + +@main.route( + '/services//broadcast//libraries/', + methods=['GET', 'POST'], +) +@user_has_permissions('send_messages') +@service_has_permission('broadcast') +def choose_broadcast_area(service_id, broadcast_message_id, library_slug): + broadcast_message = BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, + ) + library = BroadcastMessage.libraries.get(library_slug) form = BroadcastAreaForm.from_library(library) if form.validate_on_submit(): - if not session.get('broadcast_areas'): - session['broadcast_areas'] = [] - session['broadcast_areas'] = session['broadcast_areas'] + form.areas.data - session['broadcast_areas'] = list(OrderedSet( - session['broadcast_areas'] - )) + broadcast_message.add_areas(*form.areas.data) return redirect(url_for( '.preview_broadcast_areas', service_id=current_service.id, + broadcast_message_id=broadcast_message.id, )) return render_template( 'views/broadcast/areas.html', @@ -85,35 +83,41 @@ def choose_broadcast_area(service_id, library_slug): ) -@main.route('/services//broadcast/remove/') +@main.route('/services//broadcast//remove/') @user_has_permissions('send_messages') @service_has_permission('broadcast') -def remove_broadcast_area(service_id, area_slug): - session['broadcast_areas'] = list(filter( - lambda saved_area_id: saved_area_id != area_slug, - session.get('broadcast_areas', []), - )) - +def remove_broadcast_area(service_id, broadcast_message_id, area_slug): + BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, + ).remove_area( + area_slug + ) return redirect(url_for( '.preview_broadcast_areas', service_id=current_service.id, + broadcast_message_id=broadcast_message_id, )) -@main.route('/services//broadcast/preview', methods=['GET', 'POST']) +@main.route( + '/services//broadcast//preview', + methods=['GET', 'POST'], +) @user_has_permissions('send_messages') @service_has_permission('broadcast') -def preview_broadcast_message(service_id): +def preview_broadcast_message(service_id, broadcast_message_id): + broadcast_message = BroadcastMessage.from_id( + broadcast_message_id, + service_id=current_service.id, + ) if request.method == 'POST': - return 'OK' - selected_areas = session.get('broadcast_areas', []) + broadcast_message.start_broadcast() + return redirect(url_for( + '.broadcast_dashboard', + service_id=current_service.id, + )) return render_template( 'views/broadcast/preview-message.html', - selected=list(broadcast_area_libraries.get_areas( - *selected_areas - )), - template=BroadcastPreviewTemplate({ - 'content': 'Message here', - 'template_type': 'broadcast', - }) + broadcast_message=broadcast_message, ) diff --git a/app/models/broadcast_message.py b/app/models/broadcast_message.py new file mode 100644 index 000000000..041d3831a --- /dev/null +++ b/app/models/broadcast_message.py @@ -0,0 +1,111 @@ +from datetime import datetime, timedelta + +from notifications_utils.broadcast_areas import broadcast_area_libraries +from notifications_utils.template import BroadcastPreviewTemplate +from orderedset import OrderedSet + +from app.models import JSONModel +from app.notify_client.broadcast_message_api_client import ( + broadcast_message_api_client, +) +from app.notify_client.service_api_client import service_api_client + + +class BroadcastMessage(JSONModel): + + ALLOWED_PROPERTIES = { + 'id', + 'service_id', + 'template_id', + 'template_name', + 'template_version', + 'service_id', + 'created_by', + 'personalisation', + 'starts_at', + 'finishes_at', + 'status', + 'created_at', + 'approved_at', + 'cancelled_at', + 'updated_at', + 'created_by_id', + 'approved_by_id', + 'cancelled_by_id', + } + DEFAULT_TTL = timedelta(hours=72) + + libraries = broadcast_area_libraries + + @classmethod + def create(cls, *, service_id, template_id): + return cls(broadcast_message_api_client.create_broadcast_message( + service_id=service_id, + template_id=template_id, + )) + + @classmethod + def from_id(cls, broadcast_message_id, *, service_id): + return cls(broadcast_message_api_client.get_broadcast_message( + service_id=service_id, + broadcast_message_id=broadcast_message_id, + )) + + @property + def areas(self): + return broadcast_area_libraries.get_areas( + *self._dict['areas'] + ) + + @property + def polygons(self): + return broadcast_area_libraries.get_polygons_for_areas_lat_long( + *self._dict['areas'] + ) + + @property + def template(self): + response = service_api_client.get_service_template( + self.service_id, + self.template_id, + version=self.template_version, + ) + return BroadcastPreviewTemplate(response['data']) + + def add_areas(self, *new_areas): + broadcast_message_api_client.update_broadcast_message( + broadcast_message_id=self.id, + service_id=self.service_id, + data={ + 'areas': list(OrderedSet( + self._dict['areas'] + list(new_areas) + )) + }, + ) + + def remove_area(self, area_to_remove): + broadcast_message_api_client.update_broadcast_message( + broadcast_message_id=self.id, + service_id=self.service_id, + data={ + 'areas': [ + area for area in self._dict['areas'] + if area != area_to_remove + ] + }, + ) + + def start_broadcast(self): + broadcast_message_api_client.update_broadcast_message( + broadcast_message_id=self.id, + service_id=self.service_id, + data={ + 'starts_at': datetime.utcnow().isoformat(), + 'finishes_at': (datetime.utcnow() + self.DEFAULT_TTL).isoformat(), + }, + ) + broadcast_message_api_client.update_broadcast_message_status( + 'broadcasting', + broadcast_message_id=self.id, + service_id=self.service_id, + ) diff --git a/app/notify_client/broadcast_message_api_client.py b/app/notify_client/broadcast_message_api_client.py new file mode 100644 index 000000000..57439798d --- /dev/null +++ b/app/notify_client/broadcast_message_api_client.py @@ -0,0 +1,46 @@ +from app.notify_client import NotifyAdminAPIClient, _attach_current_user + + +class BroadcastMessageAPIClient(NotifyAdminAPIClient): + + def create_broadcast_message( + self, + *, + service_id, + template_id, + ): + data = { + "service_id": service_id, + "template_id": template_id, + "personalisation": {}, + } + + data = _attach_current_user(data) + + broadcast_message = self.post( + f'/service/{service_id}/broadcast-message', + data=data, + ) + + return broadcast_message + + def get_broadcast_message(self, *, service_id, broadcast_message_id): + return self.get(f'/service/{service_id}/broadcast-message/{broadcast_message_id}') + + def update_broadcast_message(self, *, service_id, broadcast_message_id, data): + self.post( + f'/service/{service_id}/broadcast-message/{broadcast_message_id}', + data=data, + ) + + def update_broadcast_message_status(self, status, *, service_id, broadcast_message_id): + data = _attach_current_user({ + 'status': status, + }) + self.post( + f'/service/{service_id}/broadcast-message/{broadcast_message_id}/status', + data=data, + ) + + +broadcast_message_api_client = BroadcastMessageAPIClient() diff --git a/app/templates/views/broadcast/areas.html b/app/templates/views/broadcast/areas.html index 3b48b75b9..dd63b4d26 100644 --- a/app/templates/views/broadcast/areas.html +++ b/app/templates/views/broadcast/areas.html @@ -14,7 +14,7 @@ {{ page_header( page_title, - back_link=url_for('.choose_broadcast_library', service_id=current_service.id), + back_link=url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message_id), )}} {{ live_search(target_selector='.multiple-choice', show=show_search_form, form=search_form, label='Search by name') }} diff --git a/app/templates/views/broadcast/libraries.html b/app/templates/views/broadcast/libraries.html index 60ce83edc..e5597d511 100644 --- a/app/templates/views/broadcast/libraries.html +++ b/app/templates/views/broadcast/libraries.html @@ -10,11 +10,11 @@ {{ page_header( "Choose where to broadcast to", - back_link=url_for(".preview_broadcast_areas", service_id=current_service.id) + back_link=url_for(".preview_broadcast_areas", service_id=current_service.id, broadcast_message_id=broadcast_message_id) ) }} {% for library in libraries|sort %} - {{ library.name }} + {{ library.name }}

{{ library.get_examples() }}

{% endfor %} diff --git a/app/templates/views/broadcast/preview-areas.html b/app/templates/views/broadcast/preview-areas.html index c719f0f22..44a10fb27 100644 --- a/app/templates/views/broadcast/preview-areas.html +++ b/app/templates/views/broadcast/preview-areas.html @@ -36,9 +36,9 @@ attribution: '© OpenStreetMap contributors' }).addTo(mymap); - {% for area in area_polygons %} + {% for polygon in broadcast_message.polygons %} polygons.push( - L.polygon({{area}}, { + L.polygon({{polygon}}, { color: '#0b0b0c', // $black fillColor: '#2B8CC4', // $light-blue fillOpacity: 0.2, @@ -58,20 +58,23 @@ {% block maincolumn_content %} - {{ page_header("Choose where to broadcast to", back_link=url_for('.choose_template', service_id=current_service.id)) }} + {{ page_header( + "Choose where to broadcast to", + back_link=url_for('.view_template', service_id=current_service.id, template_id=broadcast_message.template_id) + ) }} - {% for area in selected %} + {% for area in broadcast_message.areas %} {% if loop.first %}
    {% endif %}
  • - {{ area.name }} remove + {{ area.name }} remove
  • {% if loop.last %} {{ govukButton({ "element": "a", "text": "Add another area", - "href": url_for('.choose_broadcast_library', service_id=current_service.id), + "href": url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message.id), "classes": "govuk-button--secondary govuk-!-margin-bottom-5" }) }}
@@ -81,15 +84,15 @@ {{ govukButton({ "element": "a", "text": "Add broadcast areas", - "href": url_for('.choose_broadcast_library', service_id=current_service.id), + "href": url_for('.choose_broadcast_library', service_id=current_service.id, broadcast_message_id=broadcast_message.id), "classes": "govuk-button--secondary" }) }}

{% endfor %} - {% if selected %} + {% if broadcast_message.areas %}
-
+ {{ sticky_page_footer('Continue to preview') }}
{% endif %} diff --git a/app/templates/views/broadcast/preview-message.html b/app/templates/views/broadcast/preview-message.html index 5073a1803..f37537b1f 100644 --- a/app/templates/views/broadcast/preview-message.html +++ b/app/templates/views/broadcast/preview-message.html @@ -11,9 +11,9 @@ {% block maincolumn_content %} - {{ page_header("Preview", back_link=url_for('.preview_broadcast_areas', service_id=current_service.id)) }} + {{ page_header("Preview", back_link=url_for('.preview_broadcast_areas', service_id=current_service.id, broadcast_message_id=broadcast_message.id)) }} - {% for area in selected %} + {% for area in broadcast_message.areas %} {% if loop.first %}
    {% endif %} @@ -25,17 +25,10 @@ {% endif %} {% endfor %} - {% if selected %} - -

    - {{ template|string }} -

    - - {% call form_wrapper() %} - {{ sticky_page_footer('Start broadcast') }} - {% endcall %} - - {% endif %} + {{ broadcast_message.template|string }} + {% call form_wrapper() %} + {{ sticky_page_footer('Start broadcast') }} + {% endcall %} {% endblock %} diff --git a/app/templates/views/templates/_template.html b/app/templates/views/templates/_template.html index 8f3bbf321..ce7ed729b 100644 --- a/app/templates/views/templates/_template.html +++ b/app/templates/views/templates/_template.html @@ -31,7 +31,7 @@ {% elif template.template_type == 'broadcast' %} {% if current_user.has_permissions('send_messages') %} diff --git a/tests/app/main/views/test_broadcast.py b/tests/app/main/views/test_broadcast.py index 511d516dc..984a3fd6e 100644 --- a/tests/app/main/views/test_broadcast.py +++ b/tests/app/main/views/test_broadcast.py @@ -1,17 +1,21 @@ import pytest from flask import url_for +from freezegun import freeze_time +from tests import sample_uuid from tests.conftest import SERVICE_ONE_ID, normalize_spaces +sample_uuid = sample_uuid() + @pytest.mark.parametrize('endpoint, extra_args', ( ('.broadcast_dashboard', {}), - ('.broadcast', {}), - ('.preview_broadcast_areas', {}), - ('.choose_broadcast_library', {}), - ('.choose_broadcast_area', {'library_slug': 'countries'}), - ('.remove_broadcast_area', {'area_slug': 'england'}), - ('.preview_broadcast_message', {}), + ('.broadcast', {'template_id': sample_uuid}), + ('.preview_broadcast_areas', {'broadcast_message_id': sample_uuid}), + ('.choose_broadcast_library', {'broadcast_message_id': sample_uuid}), + ('.choose_broadcast_area', {'broadcast_message_id': sample_uuid, 'library_slug': 'countries'}), + ('.remove_broadcast_area', {'broadcast_message_id': sample_uuid, 'area_slug': 'england'}), + ('.preview_broadcast_message', {'broadcast_message_id': sample_uuid}), )) def test_broadcast_pages_403_without_permission( client_request, @@ -56,14 +60,18 @@ def test_broadcast_dashboard( def test_broadcast_page( client_request, service_one, + fake_uuid, + mock_create_broadcast_message, ): service_one['permissions'] += ['broadcast'] client_request.get( '.broadcast', service_id=SERVICE_ONE_ID, + template_id=fake_uuid, _expected_redirect=url_for( '.preview_broadcast_areas', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, _external=True, ), ), @@ -72,22 +80,28 @@ def test_broadcast_page( def test_preview_broadcast_areas_page( client_request, service_one, + fake_uuid, + mock_get_draft_broadcast_message, ): service_one['permissions'] += ['broadcast'] client_request.get( '.preview_broadcast_areas', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, ) def test_choose_broadcast_library_page( client_request, service_one, + mock_get_draft_broadcast_message, + fake_uuid, ): service_one['permissions'] += ['broadcast'] page = client_request.get( '.choose_broadcast_library', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, ) assert [ (normalize_spaces(title.text), normalize_spaces(hint.text)) @@ -111,6 +125,7 @@ def test_choose_broadcast_library_page( assert page.select_one('a.file-list-filename-large.govuk-link')['href'] == url_for( '.choose_broadcast_area', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, library_slug='counties-and-unitary-authorities-in-england-and-wales', ) @@ -118,38 +133,114 @@ def test_choose_broadcast_library_page( def test_choose_broadcast_area_page( client_request, service_one, + mock_get_draft_broadcast_message, + fake_uuid, ): service_one['permissions'] += ['broadcast'] client_request.get( '.choose_broadcast_area', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, library_slug='countries', ) +def test_add_broadcast_area( + client_request, + service_one, + mock_get_draft_broadcast_message, + mock_update_broadcast_message, + fake_uuid, +): + service_one['permissions'] += ['broadcast'] + client_request.post( + '.choose_broadcast_area', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + library_slug='countries', + _data={ + 'areas': ['england', 'wales'] + } + ) + mock_update_broadcast_message.assert_called_once_with( + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + data={ + 'areas': ['england', 'scotland', 'wales'] + }, + ) + + def test_remove_broadcast_area_page( client_request, service_one, + mock_get_draft_broadcast_message, + mock_update_broadcast_message, + fake_uuid, ): service_one['permissions'] += ['broadcast'] client_request.get( '.remove_broadcast_area', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, area_slug='england', _expected_redirect=url_for( '.preview_broadcast_areas', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, _external=True, ), - ), + ) + mock_update_broadcast_message.assert_called_once_with( + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + data={ + 'areas': ['scotland'] + }, + ) def test_preview_broadcast_message_page( client_request, service_one, + mock_get_draft_broadcast_message, + mock_get_broadcast_template, + fake_uuid, ): service_one['permissions'] += ['broadcast'] client_request.get( '.preview_broadcast_message', service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, ), + + +@freeze_time('2020-02-02 02:02:02.222222') +def test_start_broadcasting( + client_request, + service_one, + mock_get_draft_broadcast_message, + mock_get_broadcast_template, + mock_update_broadcast_message, + mock_update_broadcast_message_status, + fake_uuid, +): + service_one['permissions'] += ['broadcast'] + client_request.post( + '.preview_broadcast_message', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + ), + mock_update_broadcast_message.assert_called_once_with( + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + data={ + 'starts_at': '2020-02-02T02:02:02.222222', + 'finishes_at': '2020-02-05T02:02:02.222222', + }, + ) + mock_update_broadcast_message_status.assert_called_once_with( + 'broadcasting', + service_id=SERVICE_ONE_ID, + broadcast_message_id=fake_uuid, + ) diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 5afea8600..4a8c8bb72 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -696,6 +696,7 @@ def test_view_broadcast_template( ('Prepare broadcast', url_for( '.broadcast', service_id=SERVICE_ONE_ID, + template_id=fake_uuid, )), ('Edit', url_for( '.edit_service_template', diff --git a/tests/conftest.py b/tests/conftest.py index 13bdcb397..81f54d481 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4146,3 +4146,97 @@ def create_template( redact_personalisation=redact_personalisation, postage=postage, ) + + +@pytest.fixture(scope='function') +def mock_create_broadcast_message( + mocker, + fake_uuid, +): + def _create( + service_id, + template_id, + ): + return { + 'id': fake_uuid, + } + + return mocker.patch( + 'app.broadcast_message_api_client.create_broadcast_message', + side_effect=_create, + ) + + +@pytest.fixture(scope='function') +def mock_get_draft_broadcast_message( + mocker, + fake_uuid, +): + def _get( + *, service_id, broadcast_message_id + ): + return { + 'id': broadcast_message_id, + + 'service_id': service_id, + + 'template_id': fake_uuid, + 'template_version': 123, + 'template_name': 'Example template', + + 'personalisation': {}, + 'areas': [ + 'england', 'scotland', + ], + + 'status': 'draft', + + 'starts_at': None, + 'finishes_at': None, + + 'created_at': None, + 'approved_at': None, + 'cancelled_at': None, + 'updated_at': None, + + 'created_by_id': fake_uuid, + 'approved_by_id': None, + 'cancelled_by_id': None, + } + + return mocker.patch( + 'app.broadcast_message_api_client.get_broadcast_message', + side_effect=_get, + ) + + +@pytest.fixture(scope='function') +def mock_update_broadcast_message( + mocker, + fake_uuid, +): + def _update( + *, service_id, broadcast_message_id, data + ): + pass + + return mocker.patch( + 'app.broadcast_message_api_client.update_broadcast_message', + side_effect=_update, + ) + + +@pytest.fixture(scope='function') +def mock_update_broadcast_message_status( + mocker, + fake_uuid, +): + def _update( + status, *, service_id, broadcast_message_id + ): + pass + + return mocker.patch( + 'app.broadcast_message_api_client.update_broadcast_message_status', + side_effect=_update, + )