From 14a044178b58adc84810895f78ff7f95b3e99f79 Mon Sep 17 00:00:00 2001 From: Jenifer Tabita Ciuciu-Kiss Date: Tue, 22 Oct 2024 11:16:57 +0200 Subject: [PATCH 1/4] add possibility to define multiple hosts --- ontologytimemachine/custom_proxy.py | 6 +++++- ontologytimemachine/utils/config.py | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ontologytimemachine/custom_proxy.py b/ontologytimemachine/custom_proxy.py index 145b1f4..23a3750 100644 --- a/ontologytimemachine/custom_proxy.py +++ b/ontologytimemachine/custom_proxy.py @@ -155,9 +155,13 @@ def queue_response(self, response): "--ca-cert-file", "ca-cert.pem", "--ca-signing-key-file", "ca-signing-key.pem", ] + + for host in config.host: + sys.argv += [ + "--hostname", host, + ] sys.argv += [ - "--hostname", config.host, "--port", str(config.port), # "--log-level", config.logLevel.name, '--insecure-tls-interception', # without it the proxy would not let through a response using an invalid upstream certificate in interception mode diff --git a/ontologytimemachine/utils/config.py b/ontologytimemachine/utils/config.py index e49ea27..cb546f5 100644 --- a/ontologytimemachine/utils/config.py +++ b/ontologytimemachine/utils/config.py @@ -2,7 +2,7 @@ from dataclasses import dataclass, field from enum import Enum import logging -from typing import Dict, Any, Type, TypeVar +from typing import Dict, Any, Type, TypeVar, List logging.basicConfig( @@ -77,7 +77,7 @@ class Config: httpsInterception: HttpsInterception = HttpsInterception.ALL disableRemovingRedirects: bool = False timestamp: str = "" - host: str = "0.0.0.0" + host: List[str] = field(default_factory=lambda: ["0.0.0.0", "::"]) port: int = 8898 # manifest: Dict[str, Any] = None @@ -185,8 +185,9 @@ def parse_arguments(config_str: str = "") -> Config: parser.add_argument( "--host", type=str, + nargs='+', # Accepts one or more hostnames default=default_cfg.host, - help=f"Hostname or IP address to bind the proxy to. {help_suffix_template}", + help=f"Hostnames or IP addresses to bind the proxy to. Multiple hosts can be provided. {help_suffix_template}", ) # Port From 74a10b855094e6de96d45b4a37b30aa70f89e4ff Mon Sep 17 00:00:00 2001 From: Jenifer Tabita Ciuciu-Kiss Date: Thu, 24 Oct 2024 19:22:04 +0200 Subject: [PATCH 2/4] initial testcases for tsv --- tests/test_tsv.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/test_tsv.py diff --git a/tests/test_tsv.py b/tests/test_tsv.py new file mode 100644 index 0000000..c1e003d --- /dev/null +++ b/tests/test_tsv.py @@ -0,0 +1,88 @@ +import requests +from urllib.parse import urlparse +from ontologytimemachine.custom_proxy import IP, PORT + +# Mock proxy modes and settings for demonstration purposes +PROXY_MODES = ["live", "failover", "latest", "timestamp"] +INTERCEPTION_MODES = ["all", "archivo", "block"] + + + +PROXY = f"{IP}:{PORT}" +HTTP_PROXY = f"http://{PROXY}" +HTTPS_PROXY = f"http://{PROXY}" +PROXIES = {"http": HTTP_PROXY, "https": HTTPS_PROXY} + +# Load the TSV data +def load_tsv_data(tsv_file): + iri_data = [] + with open(tsv_file, 'r') as f: + for line in f.readlines()[1:]: + parts = line.strip().split('\t') + iri_data.append({ + "iri": parts[0], + "error_dimension": parts[1], + "expected_error": parts[2], + "iri_type": parts[3], + "comment": parts[4] if len(parts) > 4 else "" + }) + return iri_data + +# Basic request function (direct or proxied) +def make_request(iri, proxy=None, mode=None): + try: + response = requests.get(iri, proxies=proxy if proxy else {}) + return response.status_code, response.text + except requests.exceptions.RequestException as e: + return "error", str(e) + +# Test function for direct Archivo IRI requests +def test_DA(iri_data): + for item in iri_data: + if item["iri_type"] == "hash" or item["iri_type"] == "term": + status, content = make_request(item["iri"]) + print(f"DA Request to {item['iri']}: Status {status}, Expected {item['expected_error']}") + +# Test function for direct non-Archivo IRI requests +def test_DN(iri_data): + for item in iri_data: + if item["iri_type"] == "slash": + status, content = make_request(item["iri"]) + print(f"DN Request to {item['iri']}: Status {status}, Expected {item['expected_error']}") + +# Proxied Archivo requests with different modes +def test_PA(iri_data, mode): + for item in iri_data: + if item["iri_type"] == "hash" or item["iri_type"] == "term": + status, content = make_request(item["iri"], proxy=PROXY) + print(f"{mode}-PA Request to {item['iri']}: Status {status}, Expected {item['expected_error']}") + +# Proxied non-Archivo requests with different modes +def test_PN(iri_data, mode): + for item in iri_data: + if item["iri_type"] == "slash": + status, content = make_request(item["iri"], proxy=PROXY) + print(f"{mode}-PN Request to {item['iri']}: Status {status}, Expected {item['expected_error']}") + +# Main function to run the tests based on the proxy mode and interception settings +def run_tests(tsv_file, interception_mode="block"): + iri_data = load_tsv_data(tsv_file) + + print("Running Direct Archivo (DA) Tests...") + test_DA(iri_data) + + print("\nRunning Direct Non-Archivo (DN) Tests...") + test_DN(iri_data) + + if interception_mode != "block": + for mode in PROXY_MODES: + print(f"\nRunning Proxied Archivo ({mode}-PA) Tests...") + test_PA(iri_data, mode) + + print(f"\nRunning Proxied Non-Archivo ({mode}-PN) Tests...") + test_PN(iri_data, mode) + +# Example of running the tests +if __name__ == "__main__": + tsv_file = "tests/archivo_test_IRIs.tsv" + run_tests(tsv_file, interception_mode="archivo") From e05f0e4f2aa952868d8ed40216b7aeadfd07e28f Mon Sep 17 00:00:00 2001 From: Jenifer Tabita Ciuciu-Kiss Date: Fri, 25 Oct 2024 03:38:58 +0200 Subject: [PATCH 3/4] trying to make proxypy testcases work --- tests/test_proxypy.py | 220 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 tests/test_proxypy.py diff --git a/tests/test_proxypy.py b/tests/test_proxypy.py new file mode 100644 index 0000000..bc57573 --- /dev/null +++ b/tests/test_proxypy.py @@ -0,0 +1,220 @@ +import unittest +import requests +import csv +from proxy.proxy import Proxy +from proxy.common.utils import new_socket_connection +from ontologytimemachine.utils.config import Config, parse_arguments +from ontologytimemachine.custom_proxy import OntologyTimeMachinePlugin +from typing import List, Tuple +from requests.auth import HTTPBasicAuth +import time +import logging + + +logging.basicConfig( + level=logging.DEBUG, # Set the logging level to DEBUG + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + + +class OntoVersionTestCase(unittest.TestCase): + """Test case for making requests with different OntoVersions.""" + + DEFAULT_PROXY_PY_STARTUP_FLAGS = [ + '--hostname', '0.0.0.0', + '--port', '0', # Automatically bind to an available port + '--num-workers', '1', + '--num-acceptors', '1', + ] + + PROXY: Proxy = None + INPUT_ARGS: List[str] = None + + test_data = [] + plugin_config: Config = None + + @classmethod + def setUpClass(cls): + """Set up class-level resources, including reading the TSV test data.""" + # Load test data from TSV file + cls.load_test_data_from_tsv("tests/archivo_test_IRIs.tsv") + + @classmethod + def load_test_data_from_tsv(cls, filepath: str): + """Load test cases from a TSV file.""" + with open(filepath, mode='r', encoding='utf-8') as file: + reader = csv.DictReader(file, delimiter='\t') + for row in reader: + iri = row['iri'] + error_dimension = row['error_dimension'] + expected_error = row['expected_error'] + iri_type = row['iri_type'] + comment = row['comment'] + cls.test_data.append((iri, error_dimension, expected_error, iri_type, comment)) + + + def setUpProxy(self) -> None: + self.PROXY = Proxy(self.DEFAULT_PROXY_PY_STARTUP_FLAGS) + self.PROXY.flags.plugins[b'HttpProxyBasePlugin'].append( + OntologyTimeMachinePlugin, + ) + self.PROXY.__enter__() + self.wait_for_server(self.PROXY.flags.port) + + def tearDownProxy(self) -> None: + """Tear down the proxy.""" + if self.PROXY: + self.PROXY.__exit__(None, None, None) + self.PROXY = None + + @staticmethod + def wait_for_server(proxy_port: int, wait_for_seconds: float = 10.0) -> None: + """Wait for the proxy to be available.""" + start_time = time.time() + while True: + try: + new_socket_connection(('localhost', proxy_port)).close() + break + except ConnectionRefusedError: + time.sleep(0.1) + + if time.time() - start_time > wait_for_seconds: + raise TimeoutError('Timed out while waiting for proxy to start...') + + def make_request_without_proxy(self, iri: str) -> Tuple[int, str]: + """Make a direct request to the IRI without using the proxy.""" + headers = { + "Content-Type": "text/turtle" + } + try: + response = requests.get(iri, timeout=10, headers=headers) + return response + except Exception as e: + # logger.info(f'Error: {e}') + # logger.info('Error with the connection') + response.status_code = 'error' + return response + + def make_request_with_proxy(self, iri: str, proxy_port: int, mode: str) -> Tuple[int, str]: + """Make a request to the IRI using the proxy.""" + proxies = { + "http": f"http://localhost:{proxy_port}", + "https": f"https://localhost:{proxy_port}", + } + username = f"--ontoVersion {mode}" + password = "my_password" + headers = { + "Content-Type": "text/turtle" + } + try: + response = requests.get(iri, proxies=proxies, timeout=10, headers=headers, auth=HTTPBasicAuth(username, password)) + return response + except Exception as e: + # logger.info(f'Error: {e}') + # logger.info('Error with the connection') + return {'status_code': 'error'} + + + + def compare_responses(self, direct_response: Tuple[int, str], proxy_response: Tuple[int, str]): + """Compare the results of the direct and proxy responses.""" + self.assertEqual(direct_response[0], proxy_response[0], "Status codes do not match.") + self.assertEqual(direct_response[1], proxy_response[1], "Content types do not match.") + + def evaluate_results(self, direct_response, proxy_response, error_dimension, expected_error): + error_found = False # Flag to track if any assertion fails + logger.info('Test without proxy results') + try: + if error_dimension == 'http-code': + logger.info(f"Comparing direct response status code: expected {expected_error}, got {direct_response.status_code}") + self.assertEqual(int(expected_error), direct_response.status_code) + elif error_dimension == 'None': + logger.info(f"Comparing direct response status code for 'None' error dimension: expected 200, got {direct_response.status_code}") + self.assertEqual(200, direct_response.status_code) + elif error_dimension == 'content': + logger.info(f"Comparing direct response content length: expected 0, got {len(direct_response.content)}") + self.assertEqual(0, len(direct_response.content)) + else: + logger.info(f"Comparing direct response status code for unknown error dimension: expected 'error', got '{direct_response}'") + self.assertEqual('error', direct_response.status_code) + except AssertionError as e: + logger.error(f"Direct response assertion failed: {e}") + error_found = True # Mark that an error occurred but continue + + # Logs before proxy response assertions + logger.info('Test Proxy original results') + try: + logger.info(error_dimension) + if error_dimension == 'http-code': + logger.info(f"Comparing proxy response status code: expected {expected_error}, got {proxy_response.status_code}") + self.assertEqual(int(expected_error), proxy_response.status_code) + elif error_dimension == 'None': + logger.info(f"Comparing proxy response status code for 'None' error dimension: expected 200, got {proxy_response.status_code}") + self.assertEqual(200, proxy_response.status_code) + elif error_dimension == 'content': + logger.info(f"Comparing proxy response content length: expected 0, got {len(proxy_response.content)}") + self.assertEqual(0, len(proxy_response.content)) + else: + logger.info(f"Comparing proxy response status code for unknown error dimension: expected 'error', got '{proxy_response.status_code}'") + self.assertEqual('error', proxy_response.status_code) + except AssertionError as e: + logger.error(f"Proxy response assertion failed: {e}") + error_found = True # Mark that an error occurred but continue + + # If any assertion failed, mark the test as failed + if error_found: + self.fail("One or more assertions failed. See logs for details.") + + + def test_requests_with_different_onto_versions(self): + """Test requests with different OntoVersions and compare results.""" + # Make request without proxy + mode = 'original' + for iri, error_dimension, expected_error, iri_type, comment in self.test_data: + logger.info(f'IRI: {iri}') + with self.subTest(iri=iri, expected_error=expected_error, mode=mode): + self.setUpProxy() + + try: + # Make requests + direct_response = self.make_request_without_proxy(iri) + proxy_response = self.make_request_with_proxy(iri, self.PROXY.flags.port, mode) + + # Evaluate the results + logger.info('Test without proxy results') + if error_dimension == 'http-code': + logger.info(f"Comparing direct response status code: expected {expected_error}, got {direct_response.status_code}") + self.assertEqual(int(expected_error), direct_response.status_code) + logger.info(f"Comparing proxy response status code: expected {expected_error}, got {proxy_response.status_code}") + self.assertEqual(int(expected_error), proxy_response.status_code) + elif error_dimension == 'None': + logger.info(f"Comparing direct response status code for 'None' error dimension: expected 200, got {direct_response.status_code}") + self.assertEqual(200, direct_response.status_code) + logger.info(f"Comparing proxy response status code for 'None' error dimension: expected 200, got {proxy_response.status_code}") + self.assertEqual(200, proxy_response.status_code) + elif error_dimension == 'content': + logger.info(f"Comparing direct response content length: expected 0, got {len(direct_response.content)}") + self.assertEqual(0, len(direct_response.content)) + logger.info(f"Comparing proxy response content length: expected 0, got {len(proxy_response.content)}") + self.assertEqual(0, len(proxy_response.content)) + else: + logger.info(f"Comparing direct response status code for unknown error dimension: expected 'error', got '{direct_response}'") + self.assertEqual('error', direct_response.status_code) + logger.info(f"Comparing proxy response status code for unknown error dimension: expected 'error', got '{proxy_response.status_code}'") + self.assertEqual('error', proxy_response.status_code) + + + finally: + # Tear down the proxy after each test case + self.tearDownProxy() + + # Set up proxy with another OntoVersion and compare results + # self.setUpProxy("latestArchived") + # proxy_response_latest = self.make_request_with_proxy(iri, self.PROXY.flags.port) + # self.compare_responses(direct_response, proxy_response_latest) + # self.tearDownProxy() + + +if __name__ == "__main__": + unittest.main() From 1ec622461789e004f3be784f97f93a31f1efc8f0 Mon Sep 17 00:00:00 2001 From: Jenifer Tabita Ciuciu-Kiss Date: Fri, 25 Oct 2024 03:59:54 +0200 Subject: [PATCH 4/4] testcases try2 --- tests/test_proxy_new.py | 118 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/test_proxy_new.py diff --git a/tests/test_proxy_new.py b/tests/test_proxy_new.py new file mode 100644 index 0000000..bb6d74f --- /dev/null +++ b/tests/test_proxy_new.py @@ -0,0 +1,118 @@ +import pytest +import requests +import logging +import csv +from typing import List, Tuple +from requests.auth import HTTPBasicAuth +from ontologytimemachine.custom_proxy import IP, PORT + +# Proxy settings +PROXY = f"0.0.0.0:{PORT}" +HTTP_PROXY = f"http://{PROXY}" +HTTPS_PROXY = f"http://{PROXY}" +PROXIES = {"http": HTTP_PROXY, "https": HTTPS_PROXY} + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", +) +logger = logging.getLogger(__name__) + +# Load data from the TSV file dynamically +def load_test_data(file_path): + with open(file_path, 'r') as tsv_file: + reader = csv.DictReader(tsv_file, delimiter='\t') + return [row for row in reader] + + +def create_fake_response(status_code='error'): + fake_response = requests.models.Response() + fake_response.status_code = status_code # Assign the status code you want to simulate + fake_response._content = b'{"error": "This is a simulated error"}' # Set some fake content + return fake_response + + +def make_request_without_proxy(iri: str) -> Tuple[int, str]: + """Make a direct request to the IRI without using the proxy.""" + headers = { + "Content-Type": "text/turtle" + } + try: + response = requests.get(iri, timeout=10, headers=headers) + return response + except Exception as e: + # logger.info(f'Error: {e}') + # logger.info('Error with the connection') + return create_fake_response() + +def make_request_with_proxy(iri: str, mode: str) -> Tuple[int, str]: + """Make a request to the IRI using the proxy.""" + username = f"--ontoVersion {mode}" + password = "my_password" + headers = { + "Content-Type": "text/turtle" + } + try: + response = requests.get(iri, proxies=PROXIES, timeout=10, headers=headers, auth=HTTPBasicAuth(username, password)) + return response + except Exception as e: + # logger.info(f'Error: {e}') + # logger.info('Error with the connection') + return create_fake_response() + +# Parametrize the test cases with data loaded from the TSV file +@pytest.mark.parametrize("test_case", load_test_data('tests/archivo_test_IRIs.tsv')) +def test_proxy_responses(test_case): + iri = test_case['iri'] + error_dimension = test_case['error_dimension'] + expected_error = test_case['expected_error'] + iri_type = test_case['iri_type'] + comment = test_case['comment'] + + # Make direct and proxy requests + direct_response = make_request_without_proxy(iri) + proxy_response = make_request_with_proxy(iri, 'original') + + + try: + direct_response = requests.get(iri) + except Exception as e: + logger.error(f"Error making direct request to {iri}: {e}") + + try: + proxy_response = requests.get(iri, proxies=PROXIES) + except Exception as e: + logger.error(f"Error making proxy request to {iri} using proxy {PROXY}: {e}") + + # Evaluation based on error_dimension + if error_dimension == 'http-code': + logger.info(f"Comparing direct response status code: expected {expected_error}, got {direct_response.status_code}") + assert int(expected_error) == direct_response.status_code + logger.info(f"Comparing proxy response status code: expected {expected_error}, got {proxy_response.status_code}") + assert int(expected_error) == proxy_response.status_code + + elif error_dimension == 'None': + logger.info(f"Comparing direct response status code for 'None' error dimension: expected 200, got {direct_response.status_code}") + assert direct_response.status_code == 200 + logger.info(f"Comparing proxy response status code for 'None' error dimension: expected 200, got {proxy_response.status_code}") + assert proxy_response.status_code == 200 + + elif error_dimension == 'content': + logger.info(f"Comparing direct response content length: expected 0, got {len(direct_response.content)}") + assert len(direct_response.content) == 0 + logger.info(f"Comparing proxy response content length: expected 0, got {len(proxy_response.content)}") + assert len(proxy_response.content) == 0 + + elif error_dimension == 'dns' or error_dimension == 'transport': + logger.info(f"Comparing direct response status code for unknown error dimension: expected 'error', got '{direct_response}'") + assert 'error' == direct_response.status_code + logger.info(f"Comparing proxy response status code for unknown error dimension: expected 'error', got '{proxy_response.status_code}'") + assert 'error' == proxy_response.status_code + + + +if __name__ == "__main__": + # You can call pytest from within the script + pytest.main([__file__]) + + \ No newline at end of file