Skip to content

Commit

Permalink
feat: add Pearson engine client
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-canon committed Jul 29, 2024
1 parent 18f1fbc commit 0d3dac5
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 6 deletions.
2 changes: 1 addition & 1 deletion eox_nelp/api_clients/authenticators.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def authenticate(self, api_client):
if not headers:
client = BackendApplicationClient(client_id=api_client.client_id)
oauth = OAuth2Session(client_id=api_client.client_id, client=client)
authenticate_url = f"{api_client.base_url}/oauth/token"
authenticate_url = f"{api_client.base_url}/{api_client.authentication_path}"
response = oauth.fetch_token(
token_url=authenticate_url,
client_secret=api_client.client_secret,
Expand Down
1 change: 1 addition & 0 deletions eox_nelp/api_clients/futurex.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class FuturexApiClient(AbstractAPIRestClient):
def __init__(self):
self.client_id = getattr(settings, "FUTUREX_API_CLIENT_ID")
self.client_secret = getattr(settings, "FUTUREX_API_CLIENT_SECRET")
self.authentication_path = "oauth/token/"

super().__init__()

Expand Down
141 changes: 141 additions & 0 deletions eox_nelp/api_clients/pearson_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""Client module for Pearson Engine API integration.
Classes:
PearsonEngineApiClient: Base class to interact with Pearson Engine services.
"""
from django.conf import settings

from eox_nelp.api_clients import AbstractAPIRestClient
from eox_nelp.api_clients.authenticators import Oauth2Authenticator


class PearsonEngineApiClient(AbstractAPIRestClient):
"""
Client to interact with Pearson Engine API for importing candidate demographics
and exam authorization data.
Attributes:
client_id (str): The client ID for Pearson Engine API.
client_secret (str): The client secret for Pearson Engine API.
authentication_path (str): The path for authentication.
"""
authentication_class = Oauth2Authenticator

def __init__(self):
self.client_id = getattr(settings, "PEARSON_ENGINE_API_CLIENT_ID")
self.client_secret = getattr(settings, "PEARSON_ENGINE_API_CLIENT_SECRET")
self.authentication_path = "oauth2/token/"

super().__init__()

@property
def base_url(self):
"""Return the base URL for Pearson Engine API."""
return getattr(settings, "PEARSON_ENGINE_API_URL")

def _get_user_data(self, user):
"""
Retrieve user data for the request payload.
Args:
user: The user object containing user data.
Returns:
dict: The user data formatted for the request.
"""
return {
"username": user.username,
"first_name": user.first_name,
"last_name": user.last_name,
"email": user.email,
"country": user.profile.country.code,
"city": user.profile.city,
"phone": user.profile.phone_number,
"address": user.profile.mailing_address,
"arabic_first_name": user.extrainfo.arabic_first_name,
"arabic_last_name": user.extrainfo.arabic_last_name,
}

def _get_platform_data(self):
"""
Retrieve platform data for the request payload.
Returns:
dict: The platform data formatted for the request.
"""
return {
"name": settings.PLATFORM_NAME,
"tenant": getattr(settings, "EDNX_TENANT_DOMAIN", None)
}

def _get_exam_data(self, exam_id):
"""
Retrieve exam data for the request payload.
Args:
exam_id: The external key for the exam.
Returns:
dict: The exam data formatted for the request.
"""
return {
"external_key": exam_id,
}

def import_candidate_demographics(self, user):
"""
Import candidate demographics into Pearson Engine.
Args:
user: The user object containing user data.
Returns:
dict: The response from Pearson Engine API.
"""
path = "rti/api/v1/candidate-demographics/"

candidate = {
"user_data": self._get_user_data(user),
"platform_data": self._get_platform_data(),
}

return self.make_post(path, candidate)

def import_exam_authorization(self, user, exam_id):
"""
Import exam authorization data into Pearson Engine.
Args:
user: The user object containing user data.
exam_id: The external key for the exam.
Returns:
dict: The response from Pearson Engine API.
"""
path = "rti/api/v1/exam-authorization/"
exam_data = {
"user_data": {"username": user.username},
"exam_data": self._get_exam_data(exam_id)
}

return self.make_post(path, exam_data)

def real_time_import(self, user, exam_id):
"""
Perform a real-time import of exam authorization data.
Args:
user: The user object containing user data.
exam_id: The external key for the exam.
Returns:
dict: The response from Pearson Engine API.
"""
path = "rti/api/v1/exam-authorization/"
data = {
"user_data": self._get_user_data(user),
"exam_data": self._get_exam_data(exam_id),
"platform_data": self._get_platform_data(),
}

return self.make_post(path, data)
7 changes: 2 additions & 5 deletions eox_nelp/api_clients/tests/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
TestSOAPClientMixin: Basic tests that can be implemented by AbstractSOAPClient children.
TestPKCS12AuthenticatorMixin: Basic tests that can be implemented by PKCS12Authenticator children.
"""
from django.conf import settings
from django.core.cache import cache
from mock import Mock, patch
from oauthlib.oauth2 import MissingTokenError
Expand Down Expand Up @@ -279,16 +278,14 @@ def test_successful_authentication(self, oauth2_session_mock):
"expires_in": 200,
}
oauth2_session_mock.return_value.fetch_token = fetch_token_mock
authentication_url = f"{settings.FUTUREX_API_URL}/oauth/token"
client_secret = settings.FUTUREX_API_CLIENT_SECRET

api_client = self.api_class()

self.assertTrue(hasattr(api_client, "session"))
self.assertTrue("Authorization" in api_client.session.headers)
fetch_token_mock.assert_called_with(
token_url=authentication_url,
client_secret=client_secret,
token_url=f"{api_client.base_url}/{api_client.authentication_path}",
client_secret=api_client.client_secret,
include_client_id=True,
)

Expand Down
149 changes: 149 additions & 0 deletions eox_nelp/api_clients/tests/test_pearson_engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"""
Test suite for PearsonEngineApiClient.
This module contains unit tests for the PearsonEngineApiClient class, which
integrates with Pearson Engine services. The tests cover the main methods
import_candidate_demographics, import_exam_authorization, and real_time_import
to ensure they work as expected.
Classes:
TestPearsonEngineApiClient: Unit test class for PearsonEngineApiClient.
"""
import unittest
from unittest.mock import patch

import requests
from django_countries.fields import Country

from eox_nelp.api_clients.pearson_engine import PearsonEngineApiClient
from eox_nelp.api_clients.tests.mixins import TestOauth2AuthenticatorMixin, TestRestApiClientMixin


class TestPearsonEngineApiClient(TestRestApiClientMixin, TestOauth2AuthenticatorMixin, unittest.TestCase):
"""
Test suite for PearsonEngineApiClient.
This class tests the methods of PearsonEngineApiClient, including
import_candidate_demographics, import_exam_authorization, and real_time_import.
"""

def setUp(self):
"""
Set up the test environment.
This method initializes the API client class for testing.
"""
self.api_class = PearsonEngineApiClient

@patch.object(PearsonEngineApiClient, "make_post")
@patch.object(PearsonEngineApiClient, "_authenticate")
def test_import_candidate_demographics(self, auth_mock, post_mock):
"""
Test import_candidate_demographics API call.
Expected behavior:
- Response is the expected value.
- make_post is called with the correct path and payload.
"""
auth_mock.return_value = requests.Session()
expected_value = {
"status": {"success": True},
}
post_mock.return_value = expected_value

user = self._create_test_user()
api_client = self.api_class()

response = api_client.import_candidate_demographics(user)

self.assertDictEqual(response, expected_value)
# pylint: disable=protected-access
post_mock.assert_called_with("rti/api/v1/candidate-demographics/", {
"user_data": api_client._get_user_data(user),
"platform_data": api_client._get_platform_data()
})

@patch.object(PearsonEngineApiClient, "make_post")
@patch.object(PearsonEngineApiClient, "_authenticate")
def test_import_exam_authorization(self, auth_mock, post_mock):
"""
Test import_exam_authorization API call.
Expected behavior:
- Response is the expected value.
- make_post is called with the correct path and payload.
"""
auth_mock.return_value = requests.Session()
expected_value = {
"status": {"success": True, "message": "successful", "code": 1}
}
post_mock.return_value = expected_value
user = self._create_test_user()
exam_id = "exam-123"
api_client = self.api_class()

response = api_client.import_exam_authorization(user, exam_id)

self.assertDictEqual(response, expected_value)
post_mock.assert_called_with("rti/api/v1/exam-authorization/", {
"user_data": {"username": user.username},
"exam_data": api_client._get_exam_data(exam_id) # pylint: disable=protected-access
})

@patch.object(PearsonEngineApiClient, "make_post")
@patch.object(PearsonEngineApiClient, "_authenticate")
def test_real_time_import(self, auth_mock, post_mock):
"""
Test real_time_import API call.
Expected behavior:
- Response is the expected value.
- make_post is called with the correct path and payload.
"""
auth_mock.return_value = requests.Session()
expected_value = {
"status": {"success": True, "message": "successful", "code": 1}
}
post_mock.return_value = expected_value
user = self._create_test_user()
exam_id = "exam-123"
api_client = self.api_class()

response = api_client.real_time_import(user, exam_id)

self.assertDictEqual(response, expected_value)
# pylint: disable=protected-access
post_mock.assert_called_with("rti/api/v1/exam-authorization/", {
"user_data": api_client._get_user_data(user),
"exam_data": api_client._get_exam_data(exam_id),
"platform_data": api_client._get_platform_data()
})

def _create_test_user(self):
"""
Create a mock user for testing.
Returns:
user: A mock user object with necessary attributes.
"""
# pylint: disable=missing-class-docstring
class MockUser:
username = "testuser"
first_name = "Test"
last_name = "User"
email = "[email protected]"

class Profile:
country = Country("US")
city = "New York"
phone_number = "+1234567890"
mailing_address = "123 Test St"

class ExtraInfo:
arabic_first_name = "اختبار"
arabic_last_name = "مستخدم"

profile = Profile()
extrainfo = ExtraInfo()

return MockUser()
6 changes: 6 additions & 0 deletions eox_nelp/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ def plugin_settings(settings): # pylint: disable=function-redefined
settings.PEARSON_RTI_WSDL_PASSWORD = "12345678p"
settings.PEARSON_RTI_WSDL_CLIENT_ID = "12345678"

settings.PEARSON_ENGINE_API_URL = 'https://testing.com'
settings.PEARSON_ENGINE_API_CLIENT_SECRET = "12345678p"
settings.PEARSON_ENGINE_API_CLIENT_ID = "12345678"


PLATFORM_NAME = "Testing environment"

SETTINGS = SettingsClass()
plugin_settings(SETTINGS)
Expand Down

0 comments on commit 0d3dac5

Please sign in to comment.