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

refactor: extract completion logic from pipe to implement it into rec… #213

Merged
merged 2 commits into from
Aug 6, 2024
Merged
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
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
Loading