Skip to content

Commit

Permalink
refactor: extract completion logic from pipe to implement it into rec…
Browse files Browse the repository at this point in the history
…eiver
  • Loading branch information
andrey-canon committed Aug 5, 2024
1 parent de37734 commit 83f7bdc
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 253 deletions.
29 changes: 29 additions & 0 deletions eox_nelp/admin/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
for selected course enrollments.
"""

import logging

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, real_time_import_task_v2

logger = logging.getLogger(__name__)


@admin.action(description="Execute Pearson RTI request")
def pearson_real_time_action(modeladmin, request, queryset): # pylint: disable=unused-argument
Expand All @@ -31,6 +35,11 @@ def pearson_real_time_action(modeladmin, request, queryset): # pylint: disable=
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
logger.info(
"Initializing rti task for the user %s, action triggered by admin action",
course_enrollment.user.id,
)

if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
Expand Down Expand Up @@ -58,6 +67,11 @@ def pearson_add_ead_action(modeladmin, request, queryset): # pylint: disable=un
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
logger.info(
"Initializing ead add task for the user %s, action triggered by admin action",
course_enrollment.user.id,
)

if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
Expand Down Expand Up @@ -87,6 +101,11 @@ def pearson_update_ead_action(modeladmin, request, queryset): # pylint: disable
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
logger.info(
"Initializing ead update task for the user %s, action triggered by admin action",
course_enrollment.user.id,
)

if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
Expand Down Expand Up @@ -116,6 +135,11 @@ def pearson_delete_ead_action(modeladmin, request, queryset): # pylint: disable
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
logger.info(
"Initializing ead delete task for the user %s, action triggered by admin action",
course_enrollment.user.id,
)

if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
Expand Down Expand Up @@ -145,6 +169,11 @@ def pearson_cdd_action(modeladmin, request, queryset): # pylint: disable=unused
queryset: The QuerySet of selected CourseEnrollment instances.
"""
for course_enrollment in queryset:
logger.info(
"Initializing cdd task for the user %s, action triggered by admin action",
course_enrollment.user.id,
)

if getattr(settings, "USE_PEARSON_ENGINE_SERVICE", False):
real_time_import_task_v2.delay(
user_id=course_enrollment.user.id,
Expand Down
43 changes: 0 additions & 43 deletions eox_nelp/pearson_vue/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
called sequentially, where each function processes data and passes it along to the next step in the pipeline.
Functions:
handle_course_completion_status: Pipeline function to handle course completion status.
get_user_data: Retrieves and processes user data.
check_service_availability: Checks the availability of the Pearson VUE RTI service.
import_candidate_demographics: Imports candidate demographics data.
Expand Down Expand Up @@ -36,7 +35,6 @@
PearsonValidationError,
)
from eox_nelp.pearson_vue.utils import generate_client_authorization_id, update_xml_with_dict
from eox_nelp.signals.utils import get_completed_and_graded

try:
from eox_audit_model.decorators import audit_method, rename_function
Expand All @@ -55,47 +53,6 @@ def rename_function(name): # pylint: disable=unused-argument
User = get_user_model()


def handle_course_completion_status(user_id, course_id, **kwargs):
"""Pipeline that check the case of completion cases on the pipeline execution. Also this pipe
has 4 behaviours depending the case:
- skip this pipeline if setting PEARSON_RTI_TESTING_SKIP_HANDLE_COURSE_COMPLETION_STATUS is truthy.
Pipeline continues.
- is_passing is true means the course is graded(passed) and dont needs this pipe validation.
The pipeline continues without changes.
- is_complete=True and is_graded=False pipeline should continue.
(completed courses and not graded).
- Otherwise this indicates that the pipeline execution would be stopped,
for grading-courses the COURSE_GRADE_NOW_PASSED signal would act.
Args:
user_id (int): The ID of the user whose data is to be retrieved.
course_id (str): course_id to check completion or graded.
**kwargs: Additional keyword arguments.
Returns:
dict: Pipeline dict
"""
if getattr(settings, "PEARSON_RTI_TESTING_SKIP_HANDLE_COURSE_COMPLETION_STATUS", False):
logger.info(
"Skipping `handle_course_completion_status` pipe for user_id:%s and course_id: %s",
str(user_id),
course_id
)
return None

if kwargs.get("is_passing"):
return None

is_complete, is_graded = get_completed_and_graded(user_id, course_id)

if is_complete and not is_graded:
return None

return {
"safely_pipeline_termination": True,
}


def get_user_data(user_id, **kwargs): # pylint: disable=unused-argument
"""
Retrieves and processes user data for the pipeline.
Expand Down
6 changes: 1 addition & 5 deletions eox_nelp/pearson_vue/rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
get_enrollment_from_id,
get_exam_data,
get_user_data,
handle_course_completion_status,
import_candidate_demographics,
import_exam_authorization,
validate_cdd_request,
Expand Down Expand Up @@ -86,9 +85,6 @@ def run_pipeline(self):
break

self.backend_data.update(result)
if result.get("safely_pipeline_termination"):
self.backend_data["pipeline_index"] = len(pipeline) - 1
break

@abstractmethod
def get_pipeline(self):
Expand Down Expand Up @@ -145,6 +141,7 @@ class RealTimeImport(AbstractBackend):
run_pipeline(): Executes the RTI pipeline by iterating through the pipeline functions.
get_pipeline(): Returns the RTI pipeline, which is a list of functions to be executed.
"""

def handle_error(self, exception, failed_step_pipeline):
"""
Handles errors during pipeline execution.
Expand All @@ -166,7 +163,6 @@ def get_pipeline(self):
Returns the RTI pipeline, which is a list of functions to be executed.
"""
return [
handle_course_completion_status,
get_user_data,
get_exam_data,
build_cdd_request,
Expand Down
147 changes: 0 additions & 147 deletions eox_nelp/pearson_vue/tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
get_enrollment_from_id,
get_exam_data,
get_user_data,
handle_course_completion_status,
import_candidate_demographics,
import_exam_authorization,
validate_cdd_request,
Expand Down Expand Up @@ -81,152 +80,6 @@
}


class TestTerminateNotFullCompletionCases(unittest.TestCase):
"""
Unit tests for the handle_course_completion_status function.
"""

def setUp(self):
"""
Set up the test environment.
"""
self.user_id = 1
self.course_id = "course-v1:edX+213+2121"

@override_settings(PEARSON_RTI_TESTING_SKIP_HANDLE_COURSE_COMPLETION_STATUS=True)
@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_skip_pipe_with_settings(self, get_completed_and_graded_mock):
"""Test the pipeline is skipped with truthy
`PEARSON_RTI_TESTING_SKIP_HANDLE_COURSE_COMPLETION_STATUS` setting.
Expected behavior:
- logger info message expected
- get_completed_and_graded_mock is not called.
- Returned value is None
"""
pipeline_kwargs = {}
log_info = (
f"INFO:{pipeline.__name__}:Skipping `handle_course_completion_status` "
f"pipe for user_id:{self.user_id} and course_id: {self.course_id}"
)

with self.assertLogs(pipeline.__name__, level="INFO") as logs:
result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

self.assertEqual(logs.output, [log_info])
get_completed_and_graded_mock.assert_not_called()
self.assertIsNone(result)

@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_check_is_passing_bypass(self, get_completed_and_graded_mock):
"""Test the pipeline dont do anything if is_passing kwarg is truthy.
Expected behavior:
- get_completed_and_graded_mock is not called.
- Returned value is None
"""
pipeline_kwargs = {"is_passing": True}

result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

get_completed_and_graded_mock.assert_not_called()
self.assertIsNone(result)

@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_check_is_complete_not_graded(self, get_completed_and_graded_mock):
"""Test the pipeline return expected dict with empty `is_passing`, and
is_complete=True, is_graded=False.
Expected behavior:
- Returned value is None
"""
pipeline_kwargs = {}
get_complete_and_graded_output = {
"is_complete": True,
"is_graded": False,
}
get_completed_and_graded_mock.return_value = (
get_complete_and_graded_output["is_complete"],
get_complete_and_graded_output["is_graded"],
)

result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

self.assertIsNone(result)

@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_check_not_complete_not_graded(self, get_completed_and_graded_mock):
"""Test the pipeline return expected dict(safely_pipeline_termination) with empty `is_passing`, and
is_complete=False, is_graded=False.
Expected behavior:
- Returned value is dict with `safely_pipeline_termination`
"""
pipeline_kwargs = {}
get_complete_and_graded_output = {
"is_complete": False,
"is_graded": False,
}
expected_output = {
"safely_pipeline_termination": True,
}
get_completed_and_graded_mock.return_value = (
get_complete_and_graded_output["is_complete"],
get_complete_and_graded_output["is_graded"],
)

result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

self.assertDictEqual(expected_output, result)

@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_check_not_complete_graded(self, get_completed_and_graded_mock):
"""Test the pipeline return expected dict(safely_pipeline_termination) with empty `is_passing`, and
is_complete=False, is_graded=True.
Expected behavior:
- Returned value is dict with `safely_pipeline_termination`
"""
pipeline_kwargs = {}
get_complete_and_graded_output = {
"is_complete": False,
"is_graded": True,
}
expected_output = {
"safely_pipeline_termination": True,
}
get_completed_and_graded_mock.return_value = (
get_complete_and_graded_output["is_complete"],
get_complete_and_graded_output["is_graded"],
)

result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

self.assertDictEqual(expected_output, result)

@patch("eox_nelp.pearson_vue.pipeline.get_completed_and_graded")
def test_check_complete_graded(self, get_completed_and_graded_mock):
"""Test the pipeline return expected dict(safely_pipeline_termination) with empty `is_passing`, and
is_complete=True, is_graded=True.
Expected behavior:
- Returned value is dict with `safely_pipeline_termination`
"""
pipeline_kwargs = {}
get_complete_and_graded_output = {
"is_complete": True,
"is_graded": True,
}
expected_output = {
"safely_pipeline_termination": True,
}
get_completed_and_graded_mock.return_value = (
get_complete_and_graded_output["is_complete"],
get_complete_and_graded_output["is_graded"],
)

result = handle_course_completion_status(self.user_id, self.course_id, **pipeline_kwargs)

self.assertDictEqual(expected_output, result)


@ddt
class TestGetUserData(unittest.TestCase):
"""
Expand Down
37 changes: 0 additions & 37 deletions eox_nelp/pearson_vue/tests/test_rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,43 +120,6 @@ def test_pipeline_index(self):
},
)

def test_safely_pipeline_termination(self):
"""
Test the execution of the RTI finished after the second function call due
`safely_pipeline_termination` kwarg.
Expected behavior:
- Pipeline method 1 is called with the original data.
- Pipeline method 2 is called with updated data.
- Pipeline method 3 is not called.
- Pipeline method 4 is not called.
- backend_data attribute is the expected value.
Without func3,func4 data and pipeline index in the last.
"""
# Mock pipeline functions
func1 = MagicMock(return_value={"updated_key": "value1"})
func2 = MagicMock(return_value={"safely_pipeline_termination": True})
func3 = MagicMock(return_value={"additional_key": "value3"})
func4 = MagicMock(return_value={"additional_key": "value4"})

self.rti.get_pipeline = MagicMock(return_value=[func1, func2, func2])

self.rti.run_pipeline()

func1.assert_called_once_with(**self.backend_data)
func2.assert_called_once_with(**{"updated_key": "value1", "pipeline_index": 1})
func3.assert_not_called()
func4.assert_not_called()

self.assertDictEqual(
self.rti.backend_data,
{
"pipeline_index": len(self.rti.get_pipeline()) - 1, # includes total of pipeline methods
**func1(), # Include data from func1 ()
**func2(), # Include data from func2 (with safely_pipeline_termination)
},
)

def test_get_pipeline(self):
"""
Test the retrieval of the RTI pipeline.
Expand Down
Loading

0 comments on commit 83f7bdc

Please sign in to comment.