Skip to content

Commit

Permalink
feat: add validate cdd and ead pipes
Browse files Browse the repository at this point in the history
Also added error_validation_pipeline
  • Loading branch information
johanseto committed Jun 19, 2024
1 parent a3c686e commit 8e654ba
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 164 deletions.
79 changes: 79 additions & 0 deletions eox_nelp/pearson_vue/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
from eox_nelp.pearson_vue.constants import PAYLOAD_CDD, PAYLOAD_EAD, PAYLOAD_PING_DATABASE
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__)

Expand Down Expand Up @@ -379,3 +388,73 @@ def build_ead_request(
return {
"ead_request": ead_request
}


def audit_pipe_error(*args, **kwargs):
"""
Method to save an error with eox-audit.
Args:
*args, **kwargs
Logs:
LogError: Log everything with name error.
Returns:
None
"""
@audit_method(action="Error Pearson Vue save audit data")
def audit_exception(*args, **kwargs):
raise ValueError(*args, kwargs)

try:
audit_exception(*args, **kwargs)
except ValueError:
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.
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:
return {
"launch_validation_error_pipeline": {
"validation_exception": validation_exception.json()
}
}

return None

def validate_ead_request(ead_request, **kwargs): # pylint: disable=unused-argument
"""
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:
return {
"launch_validation_error_pipeline": {
"validation_exception": validation_exception.json()
}
}

return None
18 changes: 18 additions & 0 deletions eox_nelp/pearson_vue/rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
Classes:
RealTimeImport: Class for managing RTI operations and executing the pipeline.
"""
import importlib

from eox_nelp.pearson_vue.pipeline import (
audit_pipe_error,
build_cdd_request,
build_ead_request,
check_service_availability,
Expand Down Expand Up @@ -53,6 +56,11 @@ 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"):
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)
break

def get_pipeline(self):
"""
Expand Down Expand Up @@ -97,3 +105,13 @@ def get_pipeline(self):
check_service_availability,
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.
"""
return [
audit_pipe_error,
]
21 changes: 21 additions & 0 deletions eox_nelp/pearson_vue/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from eox_nelp.pearson_vue.rti_backend import (
CandidateDemographicsDataImport,
ExamAuthorizationDataImport,
ErrorValidationDataImport,
RealTimeImport,
)

Expand Down Expand Up @@ -72,3 +73,23 @@ def cdd_task(self, pipeline_index=0, **kwargs):
cdd.run_pipeline()
except Exception as exc: # pylint: disable=broad-exception-caught
self.retry(exc=exc, kwargs=cdd.backend_data)


@shared_task(bind=True)
def error_validation_task(self, pipeline_index=0, **kwargs):
"""
Performs an asynchronous call to Pearson VUE's CDD task (Candidate Demographics Data) service.
This task initiates the real-time import process using the provided pipeline index and optional keyword arguments.
Args:
self: The Celery task instance.
pipeline_index (int): The index of the pipeline to be executed (default is 0).
**kwargs: Additional keyword arguments to configure the RTI service.
"""
error_validation = ErrorValidationDataImport(pipeline_index=pipeline_index, **kwargs.copy())

try:
error_validation.run_pipeline()
except Exception as exc: # pylint: disable=broad-exception-caught
self.retry(exc=exc, kwargs=error_validation.backend_data)
163 changes: 163 additions & 0 deletions eox_nelp/pearson_vue/tests/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
from django.test import override_settings
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,
build_cdd_request,
build_ead_request,
check_service_availability,
Expand Down Expand Up @@ -688,3 +691,163 @@ def test_build_ead_request(self, mock_now):
result = build_ead_request(**input_data)

self.assertDictEqual(expected_output, result)


@ddt
class TestValidateCddRequest(unittest.TestCase):
"""
Unit tests for the validate_cdd_request method.
"""

def setUp(self):
"""
Set up the test environment.
"""
self.cdd_request = {
"@clientCandidateID": "NELC12345",
"@clientID": "12345678",
"candidateName": {"firstName": "John", "lastName": "Doe"},
"lastUpdate": "2023/05/20 12:00:00 GMT",
"primaryAddress": {
"address1": "123 Main St",
"city": "Anytown",
"country": "US",
"mobile": {"mobileCountryCode": "1", "mobileNumber": "5551234567"},
"nativeAddress": {
"address1": "123 Main St",
"city": "Anytown",
"firstName": "فلان الفلاني",
"language": "UKN",
"lastName": "فلان الفلاني",
"potentialMismatch": "false",
},
"phone": {"phoneCountryCode": "1", "phoneNumber": "5551234567"},
},
"webAccountInfo": {"email": "[email protected]"},
}

@data(
{"@clientCandidateID": ""},
{"@clientID": ""},
{"candidateName": {"firstName": ""}},
{"candidateName": {"lastName": ""}},
{"lastUpdate": ""},
{"primaryAddress": {"address1": ""}},
{"primaryAddress": {"city": ""}},
{"primaryAddress": {"country": ""}},
{"primaryAddress": {"mobile": {"mobileCountryCode": ""}}},
{"primaryAddress": {"mobile": {"mobileNumber": ""}}},
{"primaryAddress": {"nativeAddress": {"address1": ""}}},
{"primaryAddress": {"nativeAddress": {"city": ""}}},
{"primaryAddress": {"nativeAddress": {"firstName": ""}}},
{"primaryAddress": {"nativeAddress": {"language": ""}}},
{"primaryAddress": {"nativeAddress": {"lastName": ""}}},
{"primaryAddress": {"nativeAddress": {"potentialMismatch": ""}}},
{"primaryAddress": {"phone": {"phoneCountryCode": ""}}},
{"primaryAddress": {"phone": {"phoneNumber": ""}}},
{"webAccountInfo": {"email": ""}},
)
def test_wrong_cdd_request(self, wrong_update):
"""Test validator with a wrong cdd_request updating with empty string
different keys.
Expected behavior:
- The result is the expected value.(with key launch_validation_error_pipeline)
- launch_validation_error_pipeline key is truthy
"""
wrong_cdd_request = deep_update(self.cdd_request, wrong_update)

result = validate_cdd_request(wrong_cdd_request)

self.assertIn("launch_validation_error_pipeline", result)
self.assertTrue(bool(result["launch_validation_error_pipeline"]))

def test_correct_cdd_request(self):
"""Test validator with correct cdd_request.
Expected behavior:
- The result is the expected value.
"""
result = validate_cdd_request(self.cdd_request)

self.assertIsNone(result)


@ddt
class TestValidateEadRequest(unittest.TestCase):
"""
Unit tests for the validate_ead_request method.
"""

def setUp(self):
"""
Set up the test environment.
"""
self.ead_request = {
'@authorizationTransactionType': 'Add',
'@clientAuthorizationID': '12345678954',
'@clientID': '12345678',
'clientCandidateID': 'NELC12345',
'eligibilityApptDateFirst': '2024/07/15 11:59:59',
'eligibilityApptDateLast': '2025/07/15 11:59:59',
'examAuthorizationCount': 3,
'examSeriesCode': 'ABC',
'lastUpdate': '2023/05/20 12:00:00 GMT',
}

@data(
{"@authorizationTransactionType": ""},
{"@clientAuthorizationID": ""},
{"@clientID": ""},
{"clientCandidateID": ""},
{"eligibilityApptDateFirst": ""},
{"eligibilityApptDateLast": ""},
{"examAuthorizationCount": ""},
{"examSeriesCode": ""},
{"lastUpdate": ""},
)
def test_wrong_ead_request(self, wrong_update):
"""Test validator with a wrong ead_request updating with empty string
different keys.
Expected behavior:
- The result is the expected value.(with key launch_validation_error_pipeline)
- launch_validation_error_pipeline key is truthy
"""
wrong_ead_request = deep_update(self.ead_request, wrong_update)

result = validate_ead_request(wrong_ead_request)

self.assertIn("launch_validation_error_pipeline", result)
self.assertTrue(bool(result["launch_validation_error_pipeline"]))

def test_correct_ead_request(self):
"""Test validator with correct ead_request.
Expected behavior:
- The result is the expected value.
"""
result = validate_ead_request(self.ead_request)

self.assertIsNone(result)


class TestAuditPipeError(unittest.TestCase):
"""
Unit tests for the audit_pipe_error method.
"""

def test_audit_pipe_error(self):
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}"
]
self.assertListEqual(log_error, logs.output)
7 changes: 7 additions & 0 deletions eox_nelp/pearson_vue/tests/test_rti_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from eox_nelp.pearson_vue.rti_backend import (
CandidateDemographicsDataImport,
ErrorValidationDataImport,
ExamAuthorizationDataImport,
RealTimeImport,
)
Expand Down Expand Up @@ -165,3 +166,9 @@ class TestCandidateDemographicsDataImport(TestRealTimeImport):
Unit tests for the rti_backend class.
"""
rti_backend_class = CandidateDemographicsDataImport

class TestErrorValidationDataImport(TestRealTimeImport):
"""
Unit tests for the rti_backend class.
"""
rti_backend_class = ErrorValidationDataImport
9 changes: 8 additions & 1 deletion eox_nelp/pearson_vue/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
from eox_nelp.pearson_vue.tasks import cdd_task, ead_task, real_time_import_task, error_validation_task


class TestRealTimeImportTask(unittest.TestCase):
Expand Down Expand Up @@ -86,3 +86,10 @@ 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.
"""
import_class_patch = "eox_nelp.pearson_vue.tasks.ErrorValidationDataImport"
import_task_function = error_validation_task
Loading

0 comments on commit 8e654ba

Please sign in to comment.