From 179cae7fdae31c4b21a0debf7dd0fd35ec9ab84f Mon Sep 17 00:00:00 2001 From: Shaelyn Tolkamp <46355612+tolkamps1@users.noreply.github.com> Date: Fri, 7 Jun 2024 08:23:54 -0700 Subject: [PATCH] [EPICSYSTEM-79] Email verification token bug (#60) --- .../services/email_verification_service.py | 11 +-- .../api/test_email_verification_service.py | 93 ++++++++++++++++++- met-api/tests/utilities/factory_utils.py | 8 +- 3 files changed, 104 insertions(+), 8 deletions(-) diff --git a/met-api/src/met_api/services/email_verification_service.py b/met-api/src/met_api/services/email_verification_service.py index c46bca073..9db1e2640 100644 --- a/met-api/src/met_api/services/email_verification_service.py +++ b/met-api/src/met_api/services/email_verification_service.py @@ -64,12 +64,12 @@ def create(cls, email_verification: EmailVerificationSchema, email_verification['created_by'] = email_verification.get( 'participant_id') - email_verification['verification_token'] = uuid.uuid4() - EmailVerification.create(email_verification, session) + verification_token = uuid.uuid4() + EmailVerification.create({ **email_verification, 'verification_token': verification_token}, session) # TODO: remove this once email logic is brought over from submission service to here if email_verification.get('type', None) != EmailVerificationType.RejectedComment: - cls._send_verification_email(email_verification, subscription_type) + cls._send_verification_email({ **email_verification, 'verification_token': verification_token}, subscription_type) return email_verification @@ -239,14 +239,13 @@ def _get_tenant_name(tenant_id): @staticmethod def _get_project_name(subscription_type, tenant_name, engagement): - metadata_model: EngagementMetadataModel = EngagementMetadataModel.find_by_id(engagement.id) if subscription_type == SubscriptionTypes.TENANT.value: return tenant_name if subscription_type == SubscriptionTypes.PROJECT.value: metadata_model: EngagementMetadataModel = EngagementMetadataModel.find_by_id(engagement.id) - project_name = metadata_model.project_metadata.get('project_name', None) - return project_name or engagement.name + project_name = metadata_model.project_metadata.get('project_name', None) if metadata_model else engagement.name + return project_name if subscription_type == SubscriptionTypes.ENGAGEMENT.value: return engagement.name diff --git a/met-api/tests/unit/api/test_email_verification_service.py b/met-api/tests/unit/api/test_email_verification_service.py index 2898232c1..cad0a724d 100644 --- a/met-api/tests/unit/api/test_email_verification_service.py +++ b/met-api/tests/unit/api/test_email_verification_service.py @@ -18,10 +18,17 @@ """ import json +from http import HTTPStatus from faker import Faker +from unittest.mock import patch +import pytest + +from met_api.constants.email_verification import EmailVerificationType +from met_api.constants.subscription_type import SubscriptionTypes +from met_api.services.email_verification_service import EmailVerificationService from met_api.utils.enums import ContentType from tests.utilities.factory_scenarios import TestJwtClaims -from tests.utilities.factory_utils import factory_auth_header, factory_survey_and_eng_model, set_global_tenant +from tests.utilities.factory_utils import factory_auth_header, factory_email_verification, factory_survey_and_eng_model, set_global_tenant fake = Faker() @@ -40,3 +47,87 @@ def test_email_verification(client, jwt, session, notify_mock, ): # pylint:disa headers=headers, content_type=ContentType.JSON.value) assert rv.status_code == 200 + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_get_email_verification_by_token(client, jwt, session, side_effect, + expected_status): # pylint:disable=unused-argument + """Assert that an email verification can be fetched.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + email_verification = factory_email_verification(survey.id) + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.get(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + assert rv.json.get('verification_token') == email_verification.verification_token + assert rv.json.get('is_active') is True + with patch.object(EmailVerificationService, 'get_active', side_effect=side_effect): + rv = client.get(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status + # test email verification not found + email_verification_token = fake.text(max_nb_chars=20) + rv = client.get(f'/api/email_verification/{email_verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +def test_patch_email_verification_by_token(client, jwt, session): # pylint:disable=unused-argument + """Assert that an email verification can be fetched.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + email_verification = factory_email_verification(survey.id, verification_type=EmailVerificationType.Subscribe) + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == 200 + assert rv.json.get('verification_token') == email_verification.verification_token + assert rv.json.get('is_active') is False + with patch.object(EmailVerificationService, 'verify', side_effect=KeyError('Test error')): + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.NOT_FOUND + with patch.object(EmailVerificationService, 'verify', side_effect=ValueError('Test error')): + rv = client.put(f'/api/email_verification/{email_verification.verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + # test email verification not found to update the data + email_verification_token = fake.text(max_nb_chars=20) + rv = client.put(f'/api/email_verification/{email_verification_token}', + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == HTTPStatus.INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize('side_effect, expected_status', [ + (KeyError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), + (ValueError('Test error'), HTTPStatus.INTERNAL_SERVER_ERROR), +]) +def test_post_subscription_email_verification(client, jwt, session, notify_mock, + side_effect, expected_status): # pylint:disable=unused-argument + """Assert that an Subscription Email can be sent.""" + claims = TestJwtClaims.public_user_role + set_global_tenant() + survey, eng = factory_survey_and_eng_model() + to_dict = { + 'email_address': fake.email(), + 'survey_id': survey.id, + 'type': EmailVerificationType.Subscribe, + 'language': 'en', + } + headers = factory_auth_header(jwt=jwt, claims=claims) + rv = client.post(f'/api/email_verification/{SubscriptionTypes.PROJECT.value}/subscribe', + data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + + assert rv.status_code == 200 + with patch.object(EmailVerificationService, 'create', side_effect=side_effect): + rv = client.post(f'/api/email_verification/{SubscriptionTypes.PROJECT.value}/subscribe', + data=json.dumps(to_dict), + headers=headers, content_type=ContentType.JSON.value) + assert rv.status_code == expected_status \ No newline at end of file diff --git a/met-api/tests/utilities/factory_utils.py b/met-api/tests/utilities/factory_utils.py index 86df907f9..c5750e596 100644 --- a/met-api/tests/utilities/factory_utils.py +++ b/met-api/tests/utilities/factory_utils.py @@ -21,6 +21,7 @@ from met_api.config import get_named_config from met_api.constants.engagement_status import Status from met_api.constants.widget import WidgetType +from met_api.constants.email_verification import EmailVerificationType from met_api.models import Tenant from met_api.models.comment import Comment as CommentModel from met_api.models.email_verification import EmailVerification as EmailVerificationModel @@ -107,15 +108,20 @@ def factory_subscription_model(): return subscription -def factory_email_verification(survey_id): +def factory_email_verification(survey_id, verification_type=None, submission_id=None): """Produce a EmailVerification model.""" email_verification = EmailVerificationModel( verification_token=fake.uuid4(), is_active=True ) + email_verification.type = verification_type if verification_type else EmailVerificationType.Survey + if survey_id: email_verification.survey_id = survey_id + if submission_id: + email_verification.submission_id = submission_id + email_verification.save() return email_verification