diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index 5f1c6e9fe..e439de672 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -198,6 +198,31 @@ def archive_service(service_id): return service_settings(service_id) +@main.route("/services//service-settings/suspend", methods=["GET", "POST"]) +@login_required +@user_has_permissions('manage_settings', admin_override=True) +def suspend_service(service_id): + if request.method == 'POST': + service_api_client.suspend_service(service_id) + return redirect(url_for('.service_settings', service_id=service_id)) + else: + flash("This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?", + 'suspend') + return service_settings(service_id) + + +@main.route("/services//service-settings/resume", methods=["GET", "POST"]) +@login_required +@user_has_permissions('manage_settings', admin_override=True) +def resume_service(service_id): + if request.method == 'POST': + service_api_client.resume_service(service_id) + return redirect(url_for('.service_settings', service_id=service_id)) + else: + flash("This will resume the service. New api key are required for this service to use the API.", 'resume') + return service_settings(service_id) + + @main.route("/services//service-settings/set-reply-to-email", methods=['GET', 'POST']) @login_required @user_has_permissions('manage_settings', admin_override=True) diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index 2eff775f3..440301428 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -106,6 +106,12 @@ class ServiceAPIClient(NotifyAdminAPIClient): def archive_service(self, service_id): return self.post('/service/{}/archive'.format(service_id), data=None) + def suspend_service(self, service_id): + return self.post('/service/{}/suspend'.format(service_id), data=None) + + def resume_service(self, service_id): + return self.post('/service/{}/resume'.format(service_id), data=None) + def remove_user_from_service(self, service_id, user_id): """ Remove a user from a service diff --git a/app/templates/flash_messages.html b/app/templates/flash_messages.html index 283689d8b..ebb2463d4 100644 --- a/app/templates/flash_messages.html +++ b/app/templates/flash_messages.html @@ -5,7 +5,7 @@ {{ banner( message, 'default' if ((category == 'default') or (category == 'default_with_tick')) else 'dangerous', - delete_button="Yes, {}".format(category) if category in ['delete', 'remove'] else None, + delete_button="Yes, {}".format(category) if category in ['delete', 'suspend', 'resume', 'remove'] else None, with_tick=True if category == 'default_with_tick' else False )}} {% endfor %} diff --git a/app/templates/views/service-settings.html b/app/templates/views/service-settings.html index 6b1c2a401..e23a50b4e 100644 --- a/app/templates/views/service-settings.html +++ b/app/templates/views/service-settings.html @@ -115,6 +115,18 @@ Archive service +
  • + + Suspend service + +
  • + {% endif %} + {% if not current_service.active %} +
  • + + Resume service + +
  • {% endif %} diff --git a/tests/app/main/views/test_service_settings.py b/tests/app/main/views/test_service_settings.py index 677a9b917..93475bb62 100644 --- a/tests/app/main/views/test_service_settings.py +++ b/tests/app/main/views/test_service_settings.py @@ -781,4 +781,74 @@ def test_cant_archive_inactive_service(client, platform_admin_user, service_one, assert response.status_code == 200 page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') - assert 'Deactivate service' not in {a.text for a in page.find_all('a', class_='button')} + assert 'Archive service' not in {a.text for a in page.find_all('a', class_='button')} + + +def test_suspend_service_after_confirm(client, platform_admin_user, service_one, mocker): + mocked_fn = mocker.patch('app.service_api_client.post', return_value=service_one) + + client.login(platform_admin_user, mocker, service_one) + response = client.post(url_for('main.suspend_service', service_id=service_one['id'])) + + assert response.status_code == 302 + assert response.location == url_for('main.service_settings', service_id=service_one['id'], _external=True) + assert mocked_fn.call_args == call('/service/{}/suspend'.format(service_one['id']), data=None) + + +def test_suspend_service_prompts_user(client, platform_admin_user, service_one, mocker): + mocked_fn = mocker.patch('app.service_api_client.post') + + client.login(platform_admin_user, mocker, service_one) + response = client.get(url_for('main.suspend_service', service_id=service_one['id'])) + + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert 'This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?' in \ + page.find('div', class_='banner-dangerous').text + assert mocked_fn.called is False + + +def test_cant_suspend_inactive_service(client, platform_admin_user, service_one, mocker): + service_one['active'] = False + + client.login(platform_admin_user, mocker, service_one) + response = client.get(url_for('main.service_settings', service_id=service_one['id'])) + + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert 'Suspend service' not in {a.text for a in page.find_all('a', class_='button')} + + +def test_resume_service_after_confirm(client, platform_admin_user, service_one, mocker): + service_one['active'] = False + mocked_fn = mocker.patch('app.service_api_client.post', return_value=service_one) + + client.login(platform_admin_user, mocker, service_one) + response = client.post(url_for('main.resume_service', service_id=service_one['id'])) + + assert response.status_code == 302 + assert response.location == url_for('main.service_settings', service_id=service_one['id'], _external=True) + assert mocked_fn.call_args == call('/service/{}/resume'.format(service_one['id']), data=None) + + +def test_resume_service_prompts_user(client, platform_admin_user, service_one, mocker): + service_one['active'] = False + mocked_fn = mocker.patch('app.service_api_client.post') + + client.login(platform_admin_user, mocker, service_one) + response = client.get(url_for('main.resume_service', service_id=service_one['id'])) + + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert 'This will resume the service. New api key are required for this service to use the API.' in \ + page.find('div', class_='banner-dangerous').text + assert mocked_fn.called is False + + +def test_cant_resume_active_service(client, platform_admin_user, service_one, mocker): + client.login(platform_admin_user, mocker, service_one) + response = client.get(url_for('main.service_settings', service_id=service_one['id'])) + + assert response.status_code == 200 + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert 'Resume service' not in {a.text for a in page.find_all('a', class_='button')}