diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index c02256964..ad744a313 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -118,22 +118,43 @@ def user_profile_email_confirm(token): @main.route("/user-profile/mobile-number", methods=['GET', 'POST']) +@main.route( + "/user-profile/mobile-number/delete", + methods=['GET'], + endpoint="user_profile_confirm_delete_mobile_number" +) @user_is_logged_in def user_profile_mobile_number(): + user = User.from_id(current_user.id) form = ChangeMobileNumberForm(mobile_number=current_user.mobile_number) if form.validate_on_submit(): session[NEW_MOBILE] = form.mobile_number.data return redirect(url_for('.user_profile_mobile_number_authenticate')) + if (request.endpoint == "main.user_profile_confirm_delete_mobile_number"): + flash("Are you sure you want to delete your mobile number from Notify?", 'delete') + return render_template( 'views/user-profile/change.html', thing='mobile number', - form_field=form.mobile_number + form_field=form.mobile_number, + user_auth=user.auth_type ) +@main.route("/user-profile/mobile-number/delete", methods=['POST']) +@user_is_logged_in +def user_profile_mobile_number_delete(): + if current_user.auth_type != 'email_auth': + abort(403) + + current_user.update(mobile_number=None) + + return redirect(url_for('.user_profile')) + + @main.route("/user-profile/mobile-number/authenticate", methods=['GET', 'POST']) @user_is_logged_in def user_profile_mobile_number_authenticate(): diff --git a/app/navigation.py b/app/navigation.py index 110a1557a..a364fd4d4 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -67,12 +67,14 @@ class HeaderNavigation(Navigation): }, 'user-profile': { 'user_profile', + 'user_profile_confirm_delete_mobile_number', 'user_profile_email', 'user_profile_email_authenticate', 'user_profile_email_confirm', 'user_profile_mobile_number', 'user_profile_mobile_number_authenticate', 'user_profile_mobile_number_confirm', + 'user_profile_mobile_number_delete', 'user_profile_name', 'user_profile_password', 'user_profile_disable_platform_admin_view', diff --git a/app/templates/views/user-profile/change.html b/app/templates/views/user-profile/change.html index ae8a54a13..fc8858572 100644 --- a/app/templates/views/user-profile/change.html +++ b/app/templates/views/user-profile/change.html @@ -20,9 +20,20 @@
{% call form_wrapper() %} {{ form_field(error_message_with_html=True) }} - {{ page_footer('Save') }} + {% if current_user.auth_type == 'email_auth' and (current_user.mobile_number and thing == "mobile number") %} + {{ page_footer( + 'Save', + delete_link=url_for( + 'main.user_profile_mobile_number_delete', + user_id=current_user.id + ), + delete_link_text='Delete your number' + ) + }} + {% else %} + {{ page_footer('Save')}} + {% endif %} {% endcall %}
- {% endblock %} diff --git a/tests/app/main/views/test_user_profile.py b/tests/app/main/views/test_user_profile.py index 21866e775..c01af3027 100644 --- a/tests/app/main/views/test_user_profile.py +++ b/tests/app/main/views/test_user_profile.py @@ -12,6 +12,8 @@ from app.models.webauthn_credential import ( ) from tests.conftest import ( create_api_user_active, + create_user, + fake_uuid, normalize_spaces, url_for_endpoint_with_token, ) @@ -91,6 +93,8 @@ def test_should_show_email_page( 'main.user_profile_email' ) assert page.select_one('h1').text.strip() == 'Change your email address' + # template is shared with "Change your mobile number" but we don't want to show Delete mobile number link + assert 'Delete your number' not in page.text def test_should_redirect_after_email_change( @@ -183,6 +187,75 @@ def test_should_show_mobile_number_page( ): page = client_request.get(('main.user_profile_mobile_number')) assert 'Change your mobile number' in page.text + assert 'Delete your number' not in page.text + + +def test_change_your_mobile_number_page_shows_delete_link_if_user_on_email_auth( + client_request, + api_user_active_email_auth, + mocker +): + mocker.patch('app.user_api_client.get_user', return_value=api_user_active_email_auth) + page = client_request.get(('main.user_profile_mobile_number')) + assert 'Change your mobile number' in page.text + assert 'Delete your number' in page.text + + +def test_change_your_mobile_number_page_doesnt_show_delete_link_if_user_has_no_mobile_number( + client_request, + api_user_active_email_auth, + mocker +): + user = create_user( + id=fake_uuid, + auth_type='email_auth', + mobile_number=None + ) + mocker.patch('app.user_api_client.get_user', return_value=user) + page = client_request.get(('main.user_profile_mobile_number')) + assert 'Change your mobile number' in page.text + assert 'Delete your number' not in page.text + + +def test_confirm_delete_mobile_number( + client_request, + api_user_active_email_auth, + mocker +): + mocker.patch('app.user_api_client.get_user', return_value=api_user_active_email_auth) + + page = client_request.get( + '.user_profile_confirm_delete_mobile_number', + _test_page_title=False, + ) + + assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( + 'Are you sure you want to delete your mobile number from Notify? ' + 'Yes, delete' + ) + assert 'action' not in page.select_one('.banner-dangerous form') + assert page.select_one('.banner-dangerous form')['method'] == 'post' + + +def test_delete_mobile_number( + client_request, + api_user_active_email_auth, + mocker +): + mocker.patch('app.user_api_client.get_user', return_value=api_user_active_email_auth) + mock_delete = mocker.patch('app.user_api_client.update_user_attribute') + + client_request.post( + '.user_profile_mobile_number_delete', + _expected_redirect=url_for( + '.user_profile', + _external=True, + ) + ) + mock_delete.assert_called_once_with( + api_user_active_email_auth["id"], + mobile_number=None + ) @pytest.mark.parametrize('phone_number_to_register_with', [ diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index 81a6ea634..06eccdd28 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -317,6 +317,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'usage', 'user_information', 'user_profile', + 'user_profile_confirm_delete_mobile_number', 'user_profile_confirm_delete_security_key', 'user_profile_delete_security_key', 'user_profile_disable_platform_admin_view', @@ -327,6 +328,7 @@ EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { 'user_profile_mobile_number', 'user_profile_mobile_number_authenticate', 'user_profile_mobile_number_confirm', + 'user_profile_mobile_number_delete', 'user_profile_name', 'user_profile_password', 'user_profile_security_keys',