Skip to content

Commit

Permalink
send project invtation email when email is updated
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvin-muchiri committed Jul 20, 2023
1 parent c3b2b0b commit 2274a88
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 15 deletions.
50 changes: 42 additions & 8 deletions onadata/apps/api/tests/viewsets/test_project_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3018,6 +3018,9 @@ def test_role_valid(self, mock_send_mail):
mock_send_mail.assert_not_called()


@patch(
"onadata.libs.serializers.project_invitation_serializer.send_project_invitation_email_async.delay"
)
class UpdateProjectInvitationTestCase(TestAbstractViewSet):
"""Tests for update project invitation"""

Expand All @@ -3031,25 +3034,25 @@ def setUp(self):
status=ProjectInvitation.Status.PENDING,
)

def test_authentication(self):
def test_authentication(self, mock_send_mail):
"""Authentication is required"""
request = self.factory.put("/", data={})
response = self.view(request, pk=self.project.pk)
self.assertEqual(response.status_code, 401)

def test_invalid_project(self):
def test_invalid_project(self, mock_send_mail):
"""Invalid project is handled"""
request = self.factory.put("/", data={}, **self.extra)
response = self.view(request, pk=817)
self.assertEqual(response.status_code, 404)

def test_invalid_invitation_id(self):
def test_invalid_invitation_id(self, mock_send_mail):
"""Invalid project invitation is handled"""
request = self.factory.put("/", data={}, **self.extra)
response = self.view(request, pk=self.project.pk)
self.assertEqual(response.status_code, 404)

def test_only_admins_allowed(self):
def test_only_admins_allowed(self, mock_send_mail):
"""Only project admins are allowed to update project invitation"""
# login as editor alice
alice_data = {"username": "alice", "email": "[email protected]"}
Expand All @@ -3070,7 +3073,8 @@ def test_only_admins_allowed(self):
else:
self.assertEqual(response.status_code, 403)

def test_update(self):
@override_settings(PROJECT_INVITATION_URL="https://example.com/register")
def test_update(self, mock_send_mail):
"""We can update an invitation"""
payload = {
"email": "[email protected]",
Expand All @@ -3097,8 +3101,11 @@ def test_update(self):
"status": 1,
},
)
mock_send_mail.assert_called_once_with(
self.invitation.pk, "https://example.com/register"
)

def test_update_role_only(self):
def test_update_role_only(self, mock_send_mail):
"""We can update role only"""
payload = {
"email": self.invitation.email,
Expand All @@ -3124,8 +3131,10 @@ def test_update_role_only(self):
"status": 1,
},
)
mock_send_mail.assert_not_called()

def test_update_email_only(self):
@override_settings(PROJECT_INVITATION_URL="https://example.com/register")
def test_update_email_only(self, mock_send_mail):
"""We can update email only"""
payload = {
"email": "[email protected]",
Expand All @@ -3151,8 +3160,11 @@ def test_update_email_only(self):
"status": 1,
},
)
mock_send_mail.assert_called_once_with(
self.invitation.pk, "https://example.com/register"
)

def test_only_pending_allowed(self):
def test_only_pending_allowed(self, mock_send_mail):
"""Only pending invitation can be updated"""
for value, _ in ProjectInvitation.Status.choices:
invitation = self.project.invitations.create(
Expand All @@ -3174,6 +3186,28 @@ def test_only_pending_allowed(self):
else:
self.assertEqual(response.status_code, 400)

def test_user_unregistered(self, mock_send_mail):
"""Email cannot be updated to that of an unregistered user"""
alice_data = {"username": "alice", "email": "[email protected]"}
self._create_user_profile(alice_data)
post_data = {
"email": alice_data["email"],
"role": "editor",
"invitation_id": self.invitation.id,
}
request = self.factory.put(
"/",
data=json.dumps(post_data),
content_type="application/json",
**self.extra,
)
response = self.view(request, pk=self.project.pk)
print("Helleo", response.data)
self.assertEqual(response.status_code, 400)
self.invitation.refresh_from_db()
# invitation email not updated
self.assertEqual(self.invitation.email, "[email protected]")


class RevokeInvitationTestCase(TestAbstractViewSet):
"""Tests for revoke invitation"""
Expand Down
27 changes: 20 additions & 7 deletions onadata/libs/serializers/project_invitation_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ def validate_role(self, role):

return role

def _validate_email_exists(self, email):
"""Email should not be of an existing user"""
if User.objects.filter(email=email).exists():
raise serializers.ValidationError(_("User already exists"))

def _send_project_invitation_email(self, invitation_id: str) -> None:
"""Send project invitation email"""
project_activation_url = get_project_invitation_url(self.context["request"])
send_project_invitation_email_async.delay(invitation_id, project_activation_url)

def create(self, validated_data):
if ProjectInvitation.objects.filter(
email=validated_data["email"],
Expand All @@ -69,15 +79,11 @@ def create(self, validated_data):
).exists():
raise serializers.ValidationError(_("Invitation already exists."))

# email should not be of an existing user
if User.objects.filter(email=validated_data["email"]).exists():
raise serializers.ValidationError(_("User already exists"))

self._validate_email_exists(validated_data["email"])
instance = super().create(validated_data)
instance.invited_by = self.context["request"].user
instance.save()
project_activation_url = get_project_invitation_url(self.context["request"])
send_project_invitation_email_async.delay(instance.id, project_activation_url)
self._send_project_invitation_email(instance.id)

return instance

Expand All @@ -88,7 +94,14 @@ def update(self, instance, validated_data):
_("Only pending invitations can be updated")
)

return super().update(instance, validated_data)
self._validate_email_exists(validated_data["email"])
has_email_changed = instance.email != validated_data["email"]
updated_instance = super().update(instance, validated_data)

if has_email_changed:
self._send_project_invitation_email(instance.id)

return updated_instance


# pylint: disable=abstract-method
Expand Down

0 comments on commit 2274a88

Please sign in to comment.