Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement new celery task for pearson engine integration #210

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions eox_nelp/admin/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
for selected course enrollments.
"""

from django.conf import settings
from django.contrib import admin

from eox_nelp.admin.register_admin_model import register_admin_model as register
from eox_nelp.edxapp_wrapper.student import CourseEnrollment, CourseEnrollmentAdmin
from eox_nelp.pearson_vue.tasks import cdd_task, ead_task, real_time_import_task
from eox_nelp.pearson_vue.tasks import cdd_task, ead_task, real_time_import_task, real_time_import_task_v2


@admin.action(description="Execute Pearson RTI request")
Expand All @@ -30,10 +31,17 @@ def pearson_real_time_action(modeladmin, request, queryset): # pylint: disable=
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
real_time_import_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
)
if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
exam_id=str(course_enrollment.course_id),
action_name="rti",
)
else:
real_time_import_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
)


@admin.action(description="Execute Pearson EAD Add request")
Expand All @@ -50,11 +58,19 @@ def pearson_add_ead_action(modeladmin, request, queryset): # pylint: disable=un
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Add",
)
if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
exam_id=str(course_enrollment.course_id),
action_name="ead",
transaction_type="Add",
)
else:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Add",
)


@admin.action(description="Execute Pearson EAD Update request")
Expand All @@ -71,11 +87,19 @@ def pearson_update_ead_action(modeladmin, request, queryset): # pylint: disable
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Update",
)
if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
exam_id=str(course_enrollment.course_id),
action_name="ead",
transaction_type="Update",
)
else:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Update",
)


@admin.action(description="Execute Pearson EAD Delete request")
Expand All @@ -92,11 +116,19 @@ def pearson_delete_ead_action(modeladmin, request, queryset): # pylint: disable
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Delete",
)
if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
exam_id=str(course_enrollment.course_id),
action_name="ead",
transaction_type="Delete",
)
else:
ead_task.delay(
course_id=str(course_enrollment.course_id),
user_id=course_enrollment.user.id,
transaction_type="Delete",
)


@admin.action(description="Execute Pearson CDD request for student.")
Expand All @@ -113,9 +145,15 @@ def pearson_cdd_action(modeladmin, request, queryset): # pylint: disable=unused
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
cdd_task.delay(
user_id=course_enrollment.user.id,
)
if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
action_name="cdd",
)
else:
cdd_task.delay(
user_id=course_enrollment.user.id,
)


class NelpCourseEnrollmentAdmin(CourseEnrollmentAdmin):
Expand Down
133 changes: 101 additions & 32 deletions eox_nelp/admin/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from unittest.mock import MagicMock, patch

from ddt import data, ddt, unpack
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, TestCase, override_settings

from eox_nelp.admin import (
NelpCourseEnrollmentAdmin,
Expand All @@ -26,6 +26,42 @@ class TestPearsonAction(TestCase):
Unit tests for the pearson actions functions.
"""

def setUp(self):
"""Setup common conditions for every test case"""
self.request = RequestFactory().get("/admin")

def _create_mock_enrollment(self, course_id):
"""Create a mock course enrollment."""
user = MagicMock()
user.id = 1
enrollment = MagicMock()
enrollment.course_id = course_id
enrollment.user = user

return enrollment

def _prepare_call_kwargs(self, enrollments, call_args, extra_call_kwargs):
"""Prepare the call arguments for the mocked task."""
mocks_call_kwargs = []
for enrollment in enrollments:
call_kwargs = {
"user_id": enrollment.user.id,
"course_id": enrollment.course_id,
"exam_id": enrollment.course_id,
}
# Retain only required arguments and update with extra kwargs
call_kwargs = {key: call_kwargs[key] for key in call_args if key in call_kwargs}
call_kwargs.update(extra_call_kwargs)
mocks_call_kwargs.append(call_kwargs)

return mocks_call_kwargs

def _assert_mocked_task_calls(self, mocked_task, mocks_call_kwargs):
"""Assert that the mocked task was called with the correct parameters."""
for mock_call_kwargs in mocks_call_kwargs:
mocked_task.assert_any_call(**mock_call_kwargs)
self.assertEqual(mocked_task.call_count, len(mocks_call_kwargs))

@data(
{
"mock_task": "eox_nelp.pearson_vue.tasks.real_time_import_task.delay",
Expand Down Expand Up @@ -70,43 +106,76 @@ class TestPearsonAction(TestCase):
@unpack
def test_pearson_course_enrollment_action(self, mock_task, admin_action, call_args, extra_call_kwargs):
"""
Test that a pearson_action function calls the a task delay with correct parameters.
Test that a pearson_action function calls a task delay with correct parameters.
"""
user = MagicMock()
user.id = 1
course_enrollment_1 = MagicMock()
course_enrollment_1.course_id = "course-v1:TestX+T101+2024_T1"
course_enrollment_1.user = user
course_enrollment_2 = MagicMock()
course_enrollment_2.course_id = "course-v1:FutureX+T102+2025_T1"
course_enrollment_2.user = user
mocks_call_kwargs = [
{
"course_id": course_enrollment_1.course_id,
"user_id": user.id,
},
{
"course_id": course_enrollment_2.course_id,
"user_id": user.id,
}
queryset = [
self._create_mock_enrollment("course-v1:TestX+T101+2024_T1"),
self._create_mock_enrollment("course-v1:FutureX+T102+2025_T1"),
]
for mock_call_kwargs in mocks_call_kwargs:
for key in set(mock_call_kwargs.keys()).difference(call_args): # set main call args
del mock_call_kwargs[key]
mock_call_kwargs.update(extra_call_kwargs)
mocks_call_kwargs = self._prepare_call_kwargs(queryset, call_args, extra_call_kwargs)

queryset = [course_enrollment_1, course_enrollment_2]
modeladmin = MagicMock()
# Call the admin action
with patch(mock_task) as mocked_task:
admin_action(MagicMock(), self.request, queryset)
self._assert_mocked_task_calls(mocked_task, mocks_call_kwargs)

request = RequestFactory().get("/admin")
@data(
{
"admin_action": pearson_real_time_action,
"call_args": ["user_id", "exam_id"],
"extra_call_kwargs": {
"action_name": "rti",
},
},
{
"admin_action": pearson_add_ead_action,
"call_args": ["user_id", "exam_id"],
"extra_call_kwargs": {
"transaction_type": "Add",
"action_name": "ead",
},

},
{
"admin_action": pearson_update_ead_action,
"call_args": ["user_id", "exam_id"],
"extra_call_kwargs": {
"transaction_type": "Update",
"action_name": "ead",
},
},
{
"admin_action": pearson_delete_ead_action,
"call_args": ["user_id", "exam_id"],
"extra_call_kwargs": {
"transaction_type": "Delete",
"action_name": "ead",
},
},
{
"admin_action": pearson_cdd_action,
"call_args": ["user_id"],
"extra_call_kwargs": {
"action_name": "cdd",
},
},
)
@unpack
@override_settings(USE_PEARSON_ENGINE_SERVICE=True)
def test_pearson_course_enrollment_action_v2(self, admin_action, call_args, extra_call_kwargs):
"""
Test that a pearson_action function calls a task delay with correct parameters.
"""
queryset = [
self._create_mock_enrollment("course-v1:TestX+T101+2024_T1"),
self._create_mock_enrollment("course-v1:FutureX+T102+2025_T1"),
]
mocks_call_kwargs = self._prepare_call_kwargs(queryset, call_args, extra_call_kwargs)

# Call the admin action
with patch(mock_task) as mocked_task:
admin_action(modeladmin, request, queryset)
for mock_call_kwargs in mocks_call_kwargs:
mocked_task.assert_any_call(**mock_call_kwargs)
mocked_task.assert_any_call(**mock_call_kwargs)
self.assertEqual(mocked_task.call_count, len(mocks_call_kwargs))
with patch("eox_nelp.pearson_vue.tasks.real_time_import_task_v2.delay") as mocked_task:
admin_action(MagicMock(), self.request, queryset)
self._assert_mocked_task_calls(mocked_task, mocks_call_kwargs)


@ddt
Expand Down
6 changes: 6 additions & 0 deletions eox_nelp/pearson_vue/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@
</soapenv:Body>
</soapenv:Envelope>
"""

ALLOWED_RTI_ACTIONS = {
"rti": "real_time_import",
"cdd": "import_candidate_demographics",
"ead": "import_exam_authorization",
}
45 changes: 45 additions & 0 deletions eox_nelp/pearson_vue/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
"""

from celery import shared_task
from django.contrib.auth import get_user_model

from eox_nelp.api_clients.pearson_engine import PearsonEngineApiClient
from eox_nelp.pearson_vue.constants import ALLOWED_RTI_ACTIONS
from eox_nelp.pearson_vue.pipeline import audit_method, rename_function
from eox_nelp.pearson_vue.rti_backend import (
CandidateDemographicsDataImport,
ErrorRealTimeImportHandler,
ExamAuthorizationDataImport,
RealTimeImport,
)

User = get_user_model()


@shared_task(bind=True)
def real_time_import_task(self, pipeline_index=0, **kwargs):
Expand Down Expand Up @@ -93,3 +99,42 @@ def rti_error_handler_task(self, pipeline_index=0, **kwargs):
error_rti.run_pipeline()
except Exception as exc: # pylint: disable=broad-exception-caught
self.retry(exc=exc, kwargs=error_rti.backend_data)


@shared_task
def real_time_import_task_v2(user_id, exam_id=None, action_name="rti", **kwargs):
"""
Asynchronous task to perform a real-time import action using the Pearson Engine API.

This task handles different types of import actions, such as real-time import,
importing candidate demographics, and importing exam authorizations, by calling
the appropriate method on the PearsonEngineApiClient.

Args:
user_id (int): The ID of the user to be processed.
exam_id (str, optional): The ID of the exam for authorization. Default is None.
action_name (str, optional): The action to perform. Default is "rti".
Supported actions are:
- "rti" for real_time_import
- "cdd" for import_candidate_demographics
- "ead" for import_exam_authorization
**kwargs: Additional keyword arguments to pass to the API client method.

Raises:
KeyError: If action_name is not found in ALLOWED_RTI_ACTIONS.
User.DoesNotExist: If the user with the given user_id does not exist.
"""
action_key = ALLOWED_RTI_ACTIONS[action_name]

@audit_method(action="Pearson Engine Action")
@rename_function(name=action_key)
def audit_pearson_engine_action(user_id, exam_id, action_key, **kwargs):
action = getattr(PearsonEngineApiClient(), action_key)

action(
user=User.objects.get(id=user_id),
exam_id=exam_id,
**kwargs
)

audit_pearson_engine_action(user_id, exam_id, action_key, **kwargs)
Loading
Loading