mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-12 18:08:57 -04:00
Merge pull request #977 from GSA/ADMIN-97_Resend_Expired_Invites
Admin 97 resend expired invites
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -129,3 +129,6 @@ playwright/
|
||||
|
||||
# Pyenv
|
||||
.python-version
|
||||
|
||||
# Nodenv
|
||||
.node-version
|
||||
|
||||
@@ -24,6 +24,7 @@ EVENT_SCHEMAS = {
|
||||
"service_id",
|
||||
"ui_permissions",
|
||||
},
|
||||
"resend_user_invite_to_service": {"email_address", "resent_by_id", "service_id"},
|
||||
"cancel_user_invite_to_service": {"email_address", "canceled_by_id", "service_id"},
|
||||
"set_user_permissions": {
|
||||
"user_id",
|
||||
@@ -63,6 +64,10 @@ def create_cancel_user_invite_to_service_event(**kwargs):
|
||||
_send_event("cancel_user_invite_to_service", **kwargs)
|
||||
|
||||
|
||||
def create_resend_user_invite_to_service_event(**kwargs):
|
||||
_send_event("resend_user_invite_to_service", **kwargs)
|
||||
|
||||
|
||||
def create_add_user_to_service_event(**kwargs):
|
||||
_send_event("add_user_to_service", **kwargs)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from app.event_handlers import (
|
||||
create_invite_user_to_service_event,
|
||||
create_mobile_number_change_event,
|
||||
create_remove_user_from_service_event,
|
||||
create_resend_user_invite_to_service_event,
|
||||
)
|
||||
from app.formatters import redact_mobile_number
|
||||
from app.main import main
|
||||
@@ -331,3 +332,22 @@ def cancel_invited_user(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))
|
||||
|
||||
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/resend-invite/<uuid:invited_user_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions("manage_service")
|
||||
def resend_invite(service_id, invited_user_id):
|
||||
current_service.resend_invite(invited_user_id)
|
||||
|
||||
invited_user = InvitedUser.by_id_and_service_id(service_id, invited_user_id)
|
||||
create_resend_user_invite_to_service_event(
|
||||
email_address=invited_user.email_address,
|
||||
resent_by_id=current_user.id,
|
||||
service_id=service_id,
|
||||
)
|
||||
|
||||
flash(f"Invitation resent for {invited_user.email_address}", "default_with_tick")
|
||||
return redirect(url_for("main.manage_users", service_id=service_id))
|
||||
|
||||
@@ -190,6 +190,15 @@ class Service(JSONModel, SortByNameMixin):
|
||||
invited_user_id=str(invited_user_id),
|
||||
)
|
||||
|
||||
def resend_invite(self, invited_user_id):
|
||||
if str(invited_user_id) not in {user.id for user in self.invited_users}:
|
||||
abort(404)
|
||||
|
||||
return invite_api_client.resend_invite(
|
||||
service_id=self.id,
|
||||
invited_user_id=str(invited_user_id),
|
||||
)
|
||||
|
||||
def get_team_member(self, user_id):
|
||||
if str(user_id) not in {user.id for user in self.active_users}:
|
||||
abort(404)
|
||||
|
||||
@@ -32,11 +32,11 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
"folder_permissions": folder_permissions,
|
||||
}
|
||||
data = _attach_current_user(data)
|
||||
resp = self.post(url="/service/{}/invite".format(service_id), data=data)
|
||||
resp = self.post(url=f"/service/{service_id}/invite", data=data)
|
||||
return resp["data"]
|
||||
|
||||
def get_invites_for_service(self, service_id):
|
||||
return self.get("/service/{}/invite".format(service_id))["data"]
|
||||
return self.get(f"/service/{service_id}/invite")["data"]
|
||||
|
||||
def get_invited_user(self, invited_user_id):
|
||||
return self.get(f"/invite/service/{invited_user_id}")["data"]
|
||||
@@ -46,7 +46,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
|
||||
def get_count_of_invites_with_permission(self, service_id, permission):
|
||||
if permission not in all_ui_permissions:
|
||||
raise TypeError("{} is not a valid permission".format(permission))
|
||||
raise TypeError(f"{permission} is not a valid permission")
|
||||
return len(
|
||||
[
|
||||
invited_user
|
||||
@@ -56,22 +56,21 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def check_token(self, token):
|
||||
return self.get(url="/invite/service/check/{}".format(token))["data"]
|
||||
return self.get(url=f"/invite/service/check/{token}")["data"]
|
||||
|
||||
def cancel_invited_user(self, service_id, invited_user_id):
|
||||
data = {"status": "cancelled"}
|
||||
data = _attach_current_user(data)
|
||||
self.post(
|
||||
url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data
|
||||
)
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||
|
||||
def resend_invite(self, service_id, invited_user_id):
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}/resend", data={})
|
||||
|
||||
@cache.delete("service-{service_id}")
|
||||
@cache.delete("user-{invited_user_id}")
|
||||
def accept_invite(self, service_id, invited_user_id):
|
||||
data = {"status": "accepted"}
|
||||
self.post(
|
||||
url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data
|
||||
)
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||
|
||||
|
||||
invite_api_client = InviteApiClient()
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (invited)
|
||||
{%- elif user.status == 'cancelled' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (cancelled invite)
|
||||
{%- elif user.status == 'expired' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (expired invite)
|
||||
{%- elif user.id == current_user.id -%}
|
||||
<span class="live-search-relevant">(you)</span>
|
||||
{% else %}
|
||||
@@ -84,6 +86,8 @@
|
||||
{% if current_user.has_permissions('manage_service') %}
|
||||
{% if user.status == 'pending' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=user.id)}}">Cancel invitation<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.status == 'expired' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.resend_invite', service_id=current_service.id, invited_user_id=user.id)}}">Resend invite<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.is_editable_by(current_user) %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=user.id)}}">Change details<span class="usa-sr-only"> for {{ user.name }} {{ user.email_address }}</span></a>
|
||||
{% endif %}
|
||||
|
||||
@@ -1157,6 +1157,35 @@ def test_invite_user_with_email_auth_service(
|
||||
)
|
||||
|
||||
|
||||
def test_resend_expired_invitation(
|
||||
client_request,
|
||||
mock_get_invites_for_service,
|
||||
expired_invite,
|
||||
active_user_with_permissions,
|
||||
mock_get_users_by_service,
|
||||
mock_get_template_folders,
|
||||
mocker,
|
||||
):
|
||||
mock_resend = mocker.patch("app.invite_api_client.resend_invite")
|
||||
mocker.patch(
|
||||
"app.invite_api_client.get_invited_user_for_service",
|
||||
return_value=expired_invite,
|
||||
)
|
||||
page = client_request.get(
|
||||
"main.resend_invite",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
invited_user_id=expired_invite["id"],
|
||||
_follow_redirects=True,
|
||||
)
|
||||
assert normalize_spaces(page.h1.text) == "Team members"
|
||||
assert mock_resend.called
|
||||
called_args = set(mock_resend.call_args.args) | set(
|
||||
mock_resend.call_args.kwargs.values()
|
||||
)
|
||||
assert SERVICE_ONE_ID in called_args
|
||||
assert expired_invite["id"] in called_args
|
||||
|
||||
|
||||
def test_cancel_invited_user_cancels_user_invitations(
|
||||
client_request,
|
||||
mock_get_invites_for_service,
|
||||
@@ -1168,7 +1197,8 @@ def test_cancel_invited_user_cancels_user_invitations(
|
||||
):
|
||||
mock_cancel = mocker.patch("app.invite_api_client.cancel_invited_user")
|
||||
mocker.patch(
|
||||
"app.invite_api_client.get_invited_user_for_service", return_value=sample_invite
|
||||
"app.invite_api_client.get_invited_user_for_service",
|
||||
return_value=sample_invite,
|
||||
)
|
||||
|
||||
page = client_request.get(
|
||||
|
||||
@@ -36,6 +36,7 @@ EXCLUDED_ENDPOINTS = tuple(
|
||||
"callbacks",
|
||||
"cancel_invited_org_user",
|
||||
"cancel_invited_user",
|
||||
"resend_invite",
|
||||
"cancel_job",
|
||||
"change_user_auth",
|
||||
"check_and_resend_text_code",
|
||||
|
||||
@@ -1892,6 +1892,30 @@ def sample_invite(mocker, service_one):
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def expired_invite(service_one):
|
||||
id_ = USER_ONE_ID
|
||||
from_user = service_one["users"][0]
|
||||
email_address = "invited_user@test.gsa.gov"
|
||||
service_id = service_one["id"]
|
||||
permissions = "view_activity,send_emails,send_texts,manage_settings,manage_users,manage_api_keys"
|
||||
created_at = str(datetime.utcnow() - timedelta(days=3))
|
||||
auth_type = "sms_auth"
|
||||
folder_permissions = []
|
||||
|
||||
return invite_json(
|
||||
id_,
|
||||
from_user,
|
||||
service_id,
|
||||
email_address,
|
||||
permissions,
|
||||
created_at,
|
||||
"expired",
|
||||
auth_type,
|
||||
folder_permissions,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def mock_create_invite(mocker, sample_invite):
|
||||
def _create_invite(
|
||||
|
||||
Reference in New Issue
Block a user