diff --git a/eox_nelp/pearson_vue/pipeline.py b/eox_nelp/pearson_vue/pipeline.py index def22327..a1df2b7d 100644 --- a/eox_nelp/pearson_vue/pipeline.py +++ b/eox_nelp/pearson_vue/pipeline.py @@ -18,21 +18,21 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.utils import timezone +from pydantic import ValidationError from eox_nelp.api_clients.pearson_rti import PearsonRTIApiClient from eox_nelp.edxapp_wrapper.student import anonymous_id_for_user from eox_nelp.pearson_vue.constants import PAYLOAD_CDD, PAYLOAD_EAD, PAYLOAD_PING_DATABASE +from eox_nelp.pearson_vue.data_classes import CddRequest, EadRequest 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 except ImportError: def audit_method(action): # pylint: disable=unused-argument """Identity audit_method""" return lambda x: x -from pydantic import ValidationError - -from eox_nelp.pearson_vue.data_classes import CddRequest, EadRequest logger = logging.getLogger(__name__) @@ -390,7 +390,7 @@ def build_ead_request( } -def audit_pipe_error(*args, **kwargs): +def audit_error_validation(*args, **kwargs): """ Method to save an error with eox-audit. Args: @@ -410,11 +410,10 @@ def audit_exception(*args, **kwargs): pass logger.error("Validation Error args:%s-kwargs:%s", args, kwargs) - return None def validate_cdd_request(cdd_request, **kwargs): # pylint: disable=unused-argument): """ - Validates an CDD request dictionary using a Pydantic model. + Validates a CDD request dictionary using a Pydantic model. This function attempts to create a Pydantic model instance (likely named `class CddRequest`: `) @@ -436,6 +435,7 @@ def validate_cdd_request(cdd_request, **kwargs): # pylint: disable=unused-argum return None + def validate_ead_request(ead_request, **kwargs): # pylint: disable=unused-argument """ Validates an EAD request dictionary using a Pydantic model. diff --git a/eox_nelp/pearson_vue/rti_backend.py b/eox_nelp/pearson_vue/rti_backend.py index cc131586..f3affd2c 100644 --- a/eox_nelp/pearson_vue/rti_backend.py +++ b/eox_nelp/pearson_vue/rti_backend.py @@ -8,7 +8,7 @@ import importlib from eox_nelp.pearson_vue.pipeline import ( - audit_pipe_error, + audit_error_validation, build_cdd_request, build_ead_request, check_service_availability, @@ -56,10 +56,10 @@ def run_pipeline(self): if result.get("safely_pipeline_termination"): self.backend_data["pipeline_index"] = len(pipeline) - 1 break - if error_kwargs := result.get("launch_validation_error_pipeline"): + if result.get("launch_validation_error_pipeline"): self.backend_data["pipeline_index"] = len(pipeline) - 1 - error_validation_task = importlib.import_module("eox_nelp.pearson_vue.tasks.error_validation_task") - error_validation_task.delay(**error_kwargs, **self.backend_data) + tasks = importlib.import_module("eox_nelp.pearson_vue.tasks") + tasks.error_validation_task.delay(**self.backend_data) break def get_pipeline(self): @@ -106,12 +106,13 @@ def get_pipeline(self): import_candidate_demographics, ] + class ErrorValidationDataImport(RealTimeImport): """Class for managing validation error pipe executing the pipeline for data validation.""" def get_pipeline(self): """ - Returns the CDD pipeline, which is a list of functions to be executed. + Returns the error validation pipeline, which is a list of functions to be executed. """ return [ - audit_pipe_error, + audit_error_validation, ] diff --git a/eox_nelp/pearson_vue/tasks.py b/eox_nelp/pearson_vue/tasks.py index bd85d4a8..a30789d6 100644 --- a/eox_nelp/pearson_vue/tasks.py +++ b/eox_nelp/pearson_vue/tasks.py @@ -9,8 +9,8 @@ from eox_nelp.pearson_vue.rti_backend import ( CandidateDemographicsDataImport, - ExamAuthorizationDataImport, ErrorValidationDataImport, + ExamAuthorizationDataImport, RealTimeImport, ) diff --git a/eox_nelp/pearson_vue/tests/test_pipeline.py b/eox_nelp/pearson_vue/tests/test_pipeline.py index 03b3de12..69162789 100644 --- a/eox_nelp/pearson_vue/tests/test_pipeline.py +++ b/eox_nelp/pearson_vue/tests/test_pipeline.py @@ -11,13 +11,12 @@ from django.utils import timezone from django_countries.fields import Country from pydantic.v1.utils import deep_update -from eox_nelp.pearson_vue.pipeline import validate_cdd_request, validate_ead_request from eox_nelp.edxapp_wrapper.student import CourseEnrollment, anonymous_id_for_user from eox_nelp.pearson_vue import pipeline from eox_nelp.pearson_vue.constants import PAYLOAD_CDD, PAYLOAD_EAD, PAYLOAD_PING_DATABASE from eox_nelp.pearson_vue.pipeline import ( - audit_pipe_error, + audit_error_validation, build_cdd_request, build_ead_request, check_service_availability, @@ -26,6 +25,8 @@ handle_course_completion_status, import_candidate_demographics, import_exam_authorization, + validate_cdd_request, + validate_ead_request, ) User = get_user_model() @@ -834,20 +835,27 @@ def test_correct_ead_request(self): class TestAuditPipeError(unittest.TestCase): """ - Unit tests for the audit_pipe_error method. + Unit tests for the audit_error_validation method. """ - def test_audit_pipe_error(self): + def test_audit_error_validation(self): + """Test correct behaviour calling audit_error_validation. + + Expected behavior: + - The result is the expected value(None). + - Expected log error. + """ kwargs = { "validation_exception": { "error": ["String to short."] } } args = ("vaderio", 3244) - with self.assertLogs(pipeline.__name__, level="ERROR") as logs: - result = audit_pipe_error(*args, **kwargs) - self.assertIsNone(result) log_error = [ f"ERROR:{pipeline.__name__}:Validation Error args:{args}-kwargs:{kwargs}" ] + + with self.assertLogs(pipeline.__name__, level="ERROR") as logs: + self.assertIsNone(audit_error_validation(*args, **kwargs)) + self.assertListEqual(log_error, logs.output) diff --git a/eox_nelp/pearson_vue/tests/test_rti_backend.py b/eox_nelp/pearson_vue/tests/test_rti_backend.py index 3609eeeb..9da4582d 100644 --- a/eox_nelp/pearson_vue/tests/test_rti_backend.py +++ b/eox_nelp/pearson_vue/tests/test_rti_backend.py @@ -2,7 +2,7 @@ This module contains unit tests for the RealTimeImport class and its methods in rti_backend.py. """ import unittest -from unittest.mock import MagicMock, call +from unittest.mock import MagicMock, call, patch from eox_nelp.pearson_vue.rti_backend import ( CandidateDemographicsDataImport, @@ -140,6 +140,49 @@ def test_safely_pipeline_termination(self): }, ) + @patch("eox_nelp.pearson_vue.tasks.error_validation_task") + def test_launch_validation_error_pipeline(self, error_validation_task_mock): + """ + Test the execution of the RTI finished after the second function call due + `launch_validation_error_pipeline` 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. + - error_validation_task is called with updated backend_data kwargs. + """ + # Mock pipeline functions + func1 = MagicMock(return_value={"updated_key": "value1"}) + func2 = MagicMock(return_value={"launch_validation_error_pipeline": { + "validation_exception": { + "error": ["String to short."] + } + }}) + 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) + }, + ) + error_validation_task_mock.delay.assert_called_with(**self.rti.backend_data) + def test_get_pipeline(self): """ Test the retrieval of the RTI pipeline. @@ -167,6 +210,7 @@ class TestCandidateDemographicsDataImport(TestRealTimeImport): """ rti_backend_class = CandidateDemographicsDataImport + class TestErrorValidationDataImport(TestRealTimeImport): """ Unit tests for the rti_backend class. diff --git a/eox_nelp/pearson_vue/tests/test_tasks.py b/eox_nelp/pearson_vue/tests/test_tasks.py index 9f546471..edd1263a 100644 --- a/eox_nelp/pearson_vue/tests/test_tasks.py +++ b/eox_nelp/pearson_vue/tests/test_tasks.py @@ -4,7 +4,7 @@ import unittest from unittest.mock import MagicMock, call -from eox_nelp.pearson_vue.tasks import cdd_task, ead_task, real_time_import_task, error_validation_task +from eox_nelp.pearson_vue.tasks import cdd_task, ead_task, error_validation_task, real_time_import_task class TestRealTimeImportTask(unittest.TestCase): @@ -87,6 +87,7 @@ class TestCddTask(TestRealTimeImportTask): import_class_patch = "eox_nelp.pearson_vue.tasks.CandidateDemographicsDataImport" import_task_function = cdd_task + class TestErrorValidationTask(TestRealTimeImportTask): """ Unit tests for the cdd_task function. diff --git a/eox_nelp/pearson_vue/validators.py b/eox_nelp/pearson_vue/validators.py deleted file mode 100644 index 0553a261..00000000 --- a/eox_nelp/pearson_vue/validators.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Module to add validators related Pearson Vue Integration""" - -import logging - -from pydantic import ValidationError - -from eox_nelp.pearson_vue.data_classes import CddRequest, EadRequest - -try: - from eox_audit_model.decorators import audit_method -except ImportError: - def audit_method(action): # pylint: disable=unused-argument - """Identity audit_method""" - return lambda x: x - - -logger = logging.getLogger(__name__) - - -def validate_cdd_request(cdd_request): - """ - Validates an CDD request dictionary using a Pydantic model. - - This function attempts to create a Pydantic model instance (likely named `class CddRequest`: -`) - from the provided `cdd_request` dictionary. It performs data validation based on the - model's data type definitions. - Then if there is an error then that error is raised using audit. audit_validation_error - - Args: - cdd_request (dict): The dictionary containing the EAD request data. - """ - try: - CddRequest(**cdd_request) - except ValidationError as validation_exception: - logger.info("Validation error for cdd_request: %s \n %s", cdd_request, validation_exception) - audit_validation_error(validation_exception.json(), cdd_request=cdd_request) - - logger.info("Valid values for cdd_request: \n @clientCandidateID: %s", cdd_request["@clientCandidateID"]) - - -def validate_ead_request(ead_request): - """ - Validates an EAD request dictionary using a Pydantic model. - - This function attempts to create a Pydantic model instance (likely named `EadRequest`) - from the provided `ead_request` dictionary. It performs data validation based on the - model's data type definitions. - Then if there is an error then that error is raised using audit. audit_validation_error - - Args: - ead_request (dict): The dictionary containing the EAD request data. - """ - try: - EadRequest(**ead_request) - except ValidationError as validation_exception: - logger.info("Validation error for ead_request: %s \n %s", ead_request, validation_exception) - audit_validation_error(validation_exception.json(), ead_request=ead_request) - logger.info("Valid values for ead_request: \n @clientAuthorizationID: %s", ead_request["@clientAuthorizationID"]) - - -@audit_method(action="PearsonVue Error validating data") -def audit_validation_error(*args, **kwargs): - """ - Method to raise an error with eox-audit - Args: - validation_exception: exception to be raised. - Raises: - ValueError: Value error with args info. - """ - raise ValueError(*args)