From 0c7d88d3c19faa105b15963aebbcef8d5e37fe40 Mon Sep 17 00:00:00 2001 From: Adam King Date: Wed, 18 Sep 2024 16:43:06 -0500 Subject: [PATCH] Revert "#1953 - IIR - Integrate permission check into contact info lookup" (#1997) --- .talismanrc | 8 - app/celery/contact_information_tasks.py | 208 +-- app/models.py | 9 - app/notifications/process_notifications.py | 5 +- app/va/va_profile/__init__.py | 5 +- app/va/va_profile/exceptions.py | 2 +- app/va/va_profile/va_profile_client.py | 173 +-- .../dev/vaec-api-task-definition.json | 4 - .../dev/vaec-celery-task-definition.json | 4 - .../perf/vaec-api-task-definition.json | 4 - .../perf/vaec-celery-task-definition.json | 4 - .../prod/vaec-api-task-definition.json | 4 - .../prod/vaec-celery-task-definition.json | 4 - .../staging/vaec-api-task-definition.json | 4 - .../staging/vaec-celery-task-definition.json | 4 - ...ontact_information_tasks_for_profile_v3.py | 381 ------ ...ecipient_communication_permissions_task.py | 4 - .../test_process_notifications.py | 6 +- ...st_process_notifications_for_profile_v3.py | 1117 ----------------- .../test_va_profile_client_for_profile_v3.py | 404 ------ 20 files changed, 55 insertions(+), 2299 deletions(-) delete mode 100644 tests/app/celery/test_contact_information_tasks_for_profile_v3.py delete mode 100644 tests/app/notifications/test_process_notifications_for_profile_v3.py delete mode 100644 tests/app/va/va_profile/test_va_profile_client_for_profile_v3.py diff --git a/.talismanrc b/.talismanrc index 0819dc2bf2..4d34aa1f18 100644 --- a/.talismanrc +++ b/.talismanrc @@ -11,12 +11,4 @@ fileignoreconfig: checksum: 34d12acdf749363555c31add4e7e7afa9e2a27afd792bd98c85f331b87bd7112 - filename: scripts/trigger_task.py checksum: 0e9d244dbe285de23fc84bb643407963dacf7d25a3358373f01f6272fb217778 -- filename: tests/app/notifications/test_process_notifications_for_profile_v3.py - checksum: 4e15e63d349635131173ffdd7aebcd547621db08de877ef926d3a41fde72d065 -- filename: app/template/rest.py - checksum: 1e5bdac8bc694d50f8f656dec127dd036b7b1b5b6156e3282d3411956c71ba0b -- filename: app/service/rest.py - checksum: b42aefd1ae0e6ea76e75db4cf14d425facd0941943b17f7ba2e41f850ad1ec23 -- filename: app/celery/contact_information_tasks.py - checksum: 80d0acf88bafb1358583016b9e143f4523ef1160d6eacdc9754ca68859b90eae version: "1.0" diff --git a/app/celery/contact_information_tasks.py b/app/celery/contact_information_tasks.py index e37c3d1f0f..2ae4a1dd55 100644 --- a/app/celery/contact_information_tasks.py +++ b/app/celery/contact_information_tasks.py @@ -1,28 +1,14 @@ +from flask import current_app from app import notify_celery, va_profile_client -from celery import Task from app.celery.common import can_retry, handle_max_retries_exceeded from app.celery.exceptions import AutoRetryException from app.celery.service_callback_tasks import check_and_queue_callback_task +from app.va.identifier import IdentifierType +from app.va.va_profile import VAProfileRetryableException, VAProfileNonRetryableException, NoContactInfoException from app.dao.notifications_dao import get_notification_by_id, dao_update_notification, update_notification_status_by_id +from app.models import NOTIFICATION_PERMANENT_FAILURE, EMAIL_TYPE, SMS_TYPE from app.exceptions import NotificationTechnicalFailureException, NotificationPermanentFailureException -from app.feature_flags import FeatureFlag, is_feature_enabled -from app.models import ( - NOTIFICATION_PERMANENT_FAILURE, - NOTIFICATION_PREFERENCES_DECLINED, - EMAIL_TYPE, - SMS_TYPE, - Notification, - RecipientIdentifier, -) -from app.va.identifier import IdentifierType -from app.va.va_profile import ( - VAProfileRetryableException, - VAProfileNonRetryableException, - NoContactInfoException, - VAProfileResult, -) -from app.va.va_profile.exceptions import VAProfileIDNotFoundException, CommunicationItemNotFoundException -from flask import current_app +from app.va.va_profile.exceptions import VAProfileIDNotFoundException from notifications_utils.statsd_decorators import statsd from requests import Timeout @@ -38,197 +24,55 @@ ) @statsd(namespace='tasks') def lookup_contact_info( - self: Task, - notification_id: str, + self, + notification_id, ): - """ - Celery task to look up contact information (email/phone number) for a given notification. - If the feature flag, VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, is enabled, - also check for related communication permissions. - - Args: - self (Task): The Celery task instance. - notification_id (str): The ID of the notification for which to look up contact information. - - Raises: - AutoRetryException: If a retryable exception occurs during the lookup process. - NotificationTechnicalFailureException: If the maximum retries have been exceeded. - NotificationPermanentFailureException: If the exception indicates a permanent failure. - Exception: If an unhandled exception occurs. - - Returns: - None - """ current_app.logger.info('Looking up contact information for notification_id: %s.', notification_id) notification = get_notification_by_id(notification_id) recipient_identifier = notification.recipient_identifiers[IdentifierType.VA_PROFILE_ID.value] - should_send = notification.default_send try: - if is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP): - result = get_profile_result(notification, recipient_identifier) - recipient = result.recipient - should_send = result.communication_allowed - permission_message = result.permission_message + if EMAIL_TYPE == notification.notification_type: + recipient = va_profile_client.get_email(recipient_identifier) + elif SMS_TYPE == notification.notification_type: + recipient = va_profile_client.get_telephone(recipient_identifier) else: - recipient = get_recipient( - notification.notification_type, - notification_id, - recipient_identifier, + raise NotImplementedError( + f'The task lookup_contact_info failed for notification {notification_id}. ' + f'{notification.notification_type} is not supported' ) - except Exception as e: - handle_lookup_contact_info_exception(self, notification, recipient_identifier, e) - - notification.to = recipient - dao_update_notification(notification) - - if not should_send: - handle_communication_not_allowed(notification, recipient_identifier, permission_message) - - -def get_recipient( - notification_type: str, - notification_id: str, - recipient_identifier: RecipientIdentifier, -) -> str: - """ - Retrieve the recipient email or phone number. - - Args: - notification_type (str): The type of recipient info requested. - notification_id (str): The notification ID associated with this request. - recipient_identifier (RecipientIdentifier): The VA profile ID to retrieve the profile for. - - Returns: - str: The recipient email or phone number. - """ - if notification_type == EMAIL_TYPE: - return va_profile_client.get_email(recipient_identifier) - elif notification_type == SMS_TYPE: - return va_profile_client.get_telephone(recipient_identifier) - else: - raise NotImplementedError( - f'The task lookup_contact_info failed for notification {notification_id}. ' - f'{notification_type} is not supported' - ) - - -def get_profile_result( - notification: Notification, - recipient_identifier: RecipientIdentifier, -) -> VAProfileResult: - """ - Retrieve the result of looking up contact info from VA Profile. - - Args: - notification (Notification): The Notification object to get contact info and permissions for. - recipient_identifier (RecipientIdentifier): The VA profile ID to retrieve the profile for. - - Returns: - VAProfileResult: The contact info result from VA Profile. - """ - if notification.notification_type == EMAIL_TYPE: - return va_profile_client.get_email_with_permission(recipient_identifier, notification.default_send) - elif notification.notification_type == SMS_TYPE: - return va_profile_client.get_telephone_with_permission(recipient_identifier, notification.default_send) - else: - raise NotImplementedError( - f'The task lookup_contact_info failed for notification {notification.id}. ' - f'{notification.notification_type} is not supported' - ) - - -def handle_lookup_contact_info_exception( - lookup_task: Task, notification: Notification, recipient_identifier: RecipientIdentifier, e: Exception -): - """ - Handles exceptions that occur during the lookup of contact information. - - Args: - lookup_task (Task): The task object that is performing the lookup. - notification (Notification): The notification object associated with the lookup. - recipient_identifier (RecipientIdentifier): The identifier of the recipient. - e (Exception): The exception that was raised during the lookup. - - Raises: - AutoRetryException: If the exception is retryable and the task can be retried. - NotificationTechnicalFailureException: If the maximum retries have been exceeded. - NotificationPermanentFailureException: If the exception indicates a permanent failure. - Exception: If an unhandled exception occurs. - - Returns: - str or None: A message indicating the result of the exception handling, or None if no action is needed. - """ - if isinstance(e, (Timeout, VAProfileRetryableException)): - if can_retry(lookup_task.request.retries, lookup_task.max_retries, notification.id): - current_app.logger.warning('Unable to get contact info for notification id: %s, retrying', notification.id) + except (Timeout, VAProfileRetryableException) as e: + if can_retry(self.request.retries, self.max_retries, notification_id): + current_app.logger.warning('Unable to get contact info for notification id: %s, retrying', notification_id) raise AutoRetryException(f'Found {type(e).__name__}, autoretrying...', e, e.args) else: - msg = handle_max_retries_exceeded(notification.id, 'lookup_contact_info') + msg = handle_max_retries_exceeded(notification_id, 'lookup_contact_info') check_and_queue_callback_task(notification) raise NotificationTechnicalFailureException(msg) - elif isinstance(e, NoContactInfoException): + except NoContactInfoException as e: message = ( - f"Can't proceed after querying VA Profile for contact information for {notification.id}. " + f"Can't proceed after querying VA Profile for contact information for {notification_id}. " 'Stopping execution of following tasks. Notification has been updated to permanent-failure.' ) current_app.logger.warning('%s - %s: %s', e.__class__.__name__, str(e), message) update_notification_status_by_id( - notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason + notification_id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason ) check_and_queue_callback_task(notification) raise NotificationPermanentFailureException(message) from e - elif isinstance(e, (VAProfileIDNotFoundException, VAProfileNonRetryableException)): + except (VAProfileIDNotFoundException, VAProfileNonRetryableException) as e: current_app.logger.exception(e) message = ( - f'The task lookup_contact_info failed for notification {notification.id}. ' + f'The task lookup_contact_info failed for notification {notification_id}. ' 'Notification has been updated to permanent-failure' ) update_notification_status_by_id( - notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason + notification_id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason ) check_and_queue_callback_task(notification) raise NotificationPermanentFailureException(message) from e - elif isinstance(e, CommunicationItemNotFoundException): - current_app.logger.info( - 'Communication item for recipient %s not found on notification %s', - recipient_identifier.id_value, - notification.id, - ) - - return None if notification.default_send else 'No recipient opt-in found for explicit preference' - else: - current_app.logger.exception(f'Unhandled exception for notification {notification.id}: {e}') - raise e - -def handle_communication_not_allowed( - notification: Notification, recipient_identifier: RecipientIdentifier, permission_message: str -): - """ - Handles the scenario where communication is not allowed for a given notification. - - Args: - notification (Notification): The notification object associated with the communication. - recipient_identifier (RecipientIdentifier): The identifier of the recipient. - permission_message (str): The message indicating the reason for permission denial. - - Raises: - NotificationPermanentFailureException: If the recipient has declined permission to receive notifications. - """ - if is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP): - current_app.logger.info( - 'Permission denied for recipient %s for notification %s', - recipient_identifier.id_value, - notification.id, - ) - reason = permission_message if permission_message is not None else 'Contact preferences set to false' - update_notification_status_by_id(notification.id, NOTIFICATION_PREFERENCES_DECLINED, status_reason=reason) - - message = f'The recipient for notification {notification.id} has declined permission to receive notifications.' - current_app.logger.info(message) - - check_and_queue_callback_task(notification) - raise NotificationPermanentFailureException(message) + notification.to = recipient + dao_update_notification(notification) diff --git a/app/models.py b/app/models.py index dd87c83314..79822ae4d3 100644 --- a/app/models.py +++ b/app/models.py @@ -1,7 +1,6 @@ import datetime import uuid import itertools -from functools import cached_property from typing import Dict, Any from app import ( DATETIME_FORMAT, @@ -1387,14 +1386,6 @@ class Notification(db.Model): {}, ) - @cached_property - def default_send(self): - if self.template.communication_item_id: - communication_item = CommunicationItem.query.get(self.template.communication_item_id) - if communication_item: - return communication_item.default_send_indicator - return True - @property def personalisation(self): if self._personalisation: diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index c464c670db..e6fde1c8f4 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -275,10 +275,7 @@ def send_to_queue_for_recipient_info_based_on_recipient_identifier( tasks.append(lookup_contact_info.si(notification.id).set(queue=QueueNames.LOOKUP_CONTACT_INFO)) - if ( - not is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP) - and communication_item_id - ): + if communication_item_id: tasks.append( lookup_recipient_communication_permissions.si(notification.id).set( queue=QueueNames.COMMUNICATION_ITEM_PERMISSIONS diff --git a/app/va/va_profile/__init__.py b/app/va/va_profile/__init__.py index fc0ad415aa..853592e4d0 100644 --- a/app/va/va_profile/__init__.py +++ b/app/va/va_profile/__init__.py @@ -4,7 +4,4 @@ VAProfileNonRetryableException, NoContactInfoException, ) -from .va_profile_client import ( # noqa: F401 - VAProfileClient, - VAProfileResult, -) +from .va_profile_client import VAProfileClient # noqa: F401 diff --git a/app/va/va_profile/exceptions.py b/app/va/va_profile/exceptions.py index 329d8ef420..0cbff0f846 100644 --- a/app/va/va_profile/exceptions.py +++ b/app/va/va_profile/exceptions.py @@ -26,5 +26,5 @@ class ContactPreferencesException(VAProfileNonRetryableException): failure_reason = 'VA Profile contact preferences not allowing contact' -class CommunicationItemNotFoundException(VAProfileNonRetryableException): +class CommunicationItemNotFoundException(Exception): failure_reason = 'No communication bio found from VA Profile' diff --git a/app/va/va_profile/va_profile_client.py b/app/va/va_profile/va_profile_client.py index 0b8720da36..4fc7962b26 100644 --- a/app/va/va_profile/va_profile_client.py +++ b/app/va/va_profile/va_profile_client.py @@ -1,6 +1,5 @@ from __future__ import annotations -from dataclasses import dataclass from enum import Enum from http.client import responses from typing import TYPE_CHECKING, Dict, List @@ -9,10 +8,7 @@ import requests from app.va.identifier import OIDS, IdentifierType, transform_to_fhir_format from app.va.va_profile import NoContactInfoException, VAProfileNonRetryableException, VAProfileRetryableException -from app.va.va_profile.exceptions import ( - CommunicationItemNotFoundException, - VAProfileIDNotFoundException, -) +from app.va.va_profile.exceptions import CommunicationItemNotFoundException, VAProfileIDNotFoundException if TYPE_CHECKING: from app.models import RecipientIdentifier @@ -26,17 +22,6 @@ } -class CommunicationChannel(Enum): - EMAIL = ('Email', 2) - TEXT = ('Text', 1) - - def __new__(cls, value, id): - obj = object.__new__(cls) - obj._value_ = value - obj.id = id - return obj - - class PhoneNumberType(Enum): MOBILE = 'MOBILE' HOME = 'HOME' @@ -49,13 +34,6 @@ def valid_type_values() -> list[str]: return [PhoneNumberType.MOBILE.value, PhoneNumberType.HOME.value] -@dataclass -class VAProfileResult: - recipient: str - communication_allowed: bool - permission_message: str | None - - class VAProfileClient: SUCCESS_STATUS = 'COMPLETED_SUCCESS' EMAIL_BIO_TYPE = 'emails' @@ -116,6 +94,8 @@ def get_telephone(self, va_profile_id: RecipientIdentifier) -> str: self.logger.debug('V3 Profile - Retrieved ContactInformation: %s', contact_info) telephones: List[Telephone] = contact_info.get(self.PHONE_BIO_TYPE, []) + phone_numbers = ', '.join([tel['phoneNumber'] for tel in telephones]) + self.logger.debug('V3 Profile telephones: %s', phone_numbers) sorted_telephones = sorted( [phone for phone in telephones if phone['phoneType'] == PhoneNumberType.MOBILE.value], key=lambda phone: iso8601.parse_date(phone['createDate']), @@ -156,94 +136,6 @@ def get_email(self, va_profile_id: RecipientIdentifier) -> str: self.statsd_client.incr('clients.va-profile.get-email.failure') self._raise_no_contact_info_exception(self.EMAIL_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID)) - def get_telephone_with_permission( - self, - va_profile_id: RecipientIdentifier, - default_send=True, - ) -> VAProfileResult: - """ - Retrieve the telephone number from the profile information for a given VA profile ID. - - Args: - va_profile_id (RecipientIdentifier): The VA profile ID to retrieve the telephone number for. - - Returns: - VAProfileResults: The result data. - Property recipient is the telephone number retrieved from the VA Profile service. - Property communication_allowed is true when VA Profile service indicates that the recipient has allowed communication. - Property permission_message may contain an error message if the permission check encountered an exception. - """ - profile = self.get_profile(va_profile_id) - communication_allowed = True - permission_message = None - try: - communication_allowed = self.get_is_communication_allowed_from_profile(profile, CommunicationChannel.TEXT) - except CommunicationItemNotFoundException: - self.logger.info('Communication item for recipient %s not found', va_profile_id) - communication_allowed = default_send - permission_message = f'V3 Profile - No recipient opt-in found for explicit preference, falling back to default send: {default_send} (Recipient Identifier {va_profile_id})' - - contact_info: ContactInformation = profile.get('contactInformation', {}) - - telephones: List[Telephone] = contact_info.get(self.PHONE_BIO_TYPE, []) - sorted_telephones = sorted( - [phone for phone in telephones if phone['phoneType'] == PhoneNumberType.MOBILE.value], - key=lambda phone: iso8601.parse_date(phone['createDate']), - reverse=True, - ) - if sorted_telephones: - if ( - sorted_telephones[0].get('countryCode') - and sorted_telephones[0].get('areaCode') - and sorted_telephones[0].get('phoneNumber') - ): - self.statsd_client.incr('clients.va-profile.get-telephone.success') - telephone_result = f"+{sorted_telephones[0]['countryCode']}{sorted_telephones[0]['areaCode']}{sorted_telephones[0]['phoneNumber']}" - return VAProfileResult(telephone_result, communication_allowed, permission_message) - - self.statsd_client.incr('clients.va-profile.get-telephone.failure') - self._raise_no_contact_info_exception(self.PHONE_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID)) - - def get_email_with_permission( - self, - va_profile_id: RecipientIdentifier, - default_send=True, - ) -> VAProfileResult: - """ - Retrieve the email address from the profile information for a given VA profile ID. - - Args: - va_profile_id (RecipientIdentifier): The VA profile ID to retrieve the email address for. - - Returns: - VAProfileResults: The result data. - Property recipient is the telephone number retrieved from the VA Profile service. - Property communication_allowed is true when VA Profile service indicates that the recipient has allowed communication. - Property permission_message may contain an error message if the permission check encountered an exception. - """ - profile = self.get_profile(va_profile_id) - communication_allowed = default_send - permission_message = None - try: - communication_allowed = self.get_is_communication_allowed_from_profile(profile, CommunicationChannel.EMAIL) - except CommunicationItemNotFoundException: - self.logger.info('Communication item for recipient %s not found', va_profile_id) - permission_message = f'V3 Profile - No recipient opt-in found for explicit preference, falling back to default send: {default_send} (Recipient Identifier {va_profile_id})' - - contact_info: ContactInformation = profile.get('contactInformation', {}) - sorted_emails = sorted( - contact_info.get(self.EMAIL_BIO_TYPE, []), - key=lambda email: iso8601.parse_date(email['createDate']), - reverse=True, - ) - if sorted_emails: - self.statsd_client.incr('clients.va-profile.get-email.success') - email_result = sorted_emails[0].get('emailAddressText') - return VAProfileResult(email_result, communication_allowed, permission_message) - - self.statsd_client.incr('clients.va-profile.get-email.failure') - self._raise_no_contact_info_exception(self.EMAIL_BIO_TYPE, va_profile_id, contact_info.get(self.TX_AUDIT_ID)) - def get_is_communication_allowed( self, recipient_id: RecipientIdentifier, @@ -270,11 +162,31 @@ def get_is_communication_allowed( communication_permissions: CommunicationPermissions = self.get_profile(recipient_id).get( 'communicationPermissions', {} ) + self.logger.debug( + 'V3 Profile -- Retrieved Communication Permissions for recipient_id: %s, notification_id: \ + %s, notification_type: %s -- %s', + recipient_id.id_value, + notification_id, + notification_type, + communication_permissions, + ) for perm in communication_permissions: + self.logger.debug( + 'V3 Profile -- Found communication item id %s on recipient %s for notification %s', + communication_item_id, + recipient_id.id_value, + notification_id, + ) if ( perm['communicationChannelName'] == VA_NOTIFY_TO_VA_PROFILE_NOTIFICATION_TYPES[notification_type] and perm['communicationItemId'] == communication_item_id ): + self.logger.debug( + 'V3 Profile -- %s notification: Value of allowed is %s for notification %s', + perm['communicationChannelName'], + perm['allowed'], + notification_id, + ) self.statsd_client.incr('clients.va-profile.get-communication-item-permission.success') assert isinstance(perm['allowed'], bool) return perm['allowed'] @@ -291,45 +203,6 @@ def get_is_communication_allowed( self.statsd_client.incr('clients.va-profile.get-communication-item-permission.no-permissions') raise CommunicationItemNotFoundException - def get_is_communication_allowed_from_profile( - self, - profile: Profile, - notification_type: CommunicationChannel, - ) -> bool: - """ - Determine if communication is allowed for a given recipient, communication item, and notification type. - - Args: - profile (Profile): The recipient's profile. - notification_type (CommunicationChannel): The type of the notification. - - Returns: - bool: True if communication is allowed, False otherwise. - - Raises: - CommunicationItemNotFoundException: If no communication permissions are found for the given parameters. - """ - - communication_permissions: CommunicationPermissions = profile.get('communicationPermissions', {}) - for perm in communication_permissions: - if ( - perm['communicationChannelName'] == notification_type.value - and perm['communicationItemId'] == notification_type.id - ): - self.statsd_client.incr('clients.va-profile.get-communication-item-permission.success') - assert isinstance(perm['allowed'], bool) - return perm['allowed'] - - self.logger.debug( - 'V3 Profile -- did not have permission for communication item %s and channel %s', - notification_type.id, - notification_type.value, - ) - - # TODO 893 - use default communication item settings when that has been implemented - self.statsd_client.incr('clients.va-profile.get-communication-item-permission.no-permissions') - raise CommunicationItemNotFoundException - def _handle_exceptions(self, va_profile_id_value: str, error: Exception): """ Handle exceptions that occur during requests to the VA Profile service. diff --git a/cd/application-deployment/dev/vaec-api-task-definition.json b/cd/application-deployment/dev/vaec-api-task-definition.json index 1582d3fc42..348936941d 100644 --- a/cd/application-deployment/dev/vaec-api-task-definition.json +++ b/cd/application-deployment/dev/vaec-api-task-definition.json @@ -222,10 +222,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "dev-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/cd/application-deployment/dev/vaec-celery-task-definition.json b/cd/application-deployment/dev/vaec-celery-task-definition.json index 7506d57220..9e4b0a1763 100644 --- a/cd/application-deployment/dev/vaec-celery-task-definition.json +++ b/cd/application-deployment/dev/vaec-celery-task-definition.json @@ -228,10 +228,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "dev-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/cd/application-deployment/perf/vaec-api-task-definition.json b/cd/application-deployment/perf/vaec-api-task-definition.json index 30241827ae..e6cb54ab46 100644 --- a/cd/application-deployment/perf/vaec-api-task-definition.json +++ b/cd/application-deployment/perf/vaec-api-task-definition.json @@ -198,10 +198,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "perf-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/cd/application-deployment/perf/vaec-celery-task-definition.json b/cd/application-deployment/perf/vaec-celery-task-definition.json index 300d760d01..fd6dfbb77b 100644 --- a/cd/application-deployment/perf/vaec-celery-task-definition.json +++ b/cd/application-deployment/perf/vaec-celery-task-definition.json @@ -188,10 +188,6 @@ { "name": "COMP_AND_PEN_PERF_TO_NUMBER", "value": "+14254147755" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/cd/application-deployment/prod/vaec-api-task-definition.json b/cd/application-deployment/prod/vaec-api-task-definition.json index fe40b76e0b..c2f5a5a87b 100644 --- a/cd/application-deployment/prod/vaec-api-task-definition.json +++ b/cd/application-deployment/prod/vaec-api-task-definition.json @@ -202,10 +202,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "prod-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "False" } ], "secrets": [ diff --git a/cd/application-deployment/prod/vaec-celery-task-definition.json b/cd/application-deployment/prod/vaec-celery-task-definition.json index 9ab026c5c9..62c55e1c71 100644 --- a/cd/application-deployment/prod/vaec-celery-task-definition.json +++ b/cd/application-deployment/prod/vaec-celery-task-definition.json @@ -192,10 +192,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "prod-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "False" } ], "secrets": [ diff --git a/cd/application-deployment/staging/vaec-api-task-definition.json b/cd/application-deployment/staging/vaec-api-task-definition.json index 22f9fc7e71..eae765691c 100644 --- a/cd/application-deployment/staging/vaec-api-task-definition.json +++ b/cd/application-deployment/staging/vaec-api-task-definition.json @@ -218,10 +218,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "staging-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/cd/application-deployment/staging/vaec-celery-task-definition.json b/cd/application-deployment/staging/vaec-celery-task-definition.json index 02aae8eb99..b19f3bce7f 100644 --- a/cd/application-deployment/staging/vaec-celery-task-definition.json +++ b/cd/application-deployment/staging/vaec-celery-task-definition.json @@ -208,10 +208,6 @@ { "name": "COMP_AND_PEN_DYNAMODB_NAME", "value": "staging-bip-payment-notification-table" - }, - { - "name": "VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP", - "value": "True" } ], "secrets": [ diff --git a/tests/app/celery/test_contact_information_tasks_for_profile_v3.py b/tests/app/celery/test_contact_information_tasks_for_profile_v3.py deleted file mode 100644 index 16e6be6e90..0000000000 --- a/tests/app/celery/test_contact_information_tasks_for_profile_v3.py +++ /dev/null @@ -1,381 +0,0 @@ -import pytest -import uuid -from app.celery.common import RETRIES_EXCEEDED -from app.celery.contact_information_tasks import lookup_contact_info -from app.celery.exceptions import AutoRetryException -from app.exceptions import NotificationTechnicalFailureException, NotificationPermanentFailureException -from app.feature_flags import FeatureFlag -from app.models import ( - EMAIL_TYPE, - NOTIFICATION_TECHNICAL_FAILURE, - NOTIFICATION_PERMANENT_FAILURE, - RecipientIdentifier, - SMS_TYPE, -) -from app.va.identifier import IdentifierType -from app.va.va_profile import ( - NoContactInfoException, - VAProfileClient, - VAProfileNonRetryableException, - VAProfileRetryableException, - VAProfileResult, -) -from requests import Timeout - -from tests.app.factories.feature_flag import mock_feature_flag - -EXAMPLE_VA_PROFILE_ID = '135' -notification_id = str(uuid.uuid4()) - - -def test_should_get_email_address_and_update_notification(client, mocker, sample_template, sample_notification): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - - mocked_get_notification_by_id = mocker.patch( - 'app.celery.contact_information_tasks.get_notification_by_id', return_value=notification - ) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_email_with_permission = mocker.Mock( - return_value=VAProfileResult('test@test.org', True, None) - ) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - mocked_update_notification = mocker.patch('app.celery.contact_information_tasks.dao_update_notification') - - lookup_contact_info(notification.id) - - mocked_get_notification_by_id.assert_called() - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - mocked_update_notification.assert_called_with(notification) - assert notification.to == 'test@test.org' - - -def test_should_get_phone_number_and_update_notification(client, mocker, sample_notification): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - notification = sample_notification( - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}] - ) - assert notification.notification_type == SMS_TYPE - mocked_get_notification_by_id = mocker.patch( - 'app.celery.contact_information_tasks.get_notification_by_id', return_value=notification - ) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_telephone_with_permission = mocker.Mock( - return_value=VAProfileResult('+15555555555', True, None) - ) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - mocked_update_notification = mocker.patch('app.celery.contact_information_tasks.dao_update_notification') - - lookup_contact_info(notification.id) - - mocked_get_notification_by_id.assert_called() - mocked_va_profile_client.get_telephone_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_telephone_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - mocked_update_notification.assert_called_with(notification) - assert notification.to == '+15555555555' - - -def test_should_get_phone_number_and_update_notification_with_no_communication_item( - client, mocker, sample_notification -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - notification = sample_notification( - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}] - ) - notification.template.communication_item_id = None - assert notification.notification_type == SMS_TYPE - mocked_get_notification_by_id = mocker.patch( - 'app.celery.contact_information_tasks.get_notification_by_id', return_value=notification - ) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_telephone_with_permission = mocker.Mock( - return_value=VAProfileResult('+15555555555', True, None) - ) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - mocked_update_notification = mocker.patch('app.celery.contact_information_tasks.dao_update_notification') - - lookup_contact_info(notification.id) - - mocked_get_notification_by_id.assert_called() - mocked_va_profile_client.get_telephone_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_telephone_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - mocked_update_notification.assert_called_with(notification) - assert notification.to == '+15555555555' - - -def test_should_not_retry_on_non_retryable_exception(client, mocker, sample_template, sample_notification): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_check_and_queue_callback_task = mocker.patch( - 'app.celery.contact_information_tasks.check_and_queue_callback_task', - ) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - - exception = VAProfileNonRetryableException - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=exception) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - mocked_update_notification_status_by_id = mocker.patch( - 'app.celery.contact_information_tasks.update_notification_status_by_id' - ) - - with pytest.raises(NotificationPermanentFailureException): - lookup_contact_info(notification.id) - - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - - mocked_update_notification_status_by_id.assert_called_with( - notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=exception.failure_reason - ) - mocked_check_and_queue_callback_task.assert_called_once_with(notification) - - -@pytest.mark.parametrize('exception_type', (Timeout, VAProfileRetryableException)) -def test_should_retry_on_retryable_exception(client, mocker, sample_template, sample_notification, exception_type): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=exception_type('some error')) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - with pytest.raises(AutoRetryException): - lookup_contact_info(notification.id) - - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - - -@pytest.mark.parametrize('notification_type', (SMS_TYPE, EMAIL_TYPE)) -def test_lookup_contact_info_should_retry_on_timeout( - client, mocker, sample_template, sample_notification, notification_type -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=notification_type) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - - if notification_type == SMS_TYPE: - mocked_va_profile_client.get_telephone_with_permission = mocker.Mock(side_effect=Timeout('Request timed out')) - else: - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=Timeout('Request timed out')) - - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - with pytest.raises(AutoRetryException) as exc_info: - lookup_contact_info(notification.id) - - assert exc_info.value.args[0] == 'Found Timeout, autoretrying...' - assert isinstance(exc_info.value.args[1], Timeout) - assert str(exc_info.value.args[1]) == 'Request timed out' - - if notification_type == SMS_TYPE: - mocked_va_profile_client.get_telephone_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_telephone_with_permission.call_args[0][0] - else: - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - - -def test_should_update_notification_to_technical_failure_on_max_retries( - client, mocker, sample_template, sample_notification -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=VAProfileRetryableException) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - mocker.patch('app.celery.contact_information_tasks.can_retry', return_value=False) - mocked_handle_max_retries_exceeded = mocker.patch( - 'app.celery.contact_information_tasks.handle_max_retries_exceeded' - ) - - with pytest.raises(NotificationTechnicalFailureException): - lookup_contact_info(notification.id) - - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - - mocked_handle_max_retries_exceeded.assert_called_once() - - -def test_should_update_notification_to_permanent_failure_on_no_contact_info_exception( - client, mocker, sample_template, sample_notification -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - exception = NoContactInfoException - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=exception) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - - mocked_check_and_queue_callback_task = mocker.patch( - 'app.celery.contact_information_tasks.check_and_queue_callback_task', - ) - - mocked_update_notification_status_by_id = mocker.patch( - 'app.celery.contact_information_tasks.update_notification_status_by_id' - ) - - with pytest.raises(NotificationPermanentFailureException): - lookup_contact_info(notification.id) - - mocked_va_profile_client.get_email_with_permission.assert_called_with(mocker.ANY, notification.default_send) - recipient_identifier = mocked_va_profile_client.get_email_with_permission.call_args[0][0] - assert isinstance(recipient_identifier, RecipientIdentifier) - assert recipient_identifier.id_value == EXAMPLE_VA_PROFILE_ID - - mocked_update_notification_status_by_id.assert_called_with( - notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=exception.failure_reason - ) - - mocked_check_and_queue_callback_task.assert_called_once_with(notification) - - -@pytest.mark.parametrize( - 'exception, throws_additional_exception, notification_status, exception_reason', - [ - ( - VAProfileRetryableException, - NotificationTechnicalFailureException, - NOTIFICATION_TECHNICAL_FAILURE, - RETRIES_EXCEEDED, - ), - ( - NoContactInfoException, - NotificationPermanentFailureException, - NOTIFICATION_PERMANENT_FAILURE, - NoContactInfoException.failure_reason, - ), - ( - VAProfileNonRetryableException, - NotificationPermanentFailureException, - NOTIFICATION_PERMANENT_FAILURE, - VAProfileNonRetryableException.failure_reason, - ), - ], -) -def test_exception_sets_failure_reason_if_thrown( - client, - mocker, - sample_template, - sample_notification, - exception, - throws_additional_exception, - notification_status, - exception_reason, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - notification = sample_notification( - template=template, - recipient_identifiers=[{'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': EXAMPLE_VA_PROFILE_ID}], - ) - mocker.patch('app.celery.contact_information_tasks.get_notification_by_id', return_value=notification) - - mocked_va_profile_client = mocker.Mock(VAProfileClient) - mocked_va_profile_client.get_email_with_permission = mocker.Mock(side_effect=exception) - mocker.patch('app.celery.contact_information_tasks.va_profile_client', new=mocked_va_profile_client) - mocker.patch('app.celery.contact_information_tasks.can_retry', return_value=False) - - mocked_check_and_queue_callback_task = mocker.patch( - 'app.celery.contact_information_tasks.check_and_queue_callback_task', - ) - - if exception_reason == RETRIES_EXCEEDED: - mocker_handle_max_retries_exceeded = mocker.patch( - 'app.celery.contact_information_tasks.handle_max_retries_exceeded' - ) - with pytest.raises(throws_additional_exception): - lookup_contact_info(notification.id) - mocker_handle_max_retries_exceeded.assert_called_once() - else: - mocked_update_notification_status_by_id = mocker.patch( - 'app.celery.contact_information_tasks.update_notification_status_by_id' - ) - if throws_additional_exception: - with pytest.raises(throws_additional_exception): - lookup_contact_info(notification.id) - else: - lookup_contact_info(notification.id) - mocked_update_notification_status_by_id.assert_called_once_with( - notification.id, notification_status, status_reason=exception_reason - ) - - mocked_check_and_queue_callback_task.assert_called_once_with(notification) diff --git a/tests/app/celery/test_lookup_recipient_communication_permissions_task.py b/tests/app/celery/test_lookup_recipient_communication_permissions_task.py index 2f0a36b584..dc2cf16af3 100644 --- a/tests/app/celery/test_lookup_recipient_communication_permissions_task.py +++ b/tests/app/celery/test_lookup_recipient_communication_permissions_task.py @@ -19,8 +19,6 @@ from app.va.va_profile.exceptions import CommunicationItemNotFoundException from app.va.va_profile.va_profile_client import VAProfileClient from app.va.identifier import IdentifierType -from app.feature_flags import FeatureFlag -from tests.app.factories.feature_flag import mock_feature_flag @pytest.fixture @@ -185,7 +183,6 @@ def test_recipient_has_given_permission_should_return_none_if_user_permissions_n def test_recipient_has_given_permission_with_default_send_indicator_and_no_preference_set( client, mocker, send_indicator: bool ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') mocked_va_profile_client = mocker.Mock(VAProfileClient) mocked_va_profile_client.get_is_communication_allowed = mocker.Mock(side_effect=CommunicationItemNotFoundException) mocker.patch( @@ -220,7 +217,6 @@ def test_recipient_has_given_permission_max_retries_exceeded(client, mocker, fak id=uuid.uuid4(), va_profile_item_id=1, name='name', default_send_indicator=send_indicator ) - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') mocker.patch( 'app.celery.lookup_recipient_communication_permissions_task.get_communication_item', return_value=test_communication_item, diff --git a/tests/app/notifications/test_process_notifications.py b/tests/app/notifications/test_process_notifications.py index bedb014179..44a2e6d20b 100644 --- a/tests/app/notifications/test_process_notifications.py +++ b/tests/app/notifications/test_process_notifications.py @@ -512,7 +512,7 @@ def test_send_notification_to_queue_with_recipient_identifiers( sample_communication_item, sample_template, ): - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'True') + mocker.patch('app.notifications.process_notifications.is_feature_enabled', return_value=True) mocked_chain = mocker.patch('app.notifications.process_notifications.chain') template = sample_template( template_type=notification_type, @@ -579,7 +579,7 @@ def test_send_notification_to_queue_throws_exception_deletes_notification( mocker, ): notification = sample_notification(api_key=sample_api_key()) - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'False') + mocker.patch('app.notifications.process_notifications.is_feature_enabled', return_value=False) mocked_chain = mocker.patch('app.notifications.process_notifications.chain', side_effect=Boto3Error('EXPECTED')) mocker.patch('app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number') with pytest.raises(Boto3Error): @@ -983,7 +983,7 @@ def test_send_notification_to_correct_queue_to_lookup_contact_info( expected_tasks, sample_template, ): - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'True') + mocker.patch('app.notifications.process_notifications.is_feature_enabled', return_value=True) mocked_chain = mocker.patch('app.notifications.process_notifications.chain') template = sample_template(template_type=notification_type) diff --git a/tests/app/notifications/test_process_notifications_for_profile_v3.py b/tests/app/notifications/test_process_notifications_for_profile_v3.py deleted file mode 100644 index cca7946214..0000000000 --- a/tests/app/notifications/test_process_notifications_for_profile_v3.py +++ /dev/null @@ -1,1117 +0,0 @@ -import datetime -import uuid - -import pytest -from boto3.exceptions import Boto3Error -from sqlalchemy.exc import SQLAlchemyError -from freezegun import freeze_time -from collections import namedtuple -from sqlalchemy import delete, select - -from app.celery.contact_information_tasks import lookup_contact_info -from app.celery.lookup_va_profile_id_task import lookup_va_profile_id -from app.celery.onsite_notification_tasks import send_va_onsite_notification_task -from app.celery.lookup_recipient_communication_permissions_task import lookup_recipient_communication_permissions -from app.celery.provider_tasks import deliver_email, deliver_sms -from app.feature_flags import FeatureFlag -from app.models import ( - Notification, - ScheduledNotification, - Template, - LETTER_TYPE, - NOTIFICATION_CREATED, - EMAIL_TYPE, - SMS_TYPE, - RecipientIdentifier, - KEY_TYPE_TEST, -) -from app.notifications.process_notifications import ( - create_content_for_notification, - persist_notification, - persist_scheduled_notification, - send_notification_to_queue, - simulated_recipient, - send_to_queue_for_recipient_info_based_on_recipient_identifier, -) -from notifications_utils.recipients import validate_and_format_phone_number, validate_and_format_email_address -from app.v2.errors import BadRequestError -from app.va.identifier import IdentifierType - - -from tests.app.factories.feature_flag import mock_feature_flag - - -def test_create_content_for_notification_passes(notify_db_session, sample_template, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(template_type=EMAIL_TYPE) - db_template = notify_db_session.session.get(Template, template.id) - - content = create_content_for_notification(db_template, None) - assert str(content) == template.content - - -def test_create_content_for_notification_with_placeholders_passes(notify_db_session, sample_template, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(content='Hello ((name))') - db_template = notify_db_session.session.get(Template, template.id) - - content = create_content_for_notification(db_template, {'name': 'Bobby'}) - assert content.content == template.content - assert 'Bobby' in str(content) - - -def test_create_content_for_notification_fails_with_missing_personalisation(notify_db_session, sample_template, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(content='Hello ((name))\n((Additional placeholder))') - db_template = notify_db_session.session.get(Template, template.id) - - with pytest.raises(BadRequestError): - create_content_for_notification(db_template, None) - - -def test_create_content_for_notification_allows_additional_personalisation(notify_db_session, sample_template, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template(content='Hello ((name))\n((Additional placeholder))') - db_template = notify_db_session.session.get(Template, template.id) - - create_content_for_notification(db_template, {'name': 'Bobby', 'Additional placeholder': 'Data'}) - - -@pytest.mark.serial -@freeze_time('2016-01-01 11:09:00.061258') -def test_persist_notification_creates_and_save_to_db( - notify_db_session, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mocked_redis = mocker.patch('app.notifications.process_notifications.redis_store.get') - - template = sample_template() - api_key = sample_api_key(template.service) - - data = { - 'template_id': template.id, - 'notification_id': uuid.uuid4(), - 'created_at': datetime.datetime.utcnow(), - 'reference': str(uuid.uuid4()), - 'billing_code': str(uuid.uuid4()), - 'recipient': '+16502532222', - 'notification_type': SMS_TYPE, - 'api_key_id': api_key.id, - 'key_type': api_key.key_type, - 'reply_to_text': template.service.get_default_sms_sender(), - 'service_id': template.service.id, - 'template_version': template.version, - 'personalisation': {}, - } - - # Intermittently makes the status 'technical-failure' - # Cleaned by the template cleanup - persist_notification(**data) - - db_notification = notify_db_session.session.get(Notification, data['notification_id']) - - assert db_notification.id == data['notification_id'] - assert db_notification.template_id == data['template_id'] - assert db_notification.template_version == data['template_version'] - assert db_notification.api_key_id == data['api_key_id'] - assert db_notification.key_type == data['key_type'] - assert db_notification.notification_type == data['notification_type'] - assert db_notification.created_at == data['created_at'] - assert db_notification.reference == data['reference'] - assert db_notification.reply_to_text == data['reply_to_text'] - assert db_notification.billing_code == data['billing_code'] - assert db_notification.status == NOTIFICATION_CREATED - assert db_notification.billable_units == 0 - assert db_notification.updated_at is None - assert db_notification.created_by_id is None - assert db_notification.client_reference is None - assert not db_notification.sent_at - - mocked_redis.assert_called_once_with(str(template.service_id) + '-2016-01-01-count') - - -def test_persist_notification_throws_exception_when_missing_template(sample_api_key, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - notification = None - - with pytest.raises(SQLAlchemyError): - notification = persist_notification( - template_id=None, - template_version=None, - recipient='+16502532222', - service_id=api_key.service.id, - personalisation=None, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - ) - - assert notification is None - - -def test_cache_is_not_incremented_on_failure_to_persist_notification( - sample_api_key, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - mocked_redis = mocker.patch('app.redis_store.get') - mock_service_template_cache = mocker.patch('app.redis_store.get_all_from_hash') - with pytest.raises(SQLAlchemyError): - persist_notification( - template_id=None, - template_version=None, - recipient='+16502532222', - service_id=api_key.service.id, - personalisation=None, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - ) - mocked_redis.assert_not_called() - mock_service_template_cache.assert_not_called() - - -def test_persist_notification_does_not_increment_cache_if_test_key( - notify_db_session, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template() - api_key = sample_api_key(service=template.service, key_type=KEY_TYPE_TEST) - - mocker.patch('app.notifications.process_notifications.redis_store.get', return_value='cache') - mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash', return_value='cache') - daily_limit_cache = mocker.patch('app.notifications.process_notifications.redis_store.incr') - template_usage_cache = mocker.patch('app.notifications.process_notifications.redis_store.increment_hash_value') - - notification_id = uuid.uuid4() - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='+16502532222', - service_id=template.service.id, - personalisation={}, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - reference=str(uuid.uuid4()), - notification_id=notification_id, - ) - - assert notify_db_session.session.get(Notification, notification_id) - assert not daily_limit_cache.called - assert not template_usage_cache.called - - -@freeze_time('2016-01-01 11:09:00.061258') -def test_persist_notification_with_optionals( - notify_db_session, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - template = sample_template(service=api_key.service) - service = api_key.service - mocked_redis = mocker.patch('app.notifications.process_notifications.redis_store.get') - notification_id = uuid.uuid4() - created_at = datetime.datetime(2016, 11, 11, 16, 8, 18) - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='+16502532222', - service_id=service.id, - personalisation=None, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - created_at=created_at, - client_reference='ref from client', - notification_id=notification_id, - created_by_id=api_key.created_by_id, - ) - - persisted_notification = notify_db_session.session.get(Notification, notification_id) - - assert persisted_notification.id == notification_id - assert persisted_notification.created_at == created_at - mocked_redis.assert_called_once_with(str(service.id) + '-2016-01-01-count') - assert persisted_notification.client_reference == 'ref from client' - assert persisted_notification.reference is None - assert persisted_notification.international is False - assert persisted_notification.phone_prefix == '1' - assert persisted_notification.rate_multiplier == 1 - assert persisted_notification.created_by_id == api_key.created_by_id - assert not persisted_notification.reply_to_text - - -@freeze_time('2016-01-01 11:09:00.061258') -def test_persist_notification_doesnt_touch_cache_for_old_keys_that_dont_exist( - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - template = sample_template(service=api_key.service) - mock_incr = mocker.patch('app.notifications.process_notifications.redis_store.incr') - mocker.patch('app.notifications.process_notifications.redis_store.get', return_value=None) - mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash', return_value=None) - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='+16502532222', - service_id=api_key.service.id, - personalisation={}, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - reference='ref', - ) - - mock_incr.assert_not_called() - - -@freeze_time('2016-01-01 11:09:00.061258') -def test_persist_notification_increments_cache_if_key_exists( - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - template = sample_template(service=api_key.service) - service = template.service - mock_incr = mocker.patch('app.notifications.process_notifications.redis_store.incr') - mocker.patch('app.notifications.process_notifications.redis_store.get', return_value=1) - mocker.patch('app.notifications.process_notifications.redis_store.get_all_from_hash', return_value={template.id, 1}) - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='+16502532222', - service_id=service.id, - personalisation={}, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - reference='ref2', - ) - - mock_incr.assert_called_once_with(str(service.id) + '-2016-01-01-count') - - -@pytest.mark.parametrize( - 'research_mode, requested_queue, notification_type, key_type, expected_queue, expected_tasks', - [ - (True, None, SMS_TYPE, 'normal', 'research-mode-tasks', [deliver_sms]), - (True, None, EMAIL_TYPE, 'normal', 'research-mode-tasks', [deliver_email]), - (True, None, EMAIL_TYPE, 'team', 'research-mode-tasks', [deliver_email]), - (False, None, SMS_TYPE, 'normal', 'send-sms-tasks', [deliver_sms]), - (False, None, EMAIL_TYPE, 'normal', 'send-email-tasks', [deliver_email]), - (False, None, SMS_TYPE, 'team', 'send-sms-tasks', [deliver_sms]), - (False, None, SMS_TYPE, 'test', 'research-mode-tasks', [deliver_sms]), - (True, 'notify-internal-tasks', EMAIL_TYPE, 'normal', 'research-mode-tasks', [deliver_email]), - (False, 'notify-internal-tasks', SMS_TYPE, 'normal', 'notify-internal-tasks', [deliver_sms]), - (False, 'notify-internal-tasks', EMAIL_TYPE, 'normal', 'notify-internal-tasks', [deliver_email]), - (False, 'notify-internal-tasks', SMS_TYPE, 'test', 'research-mode-tasks', [deliver_sms]), - ], -) -def test_send_notification_to_queue_with_no_recipient_identifiers( - research_mode, - requested_queue, - notification_type, - key_type, - expected_queue, - expected_tasks, - mocker, - sample_template, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mocked_chain = mocker.patch('app.notifications.process_notifications.chain') - template = sample_template(template_type=notification_type) - MockService = namedtuple('Service', ['id']) - service = MockService(id=uuid.uuid4()) - - MockSmsSender = namedtuple('ServiceSmsSender', ['service_id', 'sms_sender', 'rate_limit']) - sms_sender = MockSmsSender(service_id=service.id, sms_sender='+18888888888', rate_limit=1) - - NotificationTuple = namedtuple( - 'Notification', ['id', 'key_type', 'notification_type', 'created_at', 'template', 'service_id', 'reply_to_text'] - ) - - mocker.patch( - 'app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number', return_value=None - ) - - MockSmsSender = namedtuple('ServiceSmsSender', ['service_id', 'sms_sender', 'rate_limit']) - sms_sender = MockSmsSender(service_id=service.id, sms_sender='+18888888888', rate_limit=None) - - notification = NotificationTuple( - id=uuid.uuid4(), - key_type=key_type, - notification_type=notification_type, - created_at=datetime.datetime(2016, 11, 11, 16, 8, 18), - template=template, - service_id=service.id, - reply_to_text=sms_sender.sms_sender, - ) - - send_notification_to_queue(notification=notification, research_mode=research_mode, queue=requested_queue) - - args, _ = mocked_chain.call_args - for called_task, expected_task in zip(args, expected_tasks): - assert called_task.name == expected_task.name - called_task_notification_arg = args[0].args[0] - assert called_task_notification_arg == str(notification.id) - - -@pytest.mark.parametrize( - 'research_mode, requested_queue, notification_type, key_type, expected_queue, ' - 'request_recipient_id_type, request_recipient_id_value, expected_tasks', - [ - ( - True, - None, - SMS_TYPE, - 'normal', - 'research-mode-tasks', - IdentifierType.VA_PROFILE_ID.value, - 'some va profile id', - [lookup_recipient_communication_permissions, deliver_sms], - ), - ( - True, - None, - EMAIL_TYPE, - 'normal', - 'research-mode-tasks', - IdentifierType.PID.value, - 'some pid', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_email], - ), - ( - True, - None, - EMAIL_TYPE, - 'team', - 'research-mode-tasks', - IdentifierType.ICN.value, - 'some icn', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_email], - ), - ( - True, - 'notify-internal-tasks', - EMAIL_TYPE, - 'normal', - 'research-mode-tasks', - IdentifierType.VA_PROFILE_ID.value, - 'some va profile id', - [lookup_recipient_communication_permissions, deliver_email], - ), - ( - False, - None, - SMS_TYPE, - 'normal', - 'send-sms-tasks', - IdentifierType.PID.value, - 'some pid', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_sms], - ), - ( - False, - None, - EMAIL_TYPE, - 'normal', - 'send-email-tasks', - IdentifierType.ICN.value, - 'some icn', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_email], - ), - ( - False, - None, - SMS_TYPE, - 'team', - 'send-sms-tasks', - IdentifierType.VA_PROFILE_ID.value, - 'some va profile id', - [lookup_recipient_communication_permissions, deliver_sms], - ), - ( - False, - None, - SMS_TYPE, - 'test', - 'research-mode-tasks', - IdentifierType.PID.value, - 'some pid', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_sms], - ), - ( - False, - 'notify-internal-tasks', - SMS_TYPE, - 'normal', - 'notify-internal-tasks', - IdentifierType.ICN.value, - 'some icn', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_sms], - ), - ( - False, - 'notify-internal-tasks', - EMAIL_TYPE, - 'normal', - 'notify-internal-tasks', - IdentifierType.VA_PROFILE_ID.value, - 'some va profile id', - [lookup_recipient_communication_permissions, deliver_email], - ), - ( - False, - 'notify-internal-tasks', - SMS_TYPE, - 'test', - 'research-mode-tasks', - IdentifierType.PID.value, - 'some pid', - [lookup_va_profile_id, lookup_recipient_communication_permissions, deliver_sms], - ), - ], -) -def test_send_notification_to_queue_with_recipient_identifiers( - research_mode, - requested_queue, - notification_type, - key_type, - expected_queue, - request_recipient_id_type, - request_recipient_id_value, - expected_tasks, - mocker, - sample_communication_item, - sample_template, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'True') - mocked_chain = mocker.patch('app.notifications.process_notifications.chain') - template = sample_template( - template_type=notification_type, - content='Hello (( Name))\nHere is some HTML & entities' if notification_type == SMS_TYPE else None, - ) - template.service.prefix_sms = notification_type == SMS_TYPE # True only for SMS_TYPE - - MockService = namedtuple('Service', ['id']) - service = MockService(id=uuid.uuid4()) - MockSmsSender = namedtuple('ServiceSmsSender', ['service_id', 'sms_sender', 'rate_limit']) - sms_sender = MockSmsSender(service_id=service.id, sms_sender='+18888888888', rate_limit=None) - - mocker.patch( - 'app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number', - return_value=sms_sender, - ) - - TestNotification = namedtuple( - 'Notification', - [ - 'id', - 'key_type', - 'notification_type', - 'created_at', - 'template', - 'recipient_identifiers', - 'service_id', - 'reply_to_text', - 'sms_sender', - ], - ) - notification_id = uuid.uuid4() - notification = TestNotification( - id=notification_id, - key_type=key_type, - notification_type=notification_type, - created_at=datetime.datetime(2016, 11, 11, 16, 8, 18), - template=template, - recipient_identifiers={ - f'{request_recipient_id_type}': RecipientIdentifier( - notification_id=notification_id, id_type=request_recipient_id_type, id_value=request_recipient_id_value - ) - }, - service_id=service.id, - reply_to_text=sms_sender.sms_sender, - sms_sender=sms_sender, - ) - - send_notification_to_queue( - notification=notification, - research_mode=research_mode, - queue=requested_queue, - recipient_id_type=request_recipient_id_type, - ) - - args, _ = mocked_chain.call_args - for called_task, expected_task in zip(args, expected_tasks): - assert called_task.name == expected_task.name - - -def test_send_notification_to_queue_throws_exception_deletes_notification( - sample_api_key, - sample_notification, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - notification = sample_notification(api_key=sample_api_key()) - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'False') - mocked_chain = mocker.patch('app.notifications.process_notifications.chain', side_effect=Boto3Error('EXPECTED')) - mocker.patch('app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number') - with pytest.raises(Boto3Error): - send_notification_to_queue(notification, False) - - args, _ = mocked_chain.call_args - for called_task, expected_task in zip(args, ['send-sms-tasks']): - assert called_task.args[0] == str(notification.id) - assert called_task.options['queue'] == expected_task - - -@pytest.mark.parametrize( - 'to_address, notification_type, expected', - [ - ('+16132532222', 'sms', True), - ('+16132532223', 'sms', True), - ('6132532222', 'sms', True), - ('simulate-delivered@notifications.va.gov', 'email', True), - ('simulate-delivered-2@notifications.va.gov', 'email', True), - ('simulate-delivered-3@notifications.va.gov', 'email', True), - ('6132532225', 'sms', False), - ('valid_email@test.com', 'email', False), - ], -) -def test_simulated_recipient(to_address, notification_type, expected, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - """ - The values where the expected = 'research-mode' are listed in the config['SIMULATED_EMAIL_ADDRESSES'] - and config['SIMULATED_SMS_NUMBERS']. These values should result in using the research mode queue. - SIMULATED_EMAIL_ADDRESSES = ( - 'simulate-delivered@notifications.va.gov', - 'simulate-delivered-2@notifications.va.gov', - 'simulate-delivered-2@notifications.va.gov' - ) - SIMULATED_SMS_NUMBERS = ('6132532222', '+16132532222', '+16132532223') - """ - formatted_address = None - - if notification_type == EMAIL_TYPE: - formatted_address = validate_and_format_email_address(to_address) - else: - formatted_address = validate_and_format_phone_number(to_address) - - is_simulated_address = simulated_recipient(formatted_address, notification_type) - - assert is_simulated_address == expected - - -@pytest.mark.parametrize( - 'recipient, expected_international, expected_prefix, expected_units', - [ - ('6502532222', False, '1', 1), # NA - ('+16502532222', False, '1', 1), # NA - ('+79587714230', True, '7', 1), # Russia - ('+360623400400', True, '36', 3), # Hungary - ], -) -def test_persist_notification_with_international_info_stores_correct_info( - notify_db_session, - sample_api_key, - sample_template, - mocker, - recipient, - expected_international, - expected_prefix, - expected_units, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template() - api_key = sample_api_key(service=template.service) - - notification_id = uuid.uuid4() - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient=recipient, - service_id=template.service.id, - personalisation=None, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - client_reference='ref from client', - notification_id=notification_id, - ) - - persisted_notification = notify_db_session.session.get(Notification, notification_id) - - assert persisted_notification.international is expected_international - assert persisted_notification.phone_prefix == expected_prefix - assert persisted_notification.rate_multiplier == expected_units - - -def test_persist_notification_with_international_info_does_not_store_for_email( - notify_db_session, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template() - api_key = sample_api_key(service=template.service) - - notification_id = uuid.uuid4() - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='foo@bar.com', - service_id=api_key.service.id, - personalisation=None, - notification_type=EMAIL_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - client_reference='ref from client', - notification_id=notification_id, - ) - - persisted_notification = notify_db_session.session.get(Notification, notification_id) - - assert persisted_notification.international is False - assert persisted_notification.phone_prefix is None - assert persisted_notification.rate_multiplier is None - - -# This test assumes the local timezone is EST -def test_persist_scheduled_notification(notify_db_session, sample_api_key, sample_notification, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - api_key = sample_api_key() - notification = sample_notification(api_key=api_key) - - # Cleaned by the template cleanup - persist_scheduled_notification(notification.id, '2017-05-12 14:15') - stmt = select(ScheduledNotification).where(ScheduledNotification.notification_id == notification.id) - scheduled_notification = notify_db_session.session.scalar(stmt) - - assert scheduled_notification.notification_id == notification.id - assert scheduled_notification.scheduled_for == datetime.datetime(2017, 5, 12, 18, 15) - - -@pytest.mark.parametrize( - 'recipient, expected_recipient_normalised', - [ - ('6502532222', '+16502532222'), - (' 6502532223', '+16502532223'), - ('6502532223', '+16502532223'), - ], -) -def test_persist_sms_notification_stores_normalised_number( - notify_db_session, - sample_api_key, - sample_template, - mocker, - recipient, - expected_recipient_normalised, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template() - api_key = sample_api_key(service=template.service) - - notification_id = uuid.uuid4() - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient=recipient, - service_id=api_key.service.id, - personalisation=None, - notification_type=SMS_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - notification_id=notification_id, - ) - - persisted_notification = notify_db_session.session.get(Notification, notification_id) - - assert persisted_notification.to == recipient - assert persisted_notification.normalised_to == expected_recipient_normalised - - -@pytest.mark.parametrize( - 'recipient, expected_recipient_normalised', [('FOO@bar.com', 'foo@bar.com'), ('BAR@foo.com', 'bar@foo.com')] -) -def test_persist_email_notification_stores_normalised_email( - notify_db_session, - sample_api_key, - sample_template, - mocker, - recipient, - expected_recipient_normalised, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - template = sample_template() - api_key = sample_api_key(service=template.service) - - notification_id = uuid.uuid4() - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient=recipient, - service_id=api_key.service.id, - personalisation=None, - notification_type=EMAIL_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - notification_id=notification_id, - ) - persisted_notification = notify_db_session.session.get(Notification, notification_id) - - assert persisted_notification.to == recipient - assert persisted_notification.normalised_to == expected_recipient_normalised - - -@pytest.mark.skip(reason='Mislabelled for route removal, fails when unskipped') -def test_persist_notification_with_billable_units_stores_correct_info( - notify_db_session, - mocker, - sample_service, - sample_template, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - service = sample_service(service_permissions=[LETTER_TYPE]) - template = sample_template(service=service, template_type=LETTER_TYPE) - mocker.patch('app.dao.templates_dao.dao_get_template_by_id', return_value=template) - - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - recipient='123 Main Street', - service_id=template.service.id, - personalisation=None, - notification_type=template.template_type, - api_key_id=None, - key_type='normal', - billable_units=3, - template_postage=template.postage, - ) - - stmt = select(Notification) - persisted_notification = notify_db_session.session.scalars(stmt).all()[0] - - assert persisted_notification.billable_units == 3 - - -@pytest.mark.parametrize( - 'notification_type', - [ - EMAIL_TYPE, - SMS_TYPE, - ], -) -@pytest.mark.parametrize( - 'id_type, id_value', - [ - (IdentifierType.VA_PROFILE_ID.value, 'some va profile id'), - (IdentifierType.PID.value, 'some pid'), - (IdentifierType.ICN.value, 'some icn'), - ], -) -def test_persist_notification_persists_recipient_identifiers( - notify_db_session, - notification_type, - id_type, - id_value, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mocker.patch('app.notifications.process_notifications.accept_recipient_identifiers_enabled', return_value=True) - template = sample_template(template_type=notification_type) - api_key = sample_api_key() - recipient_identifier = {'id_type': id_type, 'id_value': id_value} - - notification_id = uuid.uuid4() - # Cleaned by the template cleanup - persist_notification( - template_id=template.id, - template_version=template.version, - service_id=api_key.service.id, - personalisation=None, - notification_type=notification_type, - api_key_id=api_key.id, - key_type=api_key.key_type, - recipient_identifier=recipient_identifier, - notification_id=notification_id, - ) - - recipient_identifier = notify_db_session.session.get(RecipientIdentifier, (notification_id, id_type, id_value)) - - try: - # Persisted correctly - assert recipient_identifier.notification_id == notification_id - assert recipient_identifier.id_type == id_type - assert recipient_identifier.id_value == id_value - finally: - # Teardown - stmt = delete(RecipientIdentifier).where(RecipientIdentifier.notification_id == notification_id) - notify_db_session.session.execute(stmt) - notify_db_session.session.commit() - - -@pytest.mark.parametrize( - 'recipient_identifiers_enabled, recipient_identifier', - [(True, None), (False, {'id_type': IdentifierType.VA_PROFILE_ID.value, 'id_value': 'foo'}), (False, None)], -) -def test_persist_notification_should_not_persist_recipient_identifier_if_none_present_or_toggle_off( - notify_db_session, - recipient_identifiers_enabled, - recipient_identifier, - sample_api_key, - sample_template, - mocker, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mocker.patch( - 'app.notifications.process_notifications.accept_recipient_identifiers_enabled', - return_value=recipient_identifiers_enabled, - ) - - template = sample_template() - api_key = sample_api_key(template.service) - - # Cleaned by the template cleanup - notification = persist_notification( - template_id=template.id, - template_version=template.version, - service_id=api_key.service.id, - personalisation=None, - notification_type=EMAIL_TYPE, - api_key_id=api_key.id, - key_type=api_key.key_type, - recipient_identifier=recipient_identifier, - ) - - # Persisted correctly - assert notification.recipient_identifiers == {} - - # DB stored correctly - stmt = select(RecipientIdentifier).where(RecipientIdentifier.notification_id == notification.id) - assert notify_db_session.session.scalar(stmt) is None - - -@pytest.mark.parametrize( - 'id_type, notification_type, expected_tasks', - [ - ( - IdentifierType.VA_PROFILE_ID.value, - EMAIL_TYPE, - [ - send_va_onsite_notification_task, - lookup_contact_info, - deliver_email, - ], - ), - ( - IdentifierType.VA_PROFILE_ID.value, - SMS_TYPE, - [ - send_va_onsite_notification_task, - lookup_contact_info, - deliver_sms, - ], - ), - ( - IdentifierType.ICN.value, - EMAIL_TYPE, - [ - lookup_va_profile_id, - send_va_onsite_notification_task, - lookup_contact_info, - deliver_email, - ], - ), - ( - IdentifierType.ICN.value, - SMS_TYPE, - [ - lookup_va_profile_id, - send_va_onsite_notification_task, - lookup_contact_info, - deliver_sms, - ], - ), - ], -) -def test_send_notification_to_correct_queue_to_lookup_contact_info( - client, - mocker, - notification_type, - id_type, - expected_tasks, - sample_template, -): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'True') - mocked_chain = mocker.patch('app.notifications.process_notifications.chain') - - template = sample_template(template_type=notification_type) - notification_id = str(uuid.uuid4()) - notification = Notification(id=notification_id, notification_type=notification_type, template=template) - mock_template_id = uuid.uuid4() - - send_to_queue_for_recipient_info_based_on_recipient_identifier( - notification, id_type, 'some_id_value', mock_template_id - ) - - args, _ = mocked_chain.call_args - for called_task, expected_task in zip(args, expected_tasks): - assert called_task.name == expected_task.name - - -def test_send_notification_with_sms_sender_rate_limit_uses_rate_limit_delivery_task(client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_feature_flag(mocker, FeatureFlag.SMS_SENDER_RATE_LIMIT_ENABLED, 'True') - mocked_chain = mocker.patch('app.notifications.process_notifications.chain') - - MockService = namedtuple('Service', ['id']) - service = MockService(id='some service id') - - MockSmsSender = namedtuple('ServiceSmsSender', ['service_id', 'sms_sender', 'rate_limit']) - sms_sender = MockSmsSender(service_id=service.id, sms_sender='+18888888888', rate_limit=2) - - MockTemplate = namedtuple('MockTemplate', ['communication_item_id']) - template = MockTemplate(communication_item_id=1) - - mocker.patch( - 'app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number', - return_value=sms_sender, - ) - - notification = Notification( - id=str(uuid.uuid4()), - notification_type=SMS_TYPE, - reply_to_text=sms_sender.sms_sender, - service_id=service.id, - template=template, - ) - - send_notification_to_queue(notification, False) - - assert mocked_chain.call_args[0][0].task == 'deliver_sms_with_rate_limiting' - - -def test_send_notification_without_sms_sender_rate_limit_uses_regular_delivery_task(client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mocked_chain = mocker.patch('app.notifications.process_notifications.chain') - deliver_sms_with_rate_limiting = mocker.patch( - 'app.celery.provider_tasks.deliver_sms_with_rate_limiting.apply_async' - ) - - MockService = namedtuple('Service', ['id']) - service = MockService(id='some service id') - - MockTemplate = namedtuple('MockTemplate', ['communication_item_id']) - template = MockTemplate(communication_item_id=1) - - MockSmsSender = namedtuple('ServiceSmsSender', ['service_id', 'sms_sender', 'rate_limit']) - sms_sender = MockSmsSender(service_id=service.id, sms_sender='+18888888888', rate_limit=None) - - mocker.patch( - 'app.notifications.process_notifications.dao_get_service_sms_sender_by_service_id_and_number', - return_value=sms_sender, - ) - - notification = Notification( - id=str(uuid.uuid4()), - notification_type=SMS_TYPE, - reply_to_text=sms_sender.sms_sender, - service_id=service.id, - template=template, - ) - - send_notification_to_queue(notification, False) - - assert mocked_chain.call_args[0][0].task == 'deliver_sms' - deliver_sms_with_rate_limiting.assert_not_called() diff --git a/tests/app/va/va_profile/test_va_profile_client_for_profile_v3.py b/tests/app/va/va_profile/test_va_profile_client_for_profile_v3.py deleted file mode 100644 index 8ef5e32f19..0000000000 --- a/tests/app/va/va_profile/test_va_profile_client_for_profile_v3.py +++ /dev/null @@ -1,404 +0,0 @@ -import pytest -import json -import requests -import requests_mock -from urllib import parse - -from app.feature_flags import FeatureFlag -from app.models import EMAIL_TYPE, RecipientIdentifier -from app.va.identifier import IdentifierType, transform_to_fhir_format, OIDS -from app.va.va_profile import VAProfileClient -from app.va.va_profile.exceptions import ( - CommunicationItemNotFoundException, - NoContactInfoException, - VAProfileIDNotFoundException, - VAProfileNonRetryableException, - VAProfileRetryableException, -) -from app.va.va_profile.va_profile_client import CommunicationChannel - -from tests.app.factories.feature_flag import mock_feature_flag - -MOCK_VA_PROFILE_URL = 'http://mock.vaprofile.va.gov' - - -@pytest.fixture(scope='function') -def mock_va_profile_client(mocker, notify_api): - with notify_api.app_context(): - mock_logger = mocker.Mock() - mock_ssl_key_path = 'some_key.pem' - mock_ssl_cert_path = 'some_cert.pem' - mock_statsd_client = mocker.Mock() - mock_va_profile_token = mocker.Mock() - - client = VAProfileClient() - client.init_app( - logger=mock_logger, - va_profile_url=MOCK_VA_PROFILE_URL, - ssl_cert_path=mock_ssl_cert_path, - ssl_key_path=mock_ssl_key_path, - va_profile_token=mock_va_profile_token, - statsd_client=mock_statsd_client, - ) - - return client - - -@pytest.fixture(scope='function') -def mock_response(): - with open('tests/app/va/va_profile/mock_response.json', 'r') as f: - return json.load(f) - - -@pytest.fixture(scope='module') -def recipient_identifier(): - return RecipientIdentifier(notification_id='123456', id_type=IdentifierType.VA_PROFILE_ID, id_value='1234') - - -@pytest.fixture(scope='module') -def id_with_aaid(recipient_identifier): - return transform_to_fhir_format(recipient_identifier) - - -@pytest.fixture(scope='module') -def oid(recipient_identifier): - return OIDS.get(recipient_identifier.id_type) - - -@pytest.fixture(scope='module') -def url(oid, id_with_aaid): - return f'{MOCK_VA_PROFILE_URL}/profile-service/profile/v3/{oid}/{id_with_aaid}' - - -class TestVAProfileClient: - def test_ut_get_email_calls_endpoint_and_returns_email_address( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(url, json=mock_response, status_code=200) - - result = mock_va_profile_client.get_email_with_permission(recipient_identifier) - email = result.recipient - - assert email == mock_response['profile']['contactInformation']['emails'][0]['emailAddressText'] - assert rmock.called - - def test_ut_get_email_calls_endpoint_and_returns_email_address_with_permission_bypassed( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(url, json=mock_response, status_code=200) - - result = mock_va_profile_client.get_email_with_permission(recipient_identifier, True) - email = result.recipient - - assert email == mock_response['profile']['contactInformation']['emails'][0]['emailAddressText'] - assert rmock.called - - def test_ut_get_email_raises_NoContactInfoException_if_no_emails_exist( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_response['profile']['contactInformation']['emails'] = [] - rmock.post(url, json=mock_response, status_code=200) - - with pytest.raises(NoContactInfoException): - mock_va_profile_client.get_email_with_permission(recipient_identifier) - - def test_ut_get_profile_calls_correct_url( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, id_with_aaid, oid, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(url, json=mock_response, status_code=200) - - mock_va_profile_client.get_email_with_permission(recipient_identifier) - - assert rmock.called - - escaped_id = parse.quote(id_with_aaid, safe='') - expected_url = f'{MOCK_VA_PROFILE_URL}/profile-service/profile/v3/{oid}/{escaped_id}' - - assert rmock.request_history[0].url == expected_url - - def test_ut_get_email_raises_exception_when_failed_request( - self, rmock, mock_va_profile_client, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - response = { - 'messages': [ - { - 'code': 'CORE103', - 'key': '_CUF_NOT_FOUND', - 'text': 'The ContactInformationBio for id/criteria 103 could not be found. Please correct your requ...', - 'severity': 'INFO', - } - ], - 'txAuditId': 'dca32cae-b410-46c5-b61b-9a382567843f', - 'status': 'COMPLETED_FAILURE', - } - rmock.post(url, json=response, status_code=200) - - with pytest.raises(VAProfileNonRetryableException): - mock_va_profile_client.get_email_with_permission(recipient_identifier) - - def test_ut_get_telephone_calls_endpoint_and_returns_phone_number( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(url, json=mock_response, status_code=200) - - result = mock_va_profile_client.get_telephone_with_permission(recipient_identifier) - telephone = result.recipient - - assert telephone is not None - assert rmock.called - - def test_ut_get_telephone_calls_endpoint_and_returns_phone_number_with_permission_bypassed( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(url, json=mock_response, status_code=200) - - result = mock_va_profile_client.get_telephone_with_permission(recipient_identifier, True) - telephone = result.recipient - - assert telephone is not None - assert rmock.called - - -class TestVAProfileClientExceptionHandling: - def test_ut_get_telephone_raises_NoContactInfoException_if_no_telephones_exist( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_response['profile']['contactInformation']['telephones'] = [] - rmock.post(url, json=mock_response, status_code=200) - - with pytest.raises(NoContactInfoException): - mock_va_profile_client.get_telephone_with_permission(recipient_identifier) - - def test_ut_get_telephone_raises_NoContactInfoException_if_no_mobile_telephones_exist( - self, rmock, mock_va_profile_client, mock_response, recipient_identifier, url, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - telephones = mock_response['profile']['contactInformation']['telephones'] - mock_response['profile']['contactInformation']['telephones'] = [ - telephone for telephone in telephones if telephone['phoneType'] != 'MOBILE' - ] - rmock.post(url, json=mock_response, status_code=200) - - with pytest.raises(NoContactInfoException): - mock_va_profile_client.get_telephone_with_permission(recipient_identifier) - - def test_ut_handle_exceptions_retryable_exception(self, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - # This test checks if VAProfileRetryableException is raised for a RequestException - with pytest.raises(VAProfileRetryableException): - mock_va_profile_client._handle_exceptions('some_va_profile_id', requests.RequestException()) - - def test_ut_handle_exceptions_id_not_found_exception(self, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - # Simulate a 404 HTTP error - error = requests.HTTPError(response=requests.Response()) - error.response.status_code = 404 - # This test checks if VAProfileIDNotFoundException is raised for a 404 error - with pytest.raises(VAProfileIDNotFoundException): - mock_va_profile_client._handle_exceptions('some_va_profile_id', error) - - def test_ut_handle_exceptions_non_retryable_exception(self, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - # Simulate a 400 HTTP error - error = requests.HTTPError(response=requests.Response()) - error.response.status_code = 400 - # This test checks if VAProfileNonRetryableException is raised for a 400 error - with pytest.raises(VAProfileNonRetryableException): - mock_va_profile_client._handle_exceptions('some_va_profile_id', error) - - def test_ut_handle_exceptions_timeout_exception(self, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - # This test checks if VAProfileRetryableExcception is raised for a Timeout exception - # Timeout inherits from requests.RequestException, so all exceptions of type RequestException should - # raise a VAProfileRetryableException - with pytest.raises(VAProfileRetryableException): - mock_va_profile_client._handle_exceptions('some_va_profile_id', requests.Timeout()) - - @pytest.mark.parametrize('status', [429, 500]) - @pytest.mark.parametrize( - 'fn, args', - [ - ('get_email_with_permission', ['recipient_identifier']), - ('get_telephone_with_permission', ['recipient_identifier']), - ], - ) - def test_ut_client_raises_retryable_exception( - self, rmock, mock_va_profile_client, recipient_identifier, status, fn, args, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(requests_mock.ANY, status_code=status) - - with pytest.raises(VAProfileRetryableException): - func = getattr(mock_va_profile_client, fn) - # allow us to call `get_is_communication_allowed` though it has a different method signature - prepared_args = [recipient_identifier if arg == 'recipient_identifier' else arg for arg in args] - func(*prepared_args) - - @pytest.mark.parametrize('status', [400, 403, 404]) - @pytest.mark.parametrize( - 'fn, args', - [ - ('get_email_with_permission', ['recipient_identifier']), - ('get_telephone_with_permission', ['recipient_identifier']), - ], - ) - def test_ut_client_raises_nonretryable_exception( - self, rmock, mock_va_profile_client, recipient_identifier, status, fn, args, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(requests_mock.ANY, status_code=status) - - with pytest.raises(VAProfileNonRetryableException): - func = getattr(mock_va_profile_client, fn) - # allow us to call `get_is_communication_allowed` though it has a different method signature - prepared_args = [recipient_identifier if arg == 'recipient_identifier' else arg for arg in args] - func(*prepared_args) - - @pytest.mark.parametrize( - 'fn, args', - [ - ('get_email_with_permission', ['recipient_identifier']), - ('get_telephone_with_permission', ['recipient_identifier']), - ], - ) - def test_ut_client_raises_retryable_exception_when_request_exception_is_thrown( - self, mock_va_profile_client, recipient_identifier, fn, args, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - with requests_mock.Mocker(real_http=True) as rmock: - rmock.post(requests_mock.ANY, exc=requests.RequestException) - - with pytest.raises(VAProfileRetryableException): - func = getattr(mock_va_profile_client, fn) - # allow us to call `get_is_communication_allowed` though it has a different method signature - prepared_args = [recipient_identifier if arg == 'recipient_identifier' else arg for arg in args] - func(*prepared_args) - - -class TestCommunicationPermissions: - @pytest.mark.parametrize('expected', [True, False]) - def test_ut_get_is_communication_allowed_returns_whether_permissions_granted_for_sms_communication( - self, rmock, mock_va_profile_client, mock_response, url, expected, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_response['profile']['communicationPermissions'][0]['allowed'] = expected - - allowed = mock_va_profile_client.get_is_communication_allowed_from_profile( - mock_response['profile'], CommunicationChannel.TEXT - ) - - assert allowed is expected - - @pytest.mark.parametrize('expected', [True, False]) - def test_ut_get_is_communication_allowed_returns_whether_permissions_granted_for_email_communication( - self, rmock, mock_va_profile_client, mock_response, url, expected, mocker - ): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - mock_response['profile']['communicationPermissions'][1]['allowed'] = expected - - allowed = mock_va_profile_client.get_is_communication_allowed_from_profile( - mock_response['profile'], CommunicationChannel.EMAIL - ) - - assert allowed is expected - - -class TestSendEmailStatus: - mock_response = {} - mock_notification_data = { - 'id': '2e9e6920-4f6f-4cd5-9e16-fc306fe23867', # this is the notification id - 'reference': None, - 'to': 'test@email.com', # this is the recipient's contact info (email) - 'status': 'delivered', # this will specify the delivery status of the notification - 'status_reason': '', # populated if there's additional context on the delivery status - 'created_at': '2024-07-25T10:00:00.0', - 'completed_at': '2024-07-25T11:00:00.0', - 'sent_at': '2024-07-25T11:00:00.0', - 'notification_type': EMAIL_TYPE, # this is the channel/type of notification (email) - 'provider': 'ses', # email provider - } - - def test_ut_send_va_profile_email_status_sent_successfully(self, rmock, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(requests_mock.ANY, json=self.mock_response, status_code=200) - - mock_va_profile_client.send_va_profile_email_status(self.mock_notification_data) - - assert rmock.called - - expected_url = f'{MOCK_VA_PROFILE_URL}/contact-information-vanotify/notify/status' - assert rmock.request_history[0].url == expected_url - - def test_ut_send_va_profile_email_status_timeout(self, rmock, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(requests_mock.ANY, exc=requests.ReadTimeout) - - with pytest.raises(requests.Timeout): - mock_va_profile_client.send_va_profile_email_status(self.mock_notification_data) - - assert rmock.called - - expected_url = f'{MOCK_VA_PROFILE_URL}/contact-information-vanotify/notify/status' - assert rmock.request_history[0].url == expected_url - - def test_ut_send_va_profile_email_status_throws_exception(self, rmock, mock_va_profile_client, mocker): - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, 'True') - mock_feature_flag(mocker, FeatureFlag.VA_PROFILE_V3_IDENTIFY_MOBILE_TELEPHONE_NUMBERS, 'True') - - rmock.post(requests_mock.ANY, exc=requests.RequestException) - - with pytest.raises(requests.RequestException): - mock_va_profile_client.send_va_profile_email_status(self.mock_notification_data) - - assert rmock.called - - expected_url = f'{MOCK_VA_PROFILE_URL}/contact-information-vanotify/notify/status' - assert rmock.request_history[0].url == expected_url