pyproxytools 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchmark/benchmark.py +165 -0
- benchmark/utils/__init__.py +0 -0
- benchmark/utils/html.py +179 -0
- benchmark/utils/req.py +43 -0
- pyproxy/__init__.py +13 -0
- pyproxy/handlers/__init__.py +0 -0
- pyproxy/handlers/client.py +126 -0
- pyproxy/handlers/http.py +197 -0
- pyproxy/handlers/https.py +308 -0
- pyproxy/modules/__init__.py +0 -0
- pyproxy/modules/cancel_inspect.py +83 -0
- pyproxy/modules/custom_header.py +78 -0
- pyproxy/modules/filter.py +151 -0
- pyproxy/modules/shortcuts.py +85 -0
- pyproxy/monitoring/__init__.py +0 -0
- pyproxy/monitoring/web.py +279 -0
- pyproxy/pyproxy.py +107 -0
- pyproxy/server.py +334 -0
- pyproxy/utils/__init__.py +0 -0
- pyproxy/utils/args.py +176 -0
- pyproxy/utils/config.py +110 -0
- pyproxy/utils/crypto.py +52 -0
- pyproxy/utils/http_req.py +53 -0
- pyproxy/utils/logger.py +46 -0
- pyproxy/utils/version.py +0 -0
- pyproxytools-0.3.2.dist-info/METADATA +130 -0
- pyproxytools-0.3.2.dist-info/RECORD +40 -0
- pyproxytools-0.3.2.dist-info/WHEEL +5 -0
- pyproxytools-0.3.2.dist-info/entry_points.txt +2 -0
- pyproxytools-0.3.2.dist-info/licenses/LICENSE +21 -0
- pyproxytools-0.3.2.dist-info/top_level.txt +3 -0
- tests/modules/__init__.py +0 -0
- tests/modules/test_cancel_inspect.py +67 -0
- tests/modules/test_custom_header.py +70 -0
- tests/modules/test_filter.py +185 -0
- tests/modules/test_shortcuts.py +119 -0
- tests/utils/__init__.py +0 -0
- tests/utils/test_crypto.py +110 -0
- tests/utils/test_http_req.py +69 -0
- tests/utils/test_logger.py +68 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
test_shortcuts.py
|
|
3
|
+
|
|
4
|
+
This module contains unit tests for the `shortcuts.py` module.
|
|
5
|
+
It verifies the correct functionality of loading shortcuts and resolving aliases.
|
|
6
|
+
|
|
7
|
+
Tested Functions:
|
|
8
|
+
- load_shortcuts: Ensures that the shortcut file is correctly loaded
|
|
9
|
+
and the alias-URL mappings are correct.
|
|
10
|
+
- shortcuts_process: Ensures that alias requests are correctly processed
|
|
11
|
+
and resolved to their corresponding URLs.
|
|
12
|
+
|
|
13
|
+
Test Cases:
|
|
14
|
+
- TestLoadShortcuts: Checks the correct loading of alias-URL mappings from the file.
|
|
15
|
+
- TestShortcutsProcess: Verifies that alias requests are correctly resolved to URLs.
|
|
16
|
+
- TestLoadShortcutsFileNotFound: Verifies that a FileNotFoundError is raised
|
|
17
|
+
when the shortcuts file is missing.
|
|
18
|
+
- TestShortcutsProcessWithAliasRequest: Verifies that the process correctly
|
|
19
|
+
resolves alias requests to URLs.
|
|
20
|
+
- TestShortcutsProcessFileMonitor: Verifies that the file monitor thread correctly
|
|
21
|
+
updates the shortcuts when the file is changed.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import unittest
|
|
25
|
+
import multiprocessing
|
|
26
|
+
from unittest.mock import patch, mock_open
|
|
27
|
+
from pyproxy.modules.shortcuts import load_shortcuts, shortcuts_process
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestShortcuts(unittest.TestCase):
|
|
31
|
+
"""
|
|
32
|
+
Test suite for the shortcuts module.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def setUp(self):
|
|
36
|
+
"""Sets up the common resources for tests."""
|
|
37
|
+
self.queue = multiprocessing.Queue()
|
|
38
|
+
self.result_queue = multiprocessing.Queue()
|
|
39
|
+
|
|
40
|
+
def tearDown(self):
|
|
41
|
+
"""Cleans up after each test."""
|
|
42
|
+
while not self.queue.empty():
|
|
43
|
+
self.queue.get_nowait()
|
|
44
|
+
while not self.result_queue.empty():
|
|
45
|
+
self.result_queue.get_nowait()
|
|
46
|
+
|
|
47
|
+
def test_load_shortcuts(self):
|
|
48
|
+
"""Tests if the shortcuts are correctly loaded from the file."""
|
|
49
|
+
with patch(
|
|
50
|
+
"builtins.open",
|
|
51
|
+
new_callable=mock_open,
|
|
52
|
+
read_data="alias1=http://example.com\nalias2=http://test.com",
|
|
53
|
+
):
|
|
54
|
+
shortcuts = load_shortcuts("shortcuts.txt")
|
|
55
|
+
self.assertEqual(shortcuts["alias1"], "http://example.com")
|
|
56
|
+
self.assertEqual(shortcuts["alias2"], "http://test.com")
|
|
57
|
+
self.assertIsInstance(shortcuts, dict)
|
|
58
|
+
|
|
59
|
+
@patch("builtins.open", side_effect=FileNotFoundError("File not found"))
|
|
60
|
+
def test_load_shortcuts_file_not_found(self, _mock_file):
|
|
61
|
+
"""Tests that a FileNotFoundError is raised when the shortcuts file is missing."""
|
|
62
|
+
with self.assertRaises(FileNotFoundError):
|
|
63
|
+
load_shortcuts("invalid_file.txt")
|
|
64
|
+
|
|
65
|
+
def _test_shortcuts_process_helper(
|
|
66
|
+
self, alias, expected_url, patch_data="alias1=http://example.com"
|
|
67
|
+
):
|
|
68
|
+
"""Helper method to test shortcuts_process with different alias requests."""
|
|
69
|
+
with patch("builtins.open", new_callable=mock_open, read_data=patch_data):
|
|
70
|
+
process = multiprocessing.Process(
|
|
71
|
+
target=shortcuts_process,
|
|
72
|
+
args=(self.queue, self.result_queue, "shortcuts.txt"),
|
|
73
|
+
)
|
|
74
|
+
process.start()
|
|
75
|
+
|
|
76
|
+
self.queue.put(alias)
|
|
77
|
+
|
|
78
|
+
result = self.result_queue.get(timeout=2)
|
|
79
|
+
self.assertEqual(result, expected_url)
|
|
80
|
+
|
|
81
|
+
process.terminate()
|
|
82
|
+
process.join()
|
|
83
|
+
|
|
84
|
+
def test_shortcuts_process(self):
|
|
85
|
+
"""Tests if alias requests are correctly resolved to URLs."""
|
|
86
|
+
self._test_shortcuts_process_helper("alias1", "http://example.com")
|
|
87
|
+
|
|
88
|
+
def test_shortcuts_process_invalid_alias(self):
|
|
89
|
+
"""Tests if an invalid alias returns None."""
|
|
90
|
+
self._test_shortcuts_process_helper("invalid_alias", None)
|
|
91
|
+
|
|
92
|
+
def test_shortcuts_process_with_multiple_aliases(self):
|
|
93
|
+
"""Tests if multiple alias requests are correctly resolved."""
|
|
94
|
+
with patch(
|
|
95
|
+
"builtins.open",
|
|
96
|
+
new_callable=mock_open,
|
|
97
|
+
read_data="alias1=http://example.com\nalias2=http://test.com",
|
|
98
|
+
):
|
|
99
|
+
process = multiprocessing.Process(
|
|
100
|
+
target=shortcuts_process,
|
|
101
|
+
args=(self.queue, self.result_queue, "shortcuts.txt"),
|
|
102
|
+
)
|
|
103
|
+
process.start()
|
|
104
|
+
|
|
105
|
+
self.queue.put("alias1")
|
|
106
|
+
self.queue.put("alias2")
|
|
107
|
+
|
|
108
|
+
result1 = self.result_queue.get(timeout=2)
|
|
109
|
+
result2 = self.result_queue.get(timeout=2)
|
|
110
|
+
|
|
111
|
+
self.assertEqual(result1, "http://example.com")
|
|
112
|
+
self.assertEqual(result2, "http://test.com")
|
|
113
|
+
|
|
114
|
+
process.terminate()
|
|
115
|
+
process.join()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
unittest.main()
|
tests/utils/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests.utils.test_crypto.py
|
|
3
|
+
|
|
4
|
+
This module contains unit tests for the `crypto.py` module in the `pyproxy.utils` package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from OpenSSL import crypto
|
|
11
|
+
from pyproxy.utils.crypto import generate_certificate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestCrypto(unittest.TestCase):
|
|
15
|
+
"""
|
|
16
|
+
Test suite for the crypto module.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def setUp(self):
|
|
20
|
+
"""
|
|
21
|
+
Set up a fake CA certificate and private key for testing.
|
|
22
|
+
"""
|
|
23
|
+
self.certs_folder = tempfile.mkdtemp()
|
|
24
|
+
self.domain = "example.com"
|
|
25
|
+
self.ca_cert_path = os.path.join(self.certs_folder, "ca_cert.pem")
|
|
26
|
+
self.ca_key_path = os.path.join(self.certs_folder, "ca_key.pem")
|
|
27
|
+
|
|
28
|
+
self._generate_fake_ca()
|
|
29
|
+
|
|
30
|
+
def _generate_fake_ca(self):
|
|
31
|
+
"""
|
|
32
|
+
Generate a fake self-signed CA certificate and key for testing purposes.
|
|
33
|
+
"""
|
|
34
|
+
ca_key = crypto.PKey()
|
|
35
|
+
ca_key.generate_key(crypto.TYPE_RSA, 2048)
|
|
36
|
+
|
|
37
|
+
ca_cert = crypto.X509()
|
|
38
|
+
ca_cert.set_serial_number(1000)
|
|
39
|
+
ca_cert.get_subject().CN = "Fake CA"
|
|
40
|
+
ca_cert.gmtime_adj_notBefore(0)
|
|
41
|
+
ca_cert.gmtime_adj_notAfter(365 * 24 * 60 * 60)
|
|
42
|
+
ca_cert.set_issuer(ca_cert.get_subject())
|
|
43
|
+
ca_cert.set_pubkey(ca_key)
|
|
44
|
+
|
|
45
|
+
ca_cert.sign(ca_key, "sha256")
|
|
46
|
+
|
|
47
|
+
with open(self.ca_cert_path, "wb") as cert_file:
|
|
48
|
+
cert_file.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca_cert))
|
|
49
|
+
with open(self.ca_key_path, "wb") as key_file:
|
|
50
|
+
key_file.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, ca_key))
|
|
51
|
+
|
|
52
|
+
def test_generate_certificate(self):
|
|
53
|
+
"""
|
|
54
|
+
Test the `generate_certificate` function to ensure it generates a certificate
|
|
55
|
+
and private key file for a given domain.
|
|
56
|
+
"""
|
|
57
|
+
if not self.certs_folder.endswith("/"):
|
|
58
|
+
self.certs_folder += "/"
|
|
59
|
+
cert_path, key_path = generate_certificate(
|
|
60
|
+
self.domain, self.certs_folder, self.ca_cert_path, self.ca_key_path
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expected_cert_path = os.path.join(self.certs_folder, f"{self.domain}.pem")
|
|
64
|
+
expected_key_path = os.path.join(self.certs_folder, f"{self.domain}.key")
|
|
65
|
+
|
|
66
|
+
self.assertEqual(cert_path, expected_cert_path)
|
|
67
|
+
self.assertEqual(key_path, expected_key_path)
|
|
68
|
+
|
|
69
|
+
self.assertTrue(os.path.exists(cert_path))
|
|
70
|
+
self.assertTrue(os.path.exists(key_path))
|
|
71
|
+
|
|
72
|
+
with open(cert_path, "rb") as cert_file:
|
|
73
|
+
cert_data = cert_file.read()
|
|
74
|
+
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data)
|
|
75
|
+
|
|
76
|
+
with open(key_path, "rb") as key_file:
|
|
77
|
+
key_data = key_file.read()
|
|
78
|
+
key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data)
|
|
79
|
+
|
|
80
|
+
self.assertEqual(
|
|
81
|
+
crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()),
|
|
82
|
+
crypto.dump_publickey(crypto.FILETYPE_PEM, key),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def tearDown(self):
|
|
86
|
+
"""
|
|
87
|
+
Cleanup method executed after each test.
|
|
88
|
+
|
|
89
|
+
- Deletes the generated certificate and key files if they exist.
|
|
90
|
+
- Removes the fake CA files.
|
|
91
|
+
"""
|
|
92
|
+
cert_path = os.path.join(self.certs_folder, f"{self.domain}.pem")
|
|
93
|
+
key_path = os.path.join(self.certs_folder, f"{self.domain}.key")
|
|
94
|
+
|
|
95
|
+
if os.path.exists(cert_path):
|
|
96
|
+
os.remove(cert_path)
|
|
97
|
+
if os.path.exists(key_path):
|
|
98
|
+
os.remove(key_path)
|
|
99
|
+
|
|
100
|
+
if os.path.exists(self.ca_cert_path):
|
|
101
|
+
os.remove(self.ca_cert_path)
|
|
102
|
+
if os.path.exists(self.ca_key_path):
|
|
103
|
+
os.remove(self.ca_key_path)
|
|
104
|
+
|
|
105
|
+
if os.path.exists(self.certs_folder):
|
|
106
|
+
os.rmdir(self.certs_folder)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
unittest.main()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests.utils.test_http_req.py
|
|
3
|
+
|
|
4
|
+
This module contains unit tests for the `http_req.py` module in the `pyproxy.utils` package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
from pyproxy.utils.http_req import extract_headers, parse_url
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestHttpReq(unittest.TestCase):
|
|
12
|
+
"""
|
|
13
|
+
Test suite for the HTTP request utilities.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def test_extract_headers(self):
|
|
17
|
+
"""
|
|
18
|
+
Test the `extract_headers` function to ensure it correctly parses the headers
|
|
19
|
+
from an HTTP request string.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
request_str = """GET / HTTP/1.1
|
|
23
|
+
Host: example.com
|
|
24
|
+
User-Agent: Mozilla/5.0
|
|
25
|
+
Accept: */*
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
expected_headers = {
|
|
29
|
+
"Host": "example.com",
|
|
30
|
+
"User-Agent": "Mozilla/5.0",
|
|
31
|
+
"Accept": "*/*",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
headers = extract_headers(request_str)
|
|
35
|
+
self.assertEqual(headers, expected_headers)
|
|
36
|
+
|
|
37
|
+
def test_parse_url(self):
|
|
38
|
+
"""
|
|
39
|
+
Test the `parse_url` function to ensure it correctly extracts the host and port
|
|
40
|
+
from a URL.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
url = "http://example.com:8080/path/to/resource"
|
|
44
|
+
expected_host, expected_port = "example.com", 8080
|
|
45
|
+
host, port = parse_url(url)
|
|
46
|
+
self.assertEqual(host, expected_host)
|
|
47
|
+
self.assertEqual(port, expected_port)
|
|
48
|
+
|
|
49
|
+
url = "http://example.com/path/to/resource"
|
|
50
|
+
expected_host, expected_port = "example.com", 80
|
|
51
|
+
host, port = parse_url(url)
|
|
52
|
+
self.assertEqual(host, expected_host)
|
|
53
|
+
self.assertEqual(port, expected_port)
|
|
54
|
+
|
|
55
|
+
url = "example.com:9090"
|
|
56
|
+
expected_host, expected_port = "example.com", 9090
|
|
57
|
+
host, port = parse_url(url)
|
|
58
|
+
self.assertEqual(host, expected_host)
|
|
59
|
+
self.assertEqual(port, expected_port)
|
|
60
|
+
|
|
61
|
+
url = "example.com"
|
|
62
|
+
expected_host, expected_port = "example.com", 80
|
|
63
|
+
host, port = parse_url(url)
|
|
64
|
+
self.assertEqual(host, expected_host)
|
|
65
|
+
self.assertEqual(port, expected_port)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
unittest.main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tests.utils.test_logger.py
|
|
3
|
+
|
|
4
|
+
This module contains unit tests for the `logger.py` module.
|
|
5
|
+
It verifies the correct configuration of both console and file loggers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import unittest
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from unittest.mock import patch, MagicMock
|
|
12
|
+
from pyproxy.utils.logger import configure_console_logger, configure_file_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestLogger(unittest.TestCase):
|
|
16
|
+
"""
|
|
17
|
+
Test suite for the logger module.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@patch("sys.stdout")
|
|
21
|
+
def test_configure_console_logger(self, mock_stdout):
|
|
22
|
+
"""
|
|
23
|
+
Test that the console logger is correctly configured.
|
|
24
|
+
|
|
25
|
+
- Ensures the logger has at least one handler.
|
|
26
|
+
- Checks that the log level is set to INFO.
|
|
27
|
+
- Verifies that the handler is a StreamHandler.
|
|
28
|
+
"""
|
|
29
|
+
logger = configure_console_logger()
|
|
30
|
+
|
|
31
|
+
self.assertTrue(logger.hasHandlers())
|
|
32
|
+
self.assertEqual(logger.level, logging.INFO)
|
|
33
|
+
handler_types = [type(handler) for handler in logger.handlers]
|
|
34
|
+
self.assertIn(logging.StreamHandler, handler_types)
|
|
35
|
+
|
|
36
|
+
@patch("logging.FileHandler")
|
|
37
|
+
def test_configure_file_logger(self, mock_file_handler):
|
|
38
|
+
"""
|
|
39
|
+
Test that the file logger is correctly configured.
|
|
40
|
+
|
|
41
|
+
- Uses a mock for FileHandler to avoid creating actual files.
|
|
42
|
+
- Ensures the logger has at least one handler.
|
|
43
|
+
- Checks that the log level is set to INFO.
|
|
44
|
+
- Verifies that FileHandler is called with the correct log file path.
|
|
45
|
+
"""
|
|
46
|
+
mock_handler_instance = MagicMock()
|
|
47
|
+
mock_file_handler.return_value = mock_handler_instance
|
|
48
|
+
|
|
49
|
+
log_path = "logs/test.log"
|
|
50
|
+
logger = configure_file_logger(log_path, "TestLogger")
|
|
51
|
+
|
|
52
|
+
self.assertTrue(logger.hasHandlers())
|
|
53
|
+
self.assertEqual(logger.level, logging.INFO)
|
|
54
|
+
mock_file_handler.assert_called_once_with(log_path)
|
|
55
|
+
|
|
56
|
+
def tearDown(self):
|
|
57
|
+
"""
|
|
58
|
+
Cleanup method executed after each test.
|
|
59
|
+
|
|
60
|
+
- Deletes the test log file if it exists.
|
|
61
|
+
"""
|
|
62
|
+
log_file = "logs/test.log"
|
|
63
|
+
if os.path.exists(log_file):
|
|
64
|
+
os.remove(log_file)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
unittest.main()
|