Skip to content

Commit

Permalink
fix: adding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kiram15 committed Nov 9, 2023
1 parent 646404e commit c3f1d30
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def cancel(self, request, *args, uuid=None, **kwargs):

def remind(self, request, *args, uuid=None, **kwargs):
"""
Send reminders to a single learners with associated ``LearnerContentAssignment``
Send reminders to a single learners with associated ``LearnerContentAssignment``
record by uuid.
Raises:
Expand All @@ -214,17 +214,17 @@ def remind(self, request, *args, uuid=None, **kwargs):
except LearnerContentAssignment.DoesNotExist:
return Response(None, status=status.HTTP_404_NOT_FOUND)

# if the assignment is not cancelable, this is a no-op.
cancellation_info = assignments_api.cancel_assignments([assignment_to_cancel])
# if the assignment is not remindable, this is a no-op.
reminder_info = assignments_api.remind_assignments([assignment_to_remind])

# If the response contains one element in the `canceled` list, that is the one we sent, indicating success.
cancellation_succeeded = len(cancellation_info['canceled']) == 1
# If the response contains one element in the `reminded` list, that is the one we sent, indicating success.
reminder_succeeded = len(reminder_info['reminded']) == 1

if cancellation_succeeded:
if reminder_succeeded:
# Serialize the assignment object obtained via get_queryset() instead of the one from the assignments_api.
# Only the former has the additional dynamic fields annotated, and those are required for serialization.
assignment_to_cancel.refresh_from_db()
response_serializer = serializers.LearnerContentAssignmentAdminResponseSerializer(assignment_to_cancel)
assignment_to_remind.refresh_from_db()
response_serializer = serializers.LearnerContentAssignmentAdminResponseSerializer(assignment_to_remind)
return Response(response_serializer.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
return Response(status=status.HTTP_422_UNPROCESSABLE_ENTITY)
4 changes: 3 additions & 1 deletion enterprise_access/apps/content_assignments/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,12 +469,14 @@ def get_content_metadata_for_assignments(enterprise_catalog_uuid, assignments):
for assignment in assignments
}


def remind_assignments(assignments: Iterable[LearnerContentAssignment]) -> dict:
"""
Bulk remind assignments.
This is a no-op for assignments in the following states: [accepted, errored, canceled]. We only allow
assignments which are in the allocated state. Canceled and already-canceled assignments are bundled in the response because this function is meant to be idempotent.
assignments which are in the allocated state. Canceled and already-canceled assignments are bundled in
the response because this function is meant to be idempotent.
Args:
Expand Down
71 changes: 43 additions & 28 deletions enterprise_access/apps/content_assignments/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
"""

import logging
from datetime import datetime

from celery import shared_task
from django.apps import apps
from django.conf import settings

import enterprise_access.apps.content_assignments.api as content_api
from enterprise_access.apps.api_client.braze_client import BrazeApiClient
from enterprise_access.apps.api_client.lms_client import LmsApiClient
from enterprise_access.apps.content_assignments.constants import AssignmentActionErrors, AssignmentActions
from enterprise_access.apps.content_assignments.models import LearnerContentAssignment, LearnerContentAssignmentAction
from enterprise_access.apps.subsidy_request import _get_course_partners
from enterprise_access.tasks import LoggedTaskWithRetry

from .constants import LearnerContentAssignmentStateChoices
Expand Down Expand Up @@ -79,58 +82,66 @@ def create_pending_enterprise_learner_for_assignment_task(learner_content_assign
)


def _get_course_partners(course_data):
"""
Returns a list of course partner data for subsidy requests given a course dictionary.
"""
owners = course_data.get('owners') or []
return [{'uuid': owner.get('uuid'), 'name': owner.get('name')} for owner in owners]


@shared_task(base=LoggedTaskWithRetry)
def send_reminder_email_for_pending_assignment(assignment_uuid):
"""
Send email via braze for reminding users of their pending assignment
Args:
assignment_uuid: (string) the subsidy request uuid
"""
assignment = LearnerContentAssignment.objects.get(assignment_uuid)
policy = SubsidyAccessPolicy.objects.get(
assignment_configuration=assignment.assignment_configuration
)
# policy.catalog_uuid
get_content_metadata_for_assignments(policy.catalog_uuid, )
# caches (be mindful)
learner_content_assignment_model = apps.get_model('content_assignments.LearnerContentAssignment')
subsidy_policy_model = apps.get_model('subsidy_access_policy.SubsidyAccessPolicy')
try:
assignment = learner_content_assignment_model.objects.get(uuid=assignment_uuid)
except learner_content_assignment_model.DoesNotExist:
logger.warning(f'request with uuid: {cancelled_assignment_uuid} does not exist.')
return

# subsidy = SubsidyRequest.objects.get(
# uuid=policy.subsidy_uuid
# )
try:
policy = subsidy_policy_model.objects.get(
assignment_configuration=assignment.assignment_configuration
)
except subsidy_policy_model.DoesNotExist:
logger.warning(f'policy with assignment config: {assignment.assignment_configuration} does not exist.')
return

learner_content_assignment_action = LearnerContentAssignmentAction(
assignment=assignment, action_type=AssignmentActions.REMINDED,
)

if braze_trigger_properties is None:
braze_trigger_properties = {}

braze_trigger_properties = {}
braze_client_instance = BrazeApiClient()
lms_client = LmsApiClient()
enterprise_customer_uuid = assignment.assignment_configuration.enterprise_customer_uuid
enterprise_customer_data = lms_client.get_enterprise_customer_data(enterprise_customer_uuid)
# is content_key the same as course_id from subsidy info?
# if not can i fetch subsidy request which has the course_id?
course_data = discovery_client.get_course_data(assignment.content_key)
# how can a subsidy request have information about the course for this assignment though?
# course_data = discovery_client.get_course_data(subsidy.course_id)
admin_emails = [user['email'] for user in enterprise_customer_data['admin_users']]
course_metadata = content_api.get_content_metadata_for_assignments(
policy.catalog_uuid, assignment.assignment_configuration
)
lms_user_id = assignment.lms_user_id
braze_trigger_properties['contact_admin_link'] = braze_client_instance.generate_mailto_link(admin_emails)

try:
recipient = braze_client_instance.create_recipient(
user_email=assignment.learner_email,
lms_user_id=assignment.lms_user_id,
)
braze_trigger_properties["first_name"] = lms_user_id
braze_trigger_properties["organization"] = enterprise_customer_data['name']
braze_trigger_properties["course_title"] = assignment.content_title
braze_trigger_properties["enrollment_deadline"] = course_data["enrollment_end"]
braze_trigger_properties["start_date"] = course_data["start"]
braze_trigger_properties["course_partner"] = _get_course_partners(course_data)
braze_trigger_properties["course_card_image"] = course_data['card_image_url']

braze_trigger_properties["enrollment_deadline"] = course_metadata['normalized_metadata']['enroll_by_date']
braze_trigger_properties["start_date"] = course_metadata['normalized_metadata']['start_date']
braze_trigger_properties["course_partner"] = _get_course_partners(course_metadata)
braze_trigger_properties["course_card_image"] = course_metadata['card_image_url']
# Call to action link to Learner Portal, with logistration logic.
# Admin email hyperlink (should be available in the LMS model for the enterprise – if not available, conditional logic might be required to make this not be a link).
# Admin email hyperlink (should be available in the LMS model for the enterprise –
# if not available, conditional logic might be required to make this not be a link).

braze_client_instance.send_campaign_message(
settings.BRAZE_ASSIGNMENT_REMINDER_NOTIFICATION_CAMPAIGN,
Expand All @@ -139,6 +150,10 @@ def send_reminder_email_for_pending_assignment(assignment_uuid):
)
learner_content_assignment_action.completed_at = datetime.now()
learner_content_assignment_action.save()
logger.info(f'Sending braze campaign message for reminded assignment {assignment}')
return
except Exception as exc:
logger.error(f"Unable to send email for {lms_user_id} due to exception: {exc}")
learner_content_assignment_action.error_reason = exc
learner_content_assignment_action.error_reason = AssignmentActionErrors.EMAIL_ERROR
learner_content_assignment_action.traceback = exc
learner_content_assignment_action.save()
101 changes: 99 additions & 2 deletions enterprise_access/apps/content_assignments/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@

from enterprise_access.apps.api_client.tests.test_utils import MockResponse
from enterprise_access.apps.content_assignments.constants import LearnerContentAssignmentStateChoices
from enterprise_access.apps.content_assignments.tasks import create_pending_enterprise_learner_for_assignment_task
from enterprise_access.apps.content_assignments.models import LearnerContentAssignment, LearnerContentAssignmentAction
from enterprise_access.apps.content_assignments.tasks import (
create_pending_enterprise_learner_for_assignment_task,
send_reminder_email_for_pending_assignment
)
from enterprise_access.apps.content_assignments.tests.factories import (
AssignmentConfigurationFactory,
LearnerContentAssignmentFactory
)
from enterprise_access.apps.subsidy_access_policy.tests.factories import AssignedLearnerCreditAccessPolicyFactory
from enterprise_access.apps.subsidy_access_policy.tests.factories import (
AssignedLearnerCreditAccessPolicyFactory,
PerLearnerEnrollmentCapLearnerCreditAccessPolicyFactory
)
from test_utils import APITestWithMocks

TEST_ENTERPRISE_UUID = uuid4()
TEST_EMAIL = 'foo@bar.com'
TEST_LMS_USER_ID = 2
TEST_ASSIGNMENT_UUID = uuid4()


@ddt.ddt
Expand Down Expand Up @@ -174,3 +183,91 @@ def test_last_retry_success(self, mock_oauth_client):
# Make sure the assignment state does NOT change to errored.
self.assignment.refresh_from_db()
assert self.assignment.state == LearnerContentAssignmentStateChoices.ALLOCATED


class TestBrazeEmailTasks(APITestWithMocks):
"""
Verify cancel and remind emails hit braze client with expected args
"""
@classmethod
def setUpTestData(cls):
super().setUpTestData()
cls.assignment_configuration = AssignmentConfigurationFactory(
enterprise_customer_uuid=TEST_ENTERPRISE_UUID,
uuid=TEST_ASSIGNMENT_UUID,
)

def setUp(self):
super().setUp()
self.course_name = 'test-course-name'
self.enterprise_customer_name = 'test-customer-name'
self.assignment = LearnerContentAssignmentFactory(
uuid=TEST_ASSIGNMENT_UUID,
learner_email='TESTING THIS EMAIL',
lms_user_id=TEST_LMS_USER_ID,
assignment_configuration=self.assignment_configuration,
)
self.policy = PerLearnerEnrollmentCapLearnerCreditAccessPolicyFactory()

@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.objects')
@mock.patch('enterprise_access.apps.content_assignments.api.get_content_metadata_for_assignments')
@mock.patch('enterprise_access.apps.content_assignments.tasks.LmsApiClient')
@mock.patch('enterprise_access.apps.content_assignments.tasks.BrazeApiClient')
def test_send_reminder_email_for_pending_assignment(
self, mock_braze_client, mock_lms_client, mock_get_metadata, mock_policy_model
):
"""
Verify send_reminder_email_for_pending_assignment hits braze client with expected args
"""
content_key = 'demoX'
admin_email = 'test@admin.com'
mock_lms_client.return_value.get_enterprise_customer_data.return_value = {
'uuid': TEST_ENTERPRISE_UUID,
'slug': 'test-slug',
'admin_users': [{
'email': admin_email,
'lms_user_id': 1
}],
'name': self.enterprise_customer_name,
}
mock_recipient = {
'external_user_id': 1
}
mock_get_metadata.return_value = {
'key': content_key,
'normalized_metadata': {
'start_date': '2020-01-01 12:00:00Z',
'end_date': '2022-01-01 12:00:00Z',
'enroll_by_date': '2021-01-01 12:00:00Z',
'content_price': 123,
},
'owners': [
{'name': 'Smart Folks', 'logo_image_url': 'http://pictures.yes'},
],
'card_image_url': 'https://itsanimage.com'
}

mock_admin_mailto = f'mailto:{admin_email}'
mock_braze_client.return_value.create_recipient.return_value = mock_recipient
mock_braze_client.return_value.generate_mailto_link.return_value = mock_admin_mailto
send_reminder_email_for_pending_assignment(self.assignment.uuid)

# Make sure our LMS client got called correct times and with what we expected
mock_lms_client.return_value.get_enterprise_customer_data.assert_called_with(
self.assignment_configuration.enterprise_customer_uuid
)

mock_braze_client.return_value.send_campaign_message.assert_any_call(
'test-assignment-remind-campaign',
recipients=[mock_recipient],
trigger_properties={
'contact_admin_link': mock_admin_mailto,
'organization': self.enterprise_customer_name,
'course_name': self.assignment.content_title,
'enrollment_deadline': '2021-01-01 12:00:00Z',
'start_date': '2020-01-01 12:00:00Z',
'course_partner': 'Smart Folks',
'course_card_image': 'https://itsanimage.com',
},
)
assert mock_braze_client.return_value.send_campaign_message.call_count == 1
1 change: 0 additions & 1 deletion enterprise_access/apps/subsidy_request/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ def send_admins_email_with_new_requests_task(enterprise_customer_uuid):
lms_client = LmsApiClient()
enterprise_customer_data = lms_client.get_enterprise_customer_data(enterprise_customer_uuid)
enterprise_slug = enterprise_customer_data.get('slug')
braze_trigger_properties = {}
braze_trigger_properties['manage_requests_url'] = _get_manage_requests_url(subsidy_model, enterprise_slug)

braze_trigger_properties['requests'] = []
Expand Down
2 changes: 1 addition & 1 deletion enterprise_access/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
BRAZE_DECLINE_NOTIFICATION_CAMPAIGN = 'test-decline-campaign'
BRAZE_AUTO_DECLINE_NOTIFICATION_CAMPAIGN = 'test-campaign-id'
BRAZE_NEW_REQUESTS_NOTIFICATION_CAMPAIGN = 'test-new-subsidy-campaign'
BRAZE_ASSIGNMENT_REMINDER_NOTIFICATION_CAMPAIGN = 'test-remind-campaign'
BRAZE_ASSIGNMENT_REMINDER_NOTIFICATION_CAMPAIGN = 'test-assignment-remind-campaign'

# SEGMENT CONFIGURATION
SEGMENT_KEY = 'test-key'
Expand Down

0 comments on commit c3f1d30

Please sign in to comment.