diff --git a/eox_nelp/api_clients/__init__.py b/eox_nelp/api_clients/__init__.py deleted file mode 100644 index 7f872f64..00000000 --- a/eox_nelp/api_clients/__init__.py +++ /dev/null @@ -1,154 +0,0 @@ -"""This file contains the common functions and classes for the api_clients module. - -Classes: - AbstractApiClient: Base API class. -""" -import logging -from abc import ABC, abstractmethod - -from django.conf import settings - -from eox_nelp.api_clients.authenticators import UnAuthenticatedAuthenticator - -LOGGER = logging.getLogger(__name__) - - -class AbstractApiClient(ABC): - """Abstract api client class, this defines common API client methods.""" - - authentication_class = UnAuthenticatedAuthenticator - extra_headers_key = None - - @property - @abstractmethod - def base_url(self): - """Abstract base_url property method.""" - raise NotImplementedError - - def __init__(self): - """ - Abstract ApiClient creator, this will set the session based on the authenticate result. - """ - self.session = self._authenticate() - self.session.headers.update(self._get_extra_headers()) - - def _authenticate(self): - """Calls the authenticator's authenticate method""" - authenticator = self.authentication_class() - - return authenticator.authenticate(api_client=self) - - def _get_extra_headers(self): - """This verify the extra_headers_key attribute and returns its value from the django settings. - - Returns - Dict: The extra_headers_key must be set a dictionary. - """ - if self.extra_headers_key: - return getattr(settings, self.extra_headers_key, {}) - - return {} - - -class AbstractAPIRestClient(AbstractApiClient): - """This abstract class is an extension of AbstractApiClient that includes common http methods (POST and GET) - based on the REST API standard. - """ - - def make_post(self, path, data): - """This method uses the session attribute to perform a POST request based on the - base_url attribute and the given path, if the response has a status code 200 - this will return the json from that response otherwise this will return an empty dictionary. - - Args: - path: makes reference to the url path. - data: request body as dictionary. - - Return: - Dictionary: Empty dictionary or json response. - """ - url = f"{self.base_url}/{path}" - - response = self.session.post(url=url, json=data) - - if response.ok: - return response.json() - - LOGGER.error( - "An error has occurred trying to make post request to %s with status code %s and message %s", - url, - response.status_code, - response.json(), - ) - - return { - "error": True, - "message": f"Invalid response with status {response.status_code}" - } - - def make_get(self, path, payload): - """This method uses the session attribute to perform a GET request based on the - base_url attribute and the given path, if the response has a status code 200 - this will return the json from that response otherwise this will return an empty dictionary. - - Args: - path: makes reference to the url path. - payload: queryparams as dictionary. - - Return: - Dictionary: Empty dictionary or json response. - """ - url = f"{self.base_url}/{path}" - - response = self.session.get(url=url, params=payload) - - if response.ok: - return response.json() - - LOGGER.error( - "An error has occurred trying to make a get request to %s with status code %s and message %s", - url, - response.status_code, - response.json(), - ) - - return { - "error": True, - "message": f"Invalid response with status {response.status_code}" - } - - -class AbstractSOAPClient(AbstractApiClient): - """This abstract class is an extension of AbstractApiClient that includes - a common POST method whose expected result is a xml response. - """ - - def make_post(self, path, data): - """This method uses the session attribute to perform a POST request based on the - base_url attribute and the given path, if the status code is different from 200 this - will log the error and finally this will return the xml response in any case. - - Arguments: - path : makes reference to the url path. - data : request body as xml string. - - Return: - : xml response. - """ - if not isinstance(data, str): - raise TypeError("Invalid data type, the data argument must be a string") - - url = f"{self.base_url}/{path}" - - response = self.session.post(url=url, data=data.encode("utf-8")) - content = response.text - - if not response.ok: - LOGGER.error( - "An error has occurred trying to make post request to %s with status code %s and message %s", - url, - response.status_code, - content, - ) - - return content diff --git a/eox_nelp/api_clients/authenticators.py b/eox_nelp/api_clients/authenticators.py deleted file mode 100644 index 58e042e3..00000000 --- a/eox_nelp/api_clients/authenticators.py +++ /dev/null @@ -1,163 +0,0 @@ -""" -This module contains classes for authenticating users using various authentication methods. - -Classes: - BasicAuthenticator: - A class for authenticating users using Basic Authentication with a username and password. - - Oauth2Authenticator: - A class for authenticating users using OAuth 2.0 with client ID and client secret. - - UnAuthenticatedAuthenticator: - A class for unauthenticated request. - - PKCS12Authenticator: - A class for authenticating users using PFX certificate. -""" -from abc import ABC, abstractmethod - -import requests -from django.core.cache import cache -from oauthlib.oauth2 import BackendApplicationClient -from requests.auth import HTTPBasicAuth -from requests_oauthlib import OAuth2Session -from requests_pkcs12 import Pkcs12Adapter - - -class AbstractAuthenticator(ABC): - """This class define required methods that an autheticator class must have.""" - - @abstractmethod - def authenticate(self, api_client): - """Abstract method that should return a requests Session.""" - raise NotImplementedError - - -class Oauth2Authenticator(AbstractAuthenticator): - """ Oauth2Authenticator is a class for authenticating users using - the OAuth 2.0 standard with client ID and client secret. - """ - - def authenticate(self, api_client): - """Authenticate the session with OAuth 2.0 credentials. - - This method uses OAuth 2.0 client credentials (client ID and client secret) - to obtain an access token from the OAuth token endpoint. The access token - is then used to create and configure a requests session. - - The access token is cached to minimize token requests to the OAuth server. - - Returns: - requests.Session: Session authenticated with OAuth 2.0 credentials. - """ - # pylint: disable=no-member - key = f"{api_client.client_id}-{api_client.client_secret}" - headers = cache.get(key) - - 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}/{api_client.authentication_path}" - response = oauth.fetch_token( - token_url=authenticate_url, - client_secret=api_client.client_secret, - include_client_id=True, - ) - headers = { - "Authorization": f"{response.get('token_type')} {response.get('access_token')}" - } - - cache.set(key, headers, response.get("expires_in", 300)) - - session = requests.Session() - session.headers.update(headers) - - return session - - -class BasicAuthAuthenticator(AbstractAuthenticator): - """ BasicAuthenticator is a class for authenticating users - using Basic Authentication with a username and password. - """ - - def authenticate(self, api_client): - """Authenticate the session with the user and password. - - Creates and configures a requests session with basic authentication - provided by the user and password. - - Returns: - requests.Session: Session authenticated. - """ - # pylint: disable=no-member - session = requests.Session() - session.auth = HTTPBasicAuth(api_client.user, api_client.password) - - return session - - -class UnAuthenticatedAuthenticator(AbstractAuthenticator): - """This authenticator class doesn't implement any authentication method.""" - - def authenticate(self, api_client): - """Creates and configures a requests session without authentication. - - Returns: - requests.Session: Basic Session. - """ - # pylint: disable=no-member - return requests.Session() - - -class PKCS12Authenticator(AbstractAuthenticator): - """PKCS12Authenticator is a class for authenticating users - using a PFX certificate and its passphrase. - """ - - def authenticate(self, api_client): - """Creates and configures a requests session with a specific certificate. - - Returns: - requests.Session: Basic Session. - """ - session = requests.Session() - session.mount( - api_client.base_url, - Pkcs12Adapter(pkcs12_filename=api_client.cert, pkcs12_password=api_client.passphrase), - ) - - return session - - -class Oauth2BasicAuthenticator(BasicAuthAuthenticator): - """Authenticator for custom use using basic auth to get a Oauth2 Token (Bearer or JWT). - Token_type on depends of the response used after the oauth2 token request. - Then the token is used for the next requests. - """ - - def authenticate(self, api_client): - """Authenticate the session with basic auth in order to get token(Bearer or JWT). - Then the token is added to a new session Headers. - Is needed the user, password and token_path class atrributes to the get oauth2 token, - based on the client configuration. - """ - auth_session = super().authenticate(api_client) - key = f"oauth2-basic-{api_client.user}-{api_client.password}" - headers = cache.get(key) - - if not headers: - authenticate_url = f"{api_client.base_url}/{api_client.token_path}" - response = auth_session.post( - url=authenticate_url, - data={"grant_type": "client_credentials", "scope": "notification"} - ).json() - headers = { - "Authorization": f"{response.get('token_type')} {response.get('access_token')}" - } - - cache.set(key, headers, int(response.get("expires_in", 300))) - - session = requests.Session() - session.headers.update(headers) - - return session diff --git a/eox_nelp/api_clients/certificates.py b/eox_nelp/api_clients/certificates.py deleted file mode 100644 index b6aa615e..00000000 --- a/eox_nelp/api_clients/certificates.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Client module for external certificate's API integration. - -Classes: - ExternalCertificatesApiClient: Class to interact with NELP external certificates service. -""" -from django.conf import settings - -from eox_nelp.api_clients import AbstractAPIRestClient -from eox_nelp.api_clients.authenticators import BasicAuthAuthenticator - -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 - - -class ExternalCertificatesApiClient(AbstractAPIRestClient): - """Allow to perform multiple external certificates operations.""" - - authentication_class = BasicAuthAuthenticator - extra_headers_key = "EXTERNAL_CERTIFICATES_EXTRA_HEADERS" - - def __init__(self): - self.user = getattr(settings, "EXTERNAL_CERTIFICATES_USER") - self.password = getattr(settings, "EXTERNAL_CERTIFICATES_PASSWORD") - - super().__init__() - - @property - def base_url(self): - return getattr(settings, "EXTERNAL_CERTIFICATES_API_URL") - - def create_external_certificate(self, certificate_data): - """This will create an external certificate based on the input data, this data should have the - following keys: - - id : edx-platform certificate identifier. - created_at : when the certificate was created. - expiration_date : when the certificate expires. - grade : The associated grade with the certificate. - is_passing : Boolean value that represent if the user has passed the course. - group_code ,mandatory>: String, this is a value provided by the client. - user : Dictionary with the following data: - national_id: User National identifier. - englishs_name : User name in English. - arabic_name : User name in Arabic. - Args: - certificate_data: Information about a the certificate. - - Returns: - response: requests response as dictionary. - - Raise: - KeyError: This will be raised when the mandatory are excluded in the certificate data. - """ - @audit_method(action="Create External Certificate") - def create_external_certificate_request(certificate_data): - """This is a wrapper that allows to make audit-able the create_external_certificate method.""" - path = "Certificates" - user = certificate_data["user"] - payload = { - "reference_id": certificate_data["reference_id"], - "date": { - "issuance": certificate_data["created_at"], - "expiration": None, - }, - "individual": { - "name_en": user["english_name"], - "name_ar": user["arabic_name"], - "id": user["national_id"], - "id_type": "saudi", - }, - "group_code": certificate_data["group_code"], - "certificate_type": "completion", # What types do we have ? - "score": { - "value": certificate_data["grade"], - "type": "percentage" - }, - "metadata": getattr(settings, "EXTERNAL_CERTIFICATES_METADATA", {}), - } - return self.make_post(path, payload) - - return create_external_certificate_request(certificate_data) diff --git a/eox_nelp/api_clients/futurex.py b/eox_nelp/api_clients/futurex.py deleted file mode 100644 index d43d9361..00000000 --- a/eox_nelp/api_clients/futurex.py +++ /dev/null @@ -1,68 +0,0 @@ -"""Client module for Futurex API integration. - -Classes: - FuturexApiClient: Base class to interact with Futurex services. - FuturexMissingArguments: Exception used for indicate that some required arguments are missing. -""" -from django.conf import settings - -from eox_nelp.api_clients import AbstractAPIRestClient -from eox_nelp.api_clients.authenticators import Oauth2Authenticator - - -class FuturexApiClient(AbstractAPIRestClient): - """Allow to perform multiple Futurex API operations based on an authenticated session. - - Attributes: - base_url: Futurex domain. - session: persist certain parameters across requests. - """ - authentication_class = Oauth2Authenticator - - 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__() - - @property - def base_url(self): - return getattr(settings, "FUTUREX_API_URL") - - def enrollment_progress(self, enrollment_data): - """Push the user progress across for a course. This data will affect the learner profile - in FutureX according to their progress. - - Args: - enrollment_data: Information about a specific progress. - - Returns: - response: requests response as dictionary. - - Raise: - Basic exception when the enrollment data is not completed. - """ - path = "enrollment-progress" - required_fields = set([ - "courseId", - "userId", - "approxTotalCourseHrs", - "overallProgress", - "membershipState", - "enrolledAt", - "isCompleted", - ]) - - enrollment_data_keys = set(enrollment_data.keys()) - - if required_fields <= enrollment_data_keys: - return self.make_post(path, enrollment_data) - - raise FuturexMissingArguments( - f"The following arguments are missing {str(required_fields - enrollment_data_keys)}", - ) - - -class FuturexMissingArguments(ValueError): - """This exception indicates that one or more required arguments are missing""" diff --git a/eox_nelp/api_clients/mt.py b/eox_nelp/api_clients/mt.py deleted file mode 100644 index 3e3c0d8a..00000000 --- a/eox_nelp/api_clients/mt.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Client module for minister of tourism API integration. - -Classes: - MinisterOfTourismApiClient: Class to interact with Minister of tourism external service. -""" -from django.conf import settings - -from eox_nelp.api_clients import AbstractAPIRestClient -from eox_nelp.api_clients.authenticators import BasicAuthAuthenticator - -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 - - -class MinisterOfTourismApiClient(AbstractAPIRestClient): - """Allow to perform multiple external certificates operations.""" - - authentication_class = BasicAuthAuthenticator - - def __init__(self): - self.user = getattr(settings, "MINISTER_OF_TOURISM_USER") - self.password = getattr(settings, "MINISTER_OF_TOURISM_PASSWORD") - - super().__init__() - - @property - def base_url(self): - return getattr(settings, "MINISTER_OF_TOURISM_API_URL") - - def update_training_stage(self, course_id, national_id, stage_result): - """This updates the training stage, changes the status to pass or fail, and stores them in HCD - database. If the training stage is already updated or some parameters are missing or wrong, an error - message is returned. - - Arguments: - course_id: Course identifier as string, e.g, course-v1:edx+cos104+T4_2023 - national_id: User national identifier, e.g. 1087583085 - stage_result: Representation of pass or fail result, 1 for pass 2 for fail. - - Returns: - response: requests response as dictionary. - """ - @audit_method(action="Publish MT course completion stage") - def update_training_stage_request(course_id, national_id, stage_result): - """This is a wrapper that allows to make audit-able the update_training_stage method.""" - path = "api/v2/LMSCourse/UpdateTrainingStage" - payload = { - "courseLMSId": course_id, - "userNationalId": national_id, - "applicationStageResult": stage_result, - } - return self.make_post(path, payload) - - return update_training_stage_request(course_id, national_id, stage_result) diff --git a/eox_nelp/api_clients/pearson_engine.py b/eox_nelp/api_clients/pearson_engine.py deleted file mode 100644 index d6043b85..00000000 --- a/eox_nelp/api_clients/pearson_engine.py +++ /dev/null @@ -1,149 +0,0 @@ -"""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. - """ - user_data = { - "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_full_name": user.extrainfo.arabic_name, - "arabic_first_name": user.extrainfo.arabic_first_name, - "arabic_last_name": user.extrainfo.arabic_last_name, - } - - if user.extrainfo.national_id: - user_data["national_id"] = user.extrainfo.national_id - - return user_data - - 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, **kwargs): - """ - 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/" - data = { - "user_data": self._get_user_data(user), - "platform_data": self._get_platform_data(), - **kwargs - } - - return self.make_post(path, data) - - def import_exam_authorization(self, user, exam_id, **kwargs): - """ - 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/" - data = { - "user_data": {"username": user.username}, - "exam_data": self._get_exam_data(exam_id), - **kwargs - } - - return self.make_post(path, data) - - def real_time_import(self, user, exam_id, **kwargs): - """ - 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/real-time-import/" - data = { - "user_data": self._get_user_data(user), - "exam_data": self._get_exam_data(exam_id), - "platform_data": self._get_platform_data(), - **kwargs - } - - return self.make_post(path, data) diff --git a/eox_nelp/api_clients/pearson_rti.py b/eox_nelp/api_clients/pearson_rti.py deleted file mode 100644 index 3328bd5b..00000000 --- a/eox_nelp/api_clients/pearson_rti.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Client module for Pearson VUE RTI service. - -Classes: - PearsonRTIApiClient: Class to interact with the pearson vue Real-Time Import external service. -""" -import logging - -from bs4 import BeautifulSoup -from django.conf import settings - -from eox_nelp.api_clients import AbstractSOAPClient -from eox_nelp.api_clients.authenticators import PKCS12Authenticator - -LOGGER = logging.getLogger(__name__) - - -class PearsonRTIApiClient(AbstractSOAPClient): - """This class implements the required method to consume APIs available in the Pearson VUE systems - for importing candidate demographic and exam authorization data. - """ - authentication_class = PKCS12Authenticator - - def __init__(self): - self.cert = getattr(settings, "PEARSON_RTI_CERT") - self.passphrase = getattr(settings, "PEARSON_RTI_PASSPHRASE") - - super().__init__() - - @property - def base_url(self): - return getattr(settings, "PEARSON_RTI_WSDL_URL") - - def ping(self, payload): - """The Ping service allows to ping the VUE system to verify that web services are available. - This allows to validate the credentials(ping_database) if they are included into the payload. - - Arguments: - payload: Body xml request in string format. - - Returns: - : Parsed xml response to Dict format. - """ - result = { - "status": "failed", - } - xml_response = BeautifulSoup( - self.make_post("cxfws2/services/Ping", payload), - "xml", - ) - request_result = xml_response.find("result") - - if request_result and "status" in request_result.attrs: - result["status"] = request_result.attrs["status"] - - return result - - def import_candidate_demographics(self, payload): - """The CDD service imports candidate demographic data in to the VUE system. This - method performs the required action to store the data into the Pearson service. - - Arguments: - payload: Body xml request in string format. - - Returns: - : Parsed xml response to Dict format. - """ - xml_response = BeautifulSoup( - self.make_post("cxfws2/services/CDDService", payload), - "xml", - ) - fault = xml_response.find("soapenv:Fault") - - # There are multiple kind of errors, some of them generates a soapenv:Fault section and others - # generates an status error section, the following condition handles the first case. - if fault: - return { - "status": "error", - "fault_code": xml_response.find("faultcode").text, - "message": xml_response.find("faultstring").text, - } - - status = xml_response.find("status") - - if status: - return { - "status": status.text.lower(), - "message": xml_response.find("message").text, - "candidate_id": xml_response.find("cdd:cddResponse").attrs.get("candidateID"), - "client_candidate_id": xml_response.find("cdd:cddResponse").attrs.get("clientCandidateID"), - } - - LOGGER.error( - "An unexpected error has occurred trying to make a CDD request getting the following response: %s", - xml_response, - ) - - return { - "status": "unexpected error", - "response": xml_response, - } - - def import_exam_authorization(self, payload): - """The EAD service imports exam authorization data into the Pearson VUE system. This - method performs the required action to store the data into the Pearson service. - - Arguments: - payload: Body xml request in string format. - - Returns: - : Parsed xml response to Dict format. - """ - xml_response = BeautifulSoup( - self.make_post("cxfws2/services/EADService", payload), - "xml", - ) - fault = xml_response.find("soapenv:Fault") - - # There are multiple kind of errors, some of them generates a soapenv:Fault section and others - # generates an status error section, the following condition handles the first case. - if fault: - return { - "status": "error", - "fault_code": xml_response.find("faultcode").text, - "message": xml_response.find("faultstring").text, - } - - status = xml_response.find("status") - - if status: - return { - "status": status.text.lower(), - "message": xml_response.find("message").text if xml_response.find("message") else "", - "client_candidate_id": xml_response.find("clientCandidateID").text, - } - - LOGGER.error( - "An unexpected error has occurred trying to make a EAD request getting the following response: %s", - xml_response, - ) - - return { - "status": "unexpected error", - "response": xml_response, - } diff --git a/eox_nelp/api_clients/sms_vendor.py b/eox_nelp/api_clients/sms_vendor.py deleted file mode 100644 index 9edf3e55..00000000 --- a/eox_nelp/api_clients/sms_vendor.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Client module for SMS vendor. - -Classes: - SMSVendorApiClient: Class to interact with SMS vendor external service. -""" -from django.conf import settings - -from eox_nelp.api_clients import AbstractAPIRestClient -from eox_nelp.api_clients.authenticators import Oauth2BasicAuthenticator - -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 - - -class SMSVendorApiClient(AbstractAPIRestClient): - """Allow to perform SMS send operations.""" - authentication_class = Oauth2BasicAuthenticator - - @property - def base_url(self): - return getattr(settings, "SMS_VENDOR_URL") - - def __init__(self): - self.user = getattr(settings, "SMS_VENDOR_USERNAME") - self.password = getattr(settings, "SMS_VENDOR_PASSWORD") - self.token_path = getattr(settings, "SMS_VENDOR_TOKEN_PATH") - - super().__init__() - - def send_sms(self, recipient, message): - """This send SMS using an external Vendor via API. - - Arguments: - recipient: phone number, destination of message. - message: message body to be sent - - Returns: - response: requests response as dictionary. - """ - @audit_method(action="Send SMS with sms_vendor API") - def send_sms_request(recipient, message): - """This is a wrapper that allows to make audit-able the send_SMS method.""" - path = getattr(settings, "SMS_VENDOR_SEND_SMS_PATH", "") - payload = { - "sms_message": message, - "recipient_number": recipient, - } - return self.make_post(path, payload) - - return send_sms_request(recipient, message) diff --git a/eox_nelp/api_clients/tests/__init__.py b/eox_nelp/api_clients/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/eox_nelp/api_clients/tests/mixins.py b/eox_nelp/api_clients/tests/mixins.py deleted file mode 100644 index a10adbd4..00000000 --- a/eox_nelp/api_clients/tests/mixins.py +++ /dev/null @@ -1,355 +0,0 @@ -"""Mixins for multiple test implementations. - -Classes: - TestRestApiClientMixin: Basic tests that can be implemented by AbstractAPIRestClient children. - TestOauth2AuthenticatorMixin: Basic tests that can be implemented by Oauth2Authenticator children. - TestBasicAuthAuthenticatorMixin: Basic tests that can be implemented by BasicAuthAuthenticator children. - TestSOAPClientMixin: Basic tests that can be implemented by AbstractSOAPClient children. - TestPKCS12AuthenticatorMixin: Basic tests that can be implemented by PKCS12Authenticator children. -""" -from django.core.cache import cache -from mock import Mock, patch -from oauthlib.oauth2 import MissingTokenError -from requests.auth import HTTPBasicAuth - -from eox_nelp import api_clients - - -class TestRestApiClientMixin: - """Basic API client tests.""" - - def tearDown(self): # pylint: disable=invalid-name - """Clear cache after each test case""" - cache.clear() - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_successful_post(self, requests_mock): - """Test case when a POST request success. - - Expected behavior: - - Response is the expected value - - POST was called with the given data and right url. - """ - response = Mock() - response.ok = True - response.status_code = 200 - expected_value = { - "status": {"success": True, "message": "successful", "code": 1} - } - response.json.return_value = expected_value - requests_mock.Session.return_value.post.return_value = response - data = {"testing": True, "application": "futurex"} - - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - response = api_client.make_post("fake/path", data) - - self.assertDictEqual(response, expected_value) - requests_mock.Session.return_value.post.assert_called_with( - url=f"{api_client.base_url}/fake/path", - json=data, - ) - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_failed_post(self, requests_mock): - """Test case when a POST request fails. - - Expected behavior: - - Response is an empty dict - - POST was called with the given data and right url. - - Error was logged. - """ - response = Mock() - response.ok = False - response.status_code = 400 - response.json.return_value = {"test": True} - requests_mock.Session.return_value.post.return_value = response - data = {"testing": True, "application": "futurex"} - log_error = ( - "An error has occurred trying to make post request to https://testing.com/fake/path with status code 400 " - f"and message {response.json()}" - ) - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - with self.assertLogs(api_clients.__name__, level="ERROR") as logs: - response = api_client.make_post("fake/path", data) - - self.assertDictEqual(response, {'error': True, 'message': 'Invalid response with status 400'}) - requests_mock.Session.return_value.post.assert_called_with( - url=f"{api_client.base_url}/fake/path", - json=data, - ) - self.assertEqual(logs.output, [ - f"ERROR:{api_clients.__name__}:{log_error}" - ]) - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_successful_get(self, requests_mock): - """Test case when a GET request success. - - Expected behavior: - - Response is the expected value - - GET was called with the given data and right url. - """ - response = Mock() - response.ok = True - response.status_code = 200 - expected_value = { - "status": {"success": True, "message": "successful", "code": 1} - } - response.json.return_value = expected_value - requests_mock.Session.return_value.get.return_value = response - params = {"format": "json"} - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - response = api_client.make_get("field-options/vocabulary/language", params) - - self.assertDictEqual(response, expected_value) - requests_mock.Session.return_value.get.assert_called_with( - url=f"{api_client.base_url}/field-options/vocabulary/language", - params=params, - ) - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_failed_get(self, requests_mock): - """Test case when a GET request fails. - - Expected behavior: - - Response is an empty dict - - GET was called with the given data and right url. - - Error was logged. - """ - response = Mock() - response.ok = False - response.status_code = 404 - response.json.return_value = {"test": True} - requests_mock.Session.return_value.get.return_value = response - params = {"format": "json"} - log_error = ( - "An error has occurred trying to make a get request to https://testing.com/fake/path with status code 404 " - f"and message {response.json()}" - ) - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - with self.assertLogs(api_clients.__name__, level="ERROR") as logs: - response = api_client.make_get("fake/path", params) - - self.assertDictEqual(response, {'error': True, 'message': 'Invalid response with status 404'}) - requests_mock.Session.return_value.get.assert_called_with( - url=f"{api_client.base_url}/fake/path", - params=params, - ) - self.assertEqual(logs.output, [ - f"ERROR:{api_clients.__name__}:{log_error}" - ]) - - -class TestSOAPClientMixin: - """Basic API client tests.""" - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_successful_post(self, requests_mock): - """Test case when a POST request success. - - Expected behavior: - - Response is the expected value - - POST was called with the given data and right url. - """ - response = Mock() - response.ok = True - response.text = " xml response string from API " - expected_value = response.text - requests_mock.Session.return_value.post.return_value = response - data = """ - - - - - - - """ - - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - response = api_client.make_post("fake/path", data) - - self.assertEqual(response, expected_value) - requests_mock.Session.return_value.post.assert_called_with( - url=f"{api_client.base_url}/fake/path", - data=data.encode("utf-8"), - ) - - @patch("eox_nelp.api_clients.authenticators.requests") - def test_failed_post(self, requests_mock): - """Test case when a POST request fails. - - Expected behavior: - - Response is the expected value. - - POST was called with the given data and right url. - - Error was logged. - """ - response = Mock() - response.ok = False - response.status_code = 400 - response.text = " xml response string from API " - expected_value = response.text - requests_mock.Session.return_value.post.return_value = response - data = """ - - - - - - - """ - - log_error = ( - "An error has occurred trying to make post request to https://testing.com/fake/path with status code 400 " - f"and message {response.text}" - ) - with patch.object(self.api_class, "_authenticate") as auth_mock: - auth_mock.return_value = requests_mock.Session() - api_client = self.api_class() - - with self.assertLogs(api_clients.__name__, level="ERROR") as logs: - response = api_client.make_post("fake/path", data) - - self.assertEqual(response, expected_value) - requests_mock.Session.return_value.post.assert_called_with( - url=f"{api_client.base_url}/fake/path", - data=data.encode("utf-8"), - ) - self.assertEqual(logs.output, [ - f"ERROR:{api_clients.__name__}:{log_error}" - ]) - - def test_invalid_data(self): - """Test that a TypeError exception is raised when the data is not and string. - - Expected behavior: - - Exception was raised - """ - data = {"testing": True, "application": "futurex"} - - with patch.object(self.api_class, "_authenticate"): - api_client = self.api_class() - - with self.assertRaises(TypeError): - api_client.make_post("fake/path", data) - - -class TestOauth2AuthenticatorMixin: - """ - This test class contains test cases for the `AbstractOauth2ApiClient` class - to ensure that the authentication process using OAuth2 is working correctly. - """ - - def test_failed_authentication(self): - """Test case for invalid credentials. - - Expected behavior: - - Raise MissingTokenError exception - """ - self.assertRaises(MissingTokenError, self.api_class) - - @patch("eox_nelp.api_clients.authenticators.OAuth2Session") - def test_successful_authentication(self, oauth2_session_mock): - """Test case when the authentication response is valid. - - Expected behavior: - - Session is set - - Session headers contains Authorization key. - - fetch_token was called with the right values. - """ - fetch_token_mock = Mock() - fetch_token_mock.return_value = { - "token_type": "Bearer", - "access_token": "12345678abc", - "expires_in": 200, - } - oauth2_session_mock.return_value.fetch_token = fetch_token_mock - - 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=f"{api_client.base_url}/{api_client.authentication_path}", - client_secret=api_client.client_secret, - include_client_id=True, - ) - - -class TestBasicAuthAuthenticatorMixin: - """ - This test class contains test cases for the `AbstractBasicAuthApiClient` class - to ensure that the authentication process using Basic Auth is working correctly. - """ - - @patch("eox_nelp.api_clients.authenticators.requests.Session") - def test_authentication_call(self, session_mock): - """ - Test the authentication call for the API client. - - This test case ensures that the `_authenticate` method of the `AbstractBasicAuthApiClient` - class sets the expected HTTP Basic authentication credentials (user and password) - in the session object. - - Expected behavior: - - Session mock is called once. - - api client has the attribute session - - Session has the right auth value. - """ - expected_auth = HTTPBasicAuth(self.user, self.password) - session_mock.return_value.auth = expected_auth - - api_client = self.api_class() - - session_mock.assert_called_once() - self.assertEqual(api_client.session, session_mock.return_value) - self.assertEqual(api_client.session.auth, expected_auth) - - -class TestPKCS12AuthenticatorMixin: - """ - This test class contains test cases for the `PKCS12Authenticator` class - to ensure that the authentication process using PFX certificate is working correctly. - """ - - @patch("eox_nelp.api_clients.authenticators.Pkcs12Adapter") - @patch("eox_nelp.api_clients.authenticators.requests.Session") - def test_authentication_call(self, session_mock, adapter_mock): - """ - Test the authentication call for the API client. - - This test case ensures that the `_authenticate` method of the `PKCS12Authenticator` - class sets the session object with the right adapter. - - Expected behavior: - - Session mock is called once. - - Session mount method is called once with the right parameters. - - Adapter is called once with the right parameters. - - api client has the attribute session - """ - api_client = self.api_class() - - session_mock.assert_called_once() - session_mock.return_value.mount.assert_called_once_with( - api_client.base_url, - adapter_mock.return_value, - ) - adapter_mock.assert_called_once_with( - pkcs12_filename=api_client.cert, - pkcs12_password=api_client.passphrase, - ) - self.assertEqual(api_client.session, session_mock.return_value) diff --git a/eox_nelp/api_clients/tests/test_mt.py b/eox_nelp/api_clients/tests/test_mt.py deleted file mode 100644 index 01f60f16..00000000 --- a/eox_nelp/api_clients/tests/test_mt.py +++ /dev/null @@ -1,55 +0,0 @@ -"""This file contains all the test for mt api client file. - -Classes: - TestMinisterOfTourismApiClient: Test for eox-nelp/api_clients/mt.py. -""" -import unittest - -from django.conf import settings -from mock import Mock, patch - -from eox_nelp.api_clients.mt import MinisterOfTourismApiClient -from eox_nelp.api_clients.tests.mixins import TestBasicAuthAuthenticatorMixin, TestRestApiClientMixin - - -class TestMinisterOfTourismApiClient(TestRestApiClientMixin, TestBasicAuthAuthenticatorMixin, unittest.TestCase): - """Tests MinisterOfTourismApiClient""" - - def setUp(self): - """Setup common conditions for every test case""" - self.api_class = MinisterOfTourismApiClient - self.user = settings.MINISTER_OF_TOURISM_USER - self.password = settings.MINISTER_OF_TOURISM_PASSWORD - - @patch.object(MinisterOfTourismApiClient, "make_post") - @patch.object(MinisterOfTourismApiClient, "_authenticate", Mock) - def test_create_certificate(self, post_mock): - """Test successful post request. - - Expected behavior: - - Response is the expected value - """ - expected_value = { - "correlationID": "f3e013f8-a9e4-4714-8162-4e3384f81578", - "responseCode": 100, - "responseMessage": "The update has been successfully", - "data": { - "result": True - }, - "elapsedTime": 0.2972466 - } - post_mock.return_value = expected_value - course_id = "course-v1:FutureX+guide+2023" - national_id = "1222555888" - stage_result = 1 - api_client = self.api_class() - - response = api_client.update_training_stage( - course_id=course_id, - national_id=national_id, - stage_result=stage_result, - ) - - self.assertDictEqual(response, expected_value) - - self.assertDictEqual(response, expected_value) diff --git a/eox_nelp/api_clients/tests/test_pearson_engine.py b/eox_nelp/api_clients/tests/test_pearson_engine.py deleted file mode 100644 index cb411cae..00000000 --- a/eox_nelp/api_clients/tests/test_pearson_engine.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -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, transaction_type="Add") - - 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 - "transaction_type": "Add", - }) - - @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/real-time-import/", { - "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 = "testuser@example.com" - - class Profile: - country = Country("US") - city = "New York" - phone_number = "+1234567890" - mailing_address = "123 Test St" - - class ExtraInfo: - arabic_name = "مستخدم اختبار" - arabic_first_name = "اختبار" - arabic_last_name = "مستخدم" - national_id = "1234567890" - - profile = Profile() - extrainfo = ExtraInfo() - - return MockUser() diff --git a/eox_nelp/api_clients/tests/test_pearson_rti.py b/eox_nelp/api_clients/tests/test_pearson_rti.py deleted file mode 100644 index 9731fc3c..00000000 --- a/eox_nelp/api_clients/tests/test_pearson_rti.py +++ /dev/null @@ -1,252 +0,0 @@ -"""This file contains all the test for pearson_rti api client file. - -Classes: - TestPearsonRTIApiClient: Test for eox-nelp/api_clients/sms_vendor.py. -""" -import unittest - -import requests -from bs4 import BeautifulSoup -from mock import patch - -from eox_nelp.api_clients import pearson_rti -from eox_nelp.api_clients.pearson_rti import PearsonRTIApiClient -from eox_nelp.api_clients.tests.mixins import TestPKCS12AuthenticatorMixin, TestSOAPClientMixin - - -class TestPearsonRTIApiClient(TestSOAPClientMixin, TestPKCS12AuthenticatorMixin, unittest.TestCase): - """Tests PearsonRTIApiClient""" - - def setUp(self): - """Setup common conditions for every test case""" - self.api_class = PearsonRTIApiClient - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_successful_ping(self, post_mock): - """Test successful ping request. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "success", - } - post_mock.return_value = '' - payload = "Harry" - api_client = self.api_class() - - response = api_client.ping(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/Ping", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_failed_ping(self, post_mock): - """Test failed ping request. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "failed", - } - post_mock.return_value = '' - payload = "Harry" - api_client = self.api_class() - - response = api_client.ping(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/Ping", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_fault_cdd(self, post_mock): - """Test when the cdd request return error. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "error", - "fault_code": "vue:AuthenticationFailure", - "message": "Invalid user name or password.", - } - post_mock.return_value = """ - - - vue:AuthenticationFailure - Invalid user name or password. - - - """ - payload = "Harry" - api_client = self.api_class() - - response = api_client.import_candidate_demographics(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/CDDService", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_accepted_status_cdd(self, post_mock): - """Test when the cdd request return an accepted status. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "accepted", - "message": "ok", - "candidate_id": "12345", - "client_candidate_id": "CC1234", - } - post_mock.return_value = """ - - - Accepted - ok - - - """ - payload = "Harry" - api_client = self.api_class() - - response = api_client.import_candidate_demographics(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/CDDService", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_unexpected_cdd_error(self, post_mock): - """Test when the cdd response returns an unconsider result. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - - Error was log - """ - post_mock.return_value = '' - xml_response = BeautifulSoup(post_mock.return_value, "xml") - expected_value = { - "status": "unexpected error", - "response": xml_response, - } - payload = "Harry" - api_client = self.api_class() - log_error = ( - "An unexpected error has occurred trying to make a CDD request getting " - f"the following response: {xml_response}" - ) - - with self.assertLogs(pearson_rti.__name__, level="ERROR") as logs: - response = api_client.import_candidate_demographics(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/CDDService", payload) - self.assertEqual(logs.output, [ - f"ERROR:{pearson_rti.__name__}:{log_error}" - ]) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_fault_ead(self, post_mock): - """Test when the ead request return error. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "error", - "fault_code": "vue:AuthenticationFailure", - "message": "Invalid user name or password.", - } - post_mock.return_value = """ - - - vue:AuthenticationFailure - Invalid user name or password. - - - """ - payload = "Harry" - api_client = self.api_class() - - response = api_client.import_exam_authorization(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/EADService", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_accepted_status_ead(self, post_mock): - """Test when the ead request return an accepted status. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - """ - expected_value = { - "status": "accepted", - "message": "ok", - "client_candidate_id": "CC1234", - } - post_mock.return_value = """ - - - CC1234 - Accepted - ok - - - """ - payload = "Harry" - api_client = self.api_class() - - response = api_client.import_exam_authorization(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/EADService", payload) - - @patch.object(PearsonRTIApiClient, "make_post") - @patch.object(PearsonRTIApiClient, "_authenticate", requests.Session) - def test_unexpected_ead_error(self, post_mock): - """Test when the ead response returns an unconsider result. - - Expected behavior: - - Response is the expected value. - - Post request was made with the required parameters. - - Error was log - """ - post_mock.return_value = '' - xml_response = BeautifulSoup(post_mock.return_value, "xml") - expected_value = { - "status": "unexpected error", - "response": xml_response - } - payload = "Harry" - api_client = self.api_class() - log_error = ( - "An unexpected error has occurred trying to make a EAD request getting " - f"the following response: {xml_response}" - ) - - with self.assertLogs(pearson_rti.__name__, level="ERROR") as logs: - response = api_client.import_exam_authorization(payload) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("cxfws2/services/EADService", payload) - self.assertEqual(logs.output, [ - f"ERROR:{pearson_rti.__name__}:{log_error}" - ]) diff --git a/eox_nelp/api_clients/tests/test_sms_vendor.py b/eox_nelp/api_clients/tests/test_sms_vendor.py deleted file mode 100644 index ddd97c1f..00000000 --- a/eox_nelp/api_clients/tests/test_sms_vendor.py +++ /dev/null @@ -1,47 +0,0 @@ -"""This file contains all the test for sms_vendor api client file. - -Classes: - TestSMSVendorApiClient: Test for eox-nelp/api_clients/sms_vendor.py. -""" -import unittest - -from mock import Mock, patch - -from eox_nelp.api_clients.sms_vendor import SMSVendorApiClient -from eox_nelp.api_clients.tests.mixins import TestRestApiClientMixin - - -class TestSMSVendorApiClient(TestRestApiClientMixin, unittest.TestCase): - """Tests SMSVendorApiClient""" - - def setUp(self): - """Setup common conditions for every test case""" - self.api_class = SMSVendorApiClient - - @patch.object(SMSVendorApiClient, "make_post") - @patch.object(SMSVendorApiClient, "_authenticate", Mock()) - def test_send_sms(self, post_mock): - """Test successful post request. - - Expected behavior: - - Response is the expected value - """ - expected_value = { - "message": "Operation completed successfully", - "transaction_id": "50693df-665d-47e1-affb-01076a83b9023427", - "recipient": "+573219990000", - "timestamp": "1720220972275" - } - post_mock.return_value = expected_value - recipient = "+573219990000" - message = "This is a message to test SMS integration." - api_client = self.api_class() - expected_payload = { - "sms_message": message, - "recipient_number": recipient, - } - - response = api_client.send_sms(recipient, message) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_once_with("sms/send", expected_payload) diff --git a/eox_nelp/api_clients/tests/tests_certificates.py b/eox_nelp/api_clients/tests/tests_certificates.py deleted file mode 100644 index 22953cbe..00000000 --- a/eox_nelp/api_clients/tests/tests_certificates.py +++ /dev/null @@ -1,82 +0,0 @@ -"""This file contains all the test for certificates api client file. - -Classes: - TestExternalCertificatesApiClient: Test for eox-nelp/api_clients/certificates.py. -""" -import unittest - -from django.conf import settings -from django.test import override_settings -from django.utils import timezone -from mock import Mock, patch - -from eox_nelp.api_clients.certificates import ExternalCertificatesApiClient -from eox_nelp.api_clients.tests.mixins import TestBasicAuthAuthenticatorMixin, TestRestApiClientMixin -from eox_nelp.signals.utils import generate_reference_id - - -class TestExternalCertificatesApiClient(TestRestApiClientMixin, TestBasicAuthAuthenticatorMixin, unittest.TestCase): - """Tests ExternalCertificatesApiClient""" - - def setUp(self): - """Setup common conditions for every test case""" - self.api_class = ExternalCertificatesApiClient - self.user = settings.EXTERNAL_CERTIFICATES_USER - self.password = settings.EXTERNAL_CERTIFICATES_PASSWORD - - @patch.object(ExternalCertificatesApiClient, "make_post") - @patch.object(ExternalCertificatesApiClient, "_authenticate", Mock) - def test_create_certificate(self, post_mock): - """Test successful post request. - - Expected behavior: - - Response is the expected value - """ - expected_value = { - "status": {"success": True, "message": "successful", "code": 1} - } - post_mock.return_value = expected_value - user = { - "national_id": "1222555888", - "english_name": " Testing", - "arabic_name": "اختبارات", - } - course_id = "course-v1:FutureX+guide+2023" - data = { - "id": "124ABC", - "reference_id": generate_reference_id(user["national_id"], course_id), - "created_at": timezone.now(), - "expiration_date": timezone.now() + timezone.timedelta(days=365), - "grade": 10, - "is_passing": True, - "user": user, - "group_code": "ABC123", - } - api_client = self.api_class() - - response = api_client.create_external_certificate(data) - - self.assertDictEqual(response, expected_value) - - @patch.object(ExternalCertificatesApiClient, "_authenticate", Mock) - def test_failed_create_certificate(self): - """Test when the mandatory fields has not been sent. - - Expected behavior: - - Raise KeyError exception. - """ - data = {} - api_client = self.api_class() - - self.assertRaises(KeyError, api_client.create_external_certificate, data) - - @override_settings(EXTERNAL_CERTIFICATES_EXTRA_HEADERS={"HTTP_CUSTOM_HEADER": "ABC123"}) - def test_extra_headers(self): - """Test if the extra header has been added to the session - - Expected behavior: - - Custom Header in session headers. - """ - api_client = self.api_class() - - self.assertEqual(api_client.session.headers["HTTP_CUSTOM_HEADER"], "ABC123") diff --git a/eox_nelp/api_clients/tests/tests_futurex.py b/eox_nelp/api_clients/tests/tests_futurex.py deleted file mode 100644 index 2ccc2b11..00000000 --- a/eox_nelp/api_clients/tests/tests_futurex.py +++ /dev/null @@ -1,67 +0,0 @@ -"""This file contains all the test for futurex api client file. - -Classes: - TestFuturexApiClient: Test for eox-nelp/api_clients/futurex.py. -""" -import unittest - -import requests -from mock import patch - -from eox_nelp.api_clients.futurex import FuturexApiClient, FuturexMissingArguments -from eox_nelp.api_clients.tests.mixins import TestOauth2AuthenticatorMixin, TestRestApiClientMixin - - -class TestFuturexApiClient(TestRestApiClientMixin, TestOauth2AuthenticatorMixin, unittest.TestCase): - """Tests FuturexApiClient""" - - def setUp(self): - """Setup common conditions for every test case""" - self.api_class = FuturexApiClient - - @patch.object(FuturexApiClient, "make_post") - @patch.object(FuturexApiClient, "_authenticate") - def test_enrollment_progress(self, auth_mock, post_mock): - """Test enrollment progress API call. - - Expected behavior: - - Response is the expected value - - make_post was called with the right values. - """ - auth_mock.return_value = requests.Session() - expected_value = { - "status": {"success": True, "message": "successful", "code": 1} - } - post_mock.return_value = expected_value - data = { - "courseId": "course-v1:lms152", - "userId": 52, - "approxTotalCourseHrs": 5, - "overallProgress": 10, - "membershipState": "active", - "enrolledAt": "2012-12-30", - "isCompleted": False, - } - api_client = self.api_class() - - response = api_client.enrollment_progress(data) - - self.assertDictEqual(response, expected_value) - post_mock.assert_called_with("enrollment-progress", data) - - @patch.object(FuturexApiClient, "_authenticate") - def test_failed_enrollment_progress(self, auth_mock): - """Test when enrollment progress fails due to missing parameters. - - Expected behavior: - - Raise FuturexMissingArguments exception - """ - auth_mock.return_value = requests.Session() - data = { - "courseId": "course-v1:lms152", - "userId": 52, - "approxTotalCourseHrs": 5, - } - api_client = self.api_class() - - self.assertRaises(FuturexMissingArguments, api_client.enrollment_progress, data) diff --git a/eox_nelp/one_time_password/api/v1/views.py b/eox_nelp/one_time_password/api/v1/views.py index 0ecc916c..4e030f4a 100644 --- a/eox_nelp/one_time_password/api/v1/views.py +++ b/eox_nelp/one_time_password/api/v1/views.py @@ -10,12 +10,12 @@ from django.core.cache import cache from django.http import JsonResponse from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser +from nelc_api_clients.clients.sms_vendor import SMSVendorApiClient from rest_framework import status from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response -from eox_nelp.api_clients.sms_vendor import SMSVendorApiClient from eox_nelp.one_time_password.generators import generate_otp_code from eox_nelp.one_time_password.view_decorators import validate_otp diff --git a/eox_nelp/pearson_vue/pipeline.py b/eox_nelp/pearson_vue/pipeline.py index 7d11c93b..fee6ba2c 100644 --- a/eox_nelp/pearson_vue/pipeline.py +++ b/eox_nelp/pearson_vue/pipeline.py @@ -19,9 +19,9 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone +from nelc_api_clients.clients.pearson_rti import PearsonRTIApiClient from pydantic import ValidationError -from eox_nelp.api_clients.pearson_rti import PearsonRTIApiClient from eox_nelp.edxapp_wrapper.certificates import generate_course_certificate from eox_nelp.edxapp_wrapper.student import AnonymousUserId, CourseEnrollment, anonymous_id_for_user from eox_nelp.pearson_vue.constants import PAYLOAD_CDD, PAYLOAD_EAD, PAYLOAD_PING_DATABASE diff --git a/eox_nelp/pearson_vue/tasks.py b/eox_nelp/pearson_vue/tasks.py index 0cc29db4..85e948a6 100644 --- a/eox_nelp/pearson_vue/tasks.py +++ b/eox_nelp/pearson_vue/tasks.py @@ -6,9 +6,9 @@ """ from celery import shared_task from django.contrib.auth import get_user_model +from nelc_api_clients.clients.pearson_engine import PearsonEngineApiClient from requests import exceptions -from eox_nelp.api_clients.pearson_engine import PearsonEngineApiClient from eox_nelp.pearson_vue.constants import ALLOWED_RTI_ACTIONS from eox_nelp.pearson_vue.pipeline import audit_method, rename_function from eox_nelp.pearson_vue.rti_backend import ( diff --git a/eox_nelp/signals/tasks.py b/eox_nelp/signals/tasks.py index 49087bb5..f5997ec5 100644 --- a/eox_nelp/signals/tasks.py +++ b/eox_nelp/signals/tasks.py @@ -16,10 +16,10 @@ from eox_core.edxapp_wrapper.enrollments import get_enrollment from eventtracking import tracker from nelc_api_clients.clients.certificates import ExternalCertificatesApiClient +from nelc_api_clients.clients.futurex import FuturexApiClient +from nelc_api_clients.clients.mt import MinisterOfTourismApiClient from opaque_keys.edx.keys import UsageKey -from eox_nelp.api_clients.futurex import FuturexApiClient -from eox_nelp.api_clients.mt import MinisterOfTourismApiClient from eox_nelp.edxapp_wrapper.course_blocks import get_student_module_as_dict from eox_nelp.edxapp_wrapper.course_overviews import CourseOverview from eox_nelp.edxapp_wrapper.grades import SubsectionGradeFactory @@ -61,7 +61,7 @@ def _post_futurex_progress(data): data (dict): dict to send to futurex enrollment-progress path. """ api_client = FuturexApiClient() - response = api_client.enrollment_progress(data) + response = api_client.enrollment_progress(data) # pylint: disable=no-member logger.info( "send_futurex_progress --- The data %s was sent to the futurex service host %s. The response was: %s",