diff --git a/src/ogd/apis/models/APIRequest.py b/src/ogd/apis/models/APIRequest.py index 63210d0..b536841 100644 --- a/src/ogd/apis/models/APIRequest.py +++ b/src/ogd/apis/models/APIRequest.py @@ -1,58 +1,88 @@ import logging -import requests from typing import Any, Dict, Optional +from urllib.parse import urlparse, urlunparse, ParseResult + +import requests +from flask import current_app +from ogd.apis.models.enums.RESTType import RESTType from ogd.apis.models.enums.ResponseStatus import ResponseStatus +from ogd.apis.models.APIResponse import APIResponse + +class APIRequest: + def __init__(self, url:str, request_type:str | RESTType, params:Optional[Dict[str, Any]]=None, body:Optional[Dict[str, Any]]=None, timeout:int=1): + """Utility function to make it easier to send requests to a remote server during unit testing. + + This function does some basic sanity checking of the target URL, + maps the request type to the appropriate `requests` function call, + and performs basic error handling to notify what error occurred. + + :param url: The target URL for the web request + :type url: str + :param request: Whether to perform a "GET", "POST", or "PUT" request + :type request: str + :param params: A mapping of request parameter names to values. Defaults to {} + :type params: Dict[str, Any], optional + :param body: The body of the request to send. Defaults to None + :type body: Dict[str, Any], optional + :param logger: A logger to use for debug/error outputs. Defaults to None + :type logger: logging.Logger, optional + :raises err: Currently, any exceptions that occur during the request will be raised up. + If verbose logging is on, a simple debug message indicating the request type and URL is printed first. + :return: The `Response` object from the request, or None if an error occurred. + :rtype: requests.Response + """ + params = params or {} + + self._request_type : RESTType + + if not url.startswith("http://") or url.startswith("https://"): + url = f"https://{url}" + if isinstance(request_type, RESTType): + self._request_type = request_type + else: + try: + self._request_type = RESTType[request_type] + except KeyError: + current_app.logger.warning(f"Bad request type {request_type}, defaulting to GET") + self._request_type = RESTType.GET + + self._url = url + self._params = params + self._body = body + self._timeout = timeout + + + def Execute(self, logger:Optional[logging.Logger]=None) -> APIResponse: + ret_val : APIResponse + + if logger is None and current_app: + logger = current_app.logger -def APIRequest(url:str, request:str, params:Optional[Dict[str, Any]]=None, body:Optional[Dict[str, Any]]=None, timeout:int=1, logger:Optional[logging.Logger]=None) -> requests.Response: - """Utility function to make it easier to send requests to a remote server during unit testing. - - This function does some basic sanity checking of the target URL, - maps the request type to the appropriate `requests` function call, - and performs basic error handling to notify what error occurred. - - :param url: The target URL for the web request - :type url: str - :param request: Whether to perform a "GET", "POST", or "PUT" request - :type request: str - :param params: A mapping of request parameter names to values. Defaults to {} - :type params: Dict[str, Any], optional - :param body: The body of the request to send. Defaults to None - :type body: Dict[str, Any], optional - :param logger: A logger to use for debug/error outputs. Defaults to None - :type logger: logging.Logger, optional - :raises err: Currently, any exceptions that occur during the request will be raised up. - If verbose logging is on, a simple debug message indicating the request type and URL is printed first. - :return: The `Response` object from the request, or None if an error occurred. - :rtype: requests.Response - """ - ret_val : requests.Response - - params = params or {} - - if not (url.startswith("https://") or url.startswith("http://")): - url = f"https://{url}" # give url a default scheme - try: - match (request.upper()): - case "GET": - ret_val = requests.get(url, params=params, timeout=timeout) - case "POST": - ret_val = requests.post(url, params=params, data=body, timeout=timeout) - case "PUT": - ret_val = requests.put(url, params=params, data=body, timeout=timeout) - case _: - if logger: - logger.warning(f"Bad request type {request}, defaulting to GET") - ret_val = requests.get(url, params=params, timeout=timeout) - except Exception as err: - if logger: - logger.error(f"Error on {request} request to {url} : {err}") - raise err - else: - if logger: - out = logger.debug if ret_val.status_code == ResponseStatus.OK else logger.warning - out(f"Request sent to: {url}") - out(f"Response received from: {ret_val.url}") - out(f" Status: {ret_val.status_code}") - out(f" Data: {ret_val.text}") + response : requests.Response + try: + match (self._request_type): + case RESTType.GET: + response = requests.get( self._url, params=self._params, timeout=self._timeout) + case RESTType.POST: + response = requests.post(self._url, params=self._params, data=self._body, timeout=self._timeout) + case RESTType.PUT: + response = requests.put( self._url, params=self._params, data=self._body, timeout=self._timeout) + case _: + if logger: + logger.warning(f"Bad request type {self._request_type}, defaulting to GET") + response = requests.get(self._url, params=self._params, timeout=self._timeout) + except Exception as err: + if logger: + logger.error(f"Error on {self._request_type} request to {self._url} : {err}") + raise err + else: + ret_val = APIResponse.FromResponse(response) + if logger: + out = logger.debug if ret_val.Status == ResponseStatus.OK else logger.warning + out(f"Request sent to: {self._url}") + out(f"Response received from: {self._url}") + out(f" Status: {ret_val.Status}") + out(f" Msg: {ret_val.Message}") + out(f" Value: {ret_val.Value}") return ret_val diff --git a/src/ogd/apis/models/APIResponse.py b/src/ogd/apis/models/APIResponse.py index 7e9b143..eeddf9f 100644 --- a/src/ogd/apis/models/APIResponse.py +++ b/src/ogd/apis/models/APIResponse.py @@ -10,6 +10,7 @@ from typing import Any, Dict, Optional # import 3rd-party libraries +import requests from flask import Response # import OGD libraries @@ -21,8 +22,14 @@ from ogd.apis.models.enums.ResponseStatus import ResponseStatus class APIResponse: - def __init__(self, req_type:Optional[RESTType], val:Optional[Map], msg:str, status:ResponseStatus): - self._type : Optional[RESTType] = req_type + def __init__(self, req_type:Optional[RESTType | str], val:Optional[Map], msg:str, status:ResponseStatus): + self._type : Optional[RESTType] + if isinstance(req_type, RESTType): + self._type = req_type + elif isinstance(req_type, str): + self._type = RESTType[req_type] + else: + self._type = None self._val : Optional[Map] = val self._msg : str = msg self._status : ResponseStatus = status @@ -63,6 +70,18 @@ def FromRequestResult(result:RequestResult.RequestResult, req_type:RESTType) -> _status = ResponseStatus.INTERNAL_ERR ret_val = APIResponse(req_type=req_type, val={"session_count":result.SessionCount, "duration":str(result.Duration)}, msg=result.Message, status=_status) return ret_val + + @staticmethod + def FromResponse(result:requests.Response) -> "APIResponse": + ret_val : APIResponse + + try: + raw = result.json() + ret_val = APIResponse(req_type=raw.get("type"), val=raw.get("val"), msg=raw.get("msg"), status=ResponseStatus(result.status_code)) + except requests.exceptions.JSONDecodeError: + ret_val = APIResponse(req_type=None, val=None, msg=result.text, status=ResponseStatus(result.status_code)) + + return ret_val @staticmethod def FromDict(all_elements:Dict[str, Any], status:Optional[ResponseStatus]=None) -> Optional["APIResponse"]: diff --git a/tests/cases/apis/HelloAPI/remote/t_Hello.py b/tests/cases/apis/HelloAPI/remote/t_Hello.py index bc5f65f..490b19b 100644 --- a/tests/cases/apis/HelloAPI/remote/t_Hello.py +++ b/tests/cases/apis/HelloAPI/remote/t_Hello.py @@ -1,5 +1,4 @@ # import libraries -import json import logging from unittest import TestCase # import 3rd-party libraries @@ -7,7 +6,7 @@ from ogd.common.configs.TestConfig import TestConfig from ogd.common.utils.Logger import Logger # import locals -from src.ogd.apis.models.APIRequest import APIRequest +from ogd.apis.models.APIRequest import APIRequest from tests.config.t_config import settings class t_Hello_remote(TestCase): @@ -24,54 +23,39 @@ def setUpClass(cls) -> None: def test_get(self): _url = f"{self.base_url}/hello" try: - result = APIRequest(url=_url, request="GET", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="GET", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError as err: - Logger.Log(f"Could not parse json from {result.text}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "GET", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), "Hello! You GETted successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status.value, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "GET", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, "Hello! You GETted successfully!", f"Bad msg from {_url}") def test_post(self): _url = f"{self.base_url}/hello" try: - result = APIRequest(url=_url, request="POST", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="POST", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError as err: - Logger.Log(f"Could not parse json from {result.text}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "POST", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), "Hello! You POSTed successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status.value, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "POST", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, "Hello! You POSTed successfully!", f"Bad msg from {_url}") def test_put(self): _url = f"{self.base_url}/hello" Logger.Log(f"PUT test at {_url}", logging.DEBUG) try: - result = APIRequest(url=_url, request="PUT", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="PUT", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError as err: - Logger.Log(f"Could not parse json from {result.text}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "PUT", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), "Hello! You PUTted successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status.value, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "PUT", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, "Hello! You PUTted successfully!", f"Bad msg from {_url}") diff --git a/tests/cases/apis/HelloAPI/remote/t_ParamHello.py b/tests/cases/apis/HelloAPI/remote/t_ParamHello.py index b416693..b0f2b0e 100644 --- a/tests/cases/apis/HelloAPI/remote/t_ParamHello.py +++ b/tests/cases/apis/HelloAPI/remote/t_ParamHello.py @@ -1,5 +1,4 @@ # import libraries -import json import logging from unittest import TestCase # import 3rd-party libraries @@ -7,7 +6,7 @@ from ogd.common.configs.TestConfig import TestConfig from ogd.common.utils.Logger import Logger # import locals -from src.ogd.apis.models.APIRequest import APIRequest +from ogd.apis.models.APIRequest import APIRequest from tests.config.t_config import settings class t_ParamHello_remote(TestCase): @@ -25,54 +24,39 @@ def setUpClass(cls) -> None: def test_get(self): _url = f"{self.base_url}/p_hello/{self.param}" try: - result = APIRequest(url=_url, request="GET", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="GET", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError: - Logger.Log(f"Could not parse json result '{result.text}' from {_url}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "GET", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), f"Hello {self.param}! You GETted successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "GET", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, f"Hello {self.param}! You GETted successfully!", f"Bad msg from {_url}") def test_post(self): _url = f"{self.base_url}/p_hello/{self.param}" try: - result = APIRequest(url=_url, request="POST", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="POST", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError: - Logger.Log(f"Could not parse json result '{result.text}' from {_url}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "POST", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), f"Hello {self.param}! You POSTed successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "POST", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, f"Hello {self.param}! You POSTed successfully!", f"Bad msg from {_url}") def test_put(self): _url = f"{self.base_url}/p_hello/{self.param}" Logger.Log(f"PUT test at {_url}", logging.DEBUG) try: - result = APIRequest(url=_url, request="PUT", params={}, logger=Logger.std_logger) + result = APIRequest(url=_url, request_type="PUT", params={}).Execute(logger=Logger.std_logger) except Exception as err: self.fail(str(err)) else: self.assertNotEqual(result, None, f"No response from {_url}") - self.assertEqual(result.status_code, 200, f"Bad status from {_url}") - try: - body = json.loads(result.text) - except json.decoder.JSONDecodeError: - Logger.Log(f"Could not parse json result '{result.text}' from {_url}", logging.ERROR) - body = {} - self.assertEqual(body.get("type"), "PUT", f"Bad type from {_url}") - self.assertEqual(body.get("val"), None, f"Bad val from {_url}") - self.assertEqual(body.get("msg"), f"Hello {self.param}! You PUTted successfully!", f"Bad msg from {_url}") + self.assertEqual(result.Status, 200, f"Bad status from {_url}") + self.assertEqual(str(result.Type), "PUT", f"Bad type from {_url}") + self.assertEqual(result.Value, None, f"Bad val from {_url}") + self.assertEqual(result.Message, f"Hello {self.param}! You PUTted successfully!", f"Bad msg from {_url}") diff --git a/tests/cases/apis/HelloAPI/remote/t_Version.py b/tests/cases/apis/HelloAPI/remote/t_Version.py index 368a8a5..0596717 100644 --- a/tests/cases/apis/HelloAPI/remote/t_Version.py +++ b/tests/cases/apis/HelloAPI/remote/t_Version.py @@ -11,8 +11,8 @@ from ogd.common.utils.Logger import Logger # Logger.InitializeLogger(level=logging.INFO, use_logfile=False) # import locals -from src.ogd.apis.configs.ServerConfig import ServerConfig -from src.ogd.apis.HelloAPI import HelloAPI +from ogd.apis.configs.ServerConfig import ServerConfig +from ogd.apis.HelloAPI import HelloAPI from tests.config.t_config import settings class t_Version_remote(TestCase): diff --git a/tests/cases/models/t_APIResponse.py b/tests/cases/models/t_APIResponse.py index 51ad3d1..a14e95a 100644 --- a/tests/cases/models/t_APIResponse.py +++ b/tests/cases/models/t_APIResponse.py @@ -8,9 +8,9 @@ from ogd.common.configs.TestConfig import TestConfig from ogd.common.utils.Logger import Logger # import locals -from src.ogd.apis.models.enums.ResponseStatus import ResponseStatus -from src.ogd.apis.models.enums.RESTType import RESTType -from src.ogd.apis.models.APIResponse import APIResponse +from ogd.apis.models.enums.ResponseStatus import ResponseStatus +from ogd.apis.models.enums.RESTType import RESTType +from ogd.apis.models.APIResponse import APIResponse from tests.config.t_config import settings