diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index b4df99416..399bc1ac2 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -283,4 +283,7 @@ def confirm_edit_user_mobile_number(service_id, user_id): def cancel_invited_user(service_id, invited_user_id): current_service.cancel_invite(invited_user_id) + invited_user = InvitedUser.by_id_and_service_id(service_id, invited_user_id) + + flash(f'Invitation cancelled for {invited_user.email_address}', 'default_with_tick') return redirect(url_for('main.manage_users', service_id=service_id)) diff --git a/app/main/views/organisations.py b/app/main/views/organisations.py index 01c0224d4..8ab4eae0f 100644 --- a/app/main/views/organisations.py +++ b/app/main/views/organisations.py @@ -237,6 +237,9 @@ def remove_user_from_organisation(org_id, user_id): def cancel_invited_org_user(org_id, invited_user_id): org_invite_api_client.cancel_invited_user(org_id=org_id, invited_user_id=invited_user_id) + invited_org_user = InvitedOrgUser.by_id_and_org_id(org_id, invited_user_id) + + flash(f'Invitation cancelled for {invited_org_user.email_address}', 'default_with_tick') return redirect(url_for('main.manage_org_users', org_id=org_id)) diff --git a/app/models/user.py b/app/models/user.py index 669240867..0c27aff2e 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -464,6 +464,12 @@ class InvitedUser(JSONModel): folder_permissions, )) + @classmethod + def by_id_and_service_id(cls, service_id, invited_user_id): + return cls( + invite_api_client.get_invited_user(service_id, invited_user_id) + ) + def accept_invite(self): invite_api_client.accept_invite(self.service, self.id) @@ -586,6 +592,12 @@ class InvitedOrgUser(JSONModel): invited_org_user = session.get('invited_org_user') return cls(invited_org_user) if invited_org_user else None + @classmethod + def by_id_and_org_id(cls, org_id, invited_user_id): + return cls( + org_invite_api_client.get_invited_user(org_id, invited_user_id) + ) + def serialize(self, permissions_as_string=False): data = {'id': self.id, 'organisation': self.organisation, diff --git a/app/notify_client/invite_api_client.py b/app/notify_client/invite_api_client.py index e44ba506a..8954104ea 100644 --- a/app/notify_client/invite_api_client.py +++ b/app/notify_client/invite_api_client.py @@ -37,6 +37,11 @@ class InviteApiClient(NotifyAdminAPIClient): '/service/{}/invite'.format(service_id) )['data'] + def get_invited_user(self, service_id, invited_user_id): + return self.get( + f'/service/{service_id}/invite/{invited_user_id}' + )['data'] + def get_count_of_invites_with_permission(self, service_id, permission): if permission not in roles.keys(): raise TypeError('{} is not a valid permission'.format(permission)) diff --git a/app/notify_client/org_invite_api_client.py b/app/notify_client/org_invite_api_client.py index 33a1eb45f..c5d333d18 100644 --- a/app/notify_client/org_invite_api_client.py +++ b/app/notify_client/org_invite_api_client.py @@ -23,6 +23,11 @@ class OrgInviteApiClient(NotifyAdminAPIClient): resp = self.get(endpoint) return resp['data'] + def get_invited_user(self, org_id, invited_org_user_id): + return self.get( + f'/organisation/{org_id}/invite/{invited_org_user_id}' + )['data'] + def check_token(self, token): resp = self.get(url='/invite/organisation/{}'.format(token)) return resp['data'] diff --git a/tests/app/main/views/organisations/test_organisation.py b/tests/app/main/views/organisations/test_organisation.py index d0892f96e..36e1b4cdf 100644 --- a/tests/app/main/views/organisations/test_organisation.py +++ b/tests/app/main/views/organisations/test_organisation.py @@ -631,6 +631,34 @@ def test_organisation_trial_mode_services_doesnt_work_if_not_platform_admin( ) +def test_cancel_invited_org_user_cancels_user_invitations( + client_request, + mock_get_invites_for_organisation, + sample_org_invite, + mock_get_organisation, + mock_get_users_for_organisation, + mocker, +): + mock_cancel = mocker.patch('app.org_invite_api_client.cancel_invited_user') + mocker.patch('app.org_invite_api_client.get_invited_user', return_value=sample_org_invite) + + page = client_request.get( + 'main.cancel_invited_org_user', + org_id=ORGANISATION_ID, + invited_user_id=sample_org_invite['id'], + _follow_redirects=True + ) + assert normalize_spaces(page.h1.text) == 'Team members' + flash_banner = normalize_spaces( + page.find('div', class_='banner-default-with-tick').text + ) + assert flash_banner == f"Invitation cancelled for {sample_org_invite['email_address']}" + mock_cancel.assert_called_once_with( + org_id=ORGANISATION_ID, + invited_user_id=sample_org_invite['id'], + ) + + def test_organisation_settings_platform_admin_only( client_request, mock_get_organisation, diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index e27250644..e09fd98ff 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -1002,18 +1002,25 @@ def test_cancel_invited_user_cancels_user_invitations( mock_get_invites_for_service, sample_invite, active_user_with_permissions, + mock_get_users_by_service, + mock_get_template_folders, mocker, ): mock_cancel = mocker.patch('app.invite_api_client.cancel_invited_user') - client_request.get( + mocker.patch('app.invite_api_client.get_invited_user', return_value=sample_invite) + + page = client_request.get( 'main.cancel_invited_user', service_id=SERVICE_ONE_ID, invited_user_id=sample_invite['id'], - _expected_status=302, - _expected_redirect=url_for( - 'main.manage_users', service_id=SERVICE_ONE_ID, _external=True - ), + _follow_redirects=True, ) + + assert normalize_spaces(page.h1.text) == 'Team members' + flash_banner = normalize_spaces( + page.find('div', class_='banner-default-with-tick').text + ) + assert flash_banner == f"Invitation cancelled for {sample_invite['email_address']}" mock_cancel.assert_called_once_with( service_id=SERVICE_ONE_ID, invited_user_id=sample_invite['id'], diff --git a/tests/conftest.py b/tests/conftest.py index b2ece7b0c..a678f8692 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +import copy import json import os from contextlib import contextmanager @@ -2398,8 +2399,6 @@ def mock_create_invite(mocker, sample_invite): @pytest.fixture(scope='function') def mock_get_invites_for_service(mocker, service_one, sample_invite): - import copy - def _get_invites(service_id): data = [] for i in range(0, 5): @@ -3437,6 +3436,19 @@ def sample_org_invite(mocker, organisation_one): return org_invite_json(id_, invited_by, organisation, email_address, created_at, status) +@pytest.fixture(scope='function') +def mock_get_invites_for_organisation(mocker, sample_org_invite): + def _get_org_invites(org_id): + data = [] + for i in range(0, 5): + invite = copy.copy(sample_org_invite) + invite['email_address'] = 'user_{}@testnotify.gov.uk'.format(i) + data.append(invite) + return data + + return mocker.patch('app.models.user.OrganisationInvitedUsers.client_method', side_effect=_get_org_invites) + + @pytest.fixture(scope='function') def mock_check_org_invite_token(mocker, sample_org_invite): def _check_org_token(token):