Initial project import

This commit is contained in:
XM
2026-09-18 03:46:58 +08:00
commit b600da4b76
209 changed files with 45904 additions and 0 deletions
+415
View File
@@ -0,0 +1,415 @@
import copy
import json
import threading
import time
import unittest
from concurrent.futures import ThreadPoolExecutor
from unittest.mock import patch
from urllib.parse import urlparse
from services.config import DEFAULT_PROXY_RUNTIME
from services.proxy_service import (
ClearanceBundle,
FlareSolverrClearanceProvider,
ProxySettingsStore,
normalize_proxy_url,
)
class FakeConfig:
def __init__(self, legacy_proxy: str = "", runtime: dict[str, object] | None = None) -> None:
self.legacy_proxy = legacy_proxy
self.runtime = runtime if runtime is not None else copy.deepcopy(DEFAULT_PROXY_RUNTIME)
def get_proxy_settings(self) -> str:
return self.legacy_proxy
def get_proxy_runtime_settings(self) -> dict[str, object]:
return copy.deepcopy(self.runtime)
def make_runtime(**overrides: object) -> dict[str, object]:
runtime = copy.deepcopy(DEFAULT_PROXY_RUNTIME)
clearance = overrides.pop("clearance", None)
runtime.update(overrides)
if clearance is not None:
runtime["clearance"].update(clearance) # type: ignore[index,union-attr]
return runtime
class ProxyServiceTests(unittest.TestCase):
def test_normalize_proxy_url_strips_and_converts_socks_schemes(self) -> None:
self.assertEqual(normalize_proxy_url(" http://proxy.example:8080 "), "http://proxy.example:8080")
self.assertEqual(normalize_proxy_url("\thttps://proxy.example:8443\n"), "https://proxy.example:8443")
self.assertEqual(normalize_proxy_url(" socks://proxy.example:1080 "), "socks5h://proxy.example:1080")
self.assertEqual(normalize_proxy_url("socks5://proxy.example:1080"), "socks5h://proxy.example:1080")
self.assertEqual(normalize_proxy_url(" socks5h://proxy.example:1080 "), "socks5h://proxy.example:1080")
self.assertEqual(normalize_proxy_url(" "), "")
def test_build_session_kwargs_keeps_legacy_global_proxy_when_runtime_disabled(self) -> None:
store = ProxySettingsStore(FakeConfig(legacy_proxy=" http://legacy.example:8080 "))
kwargs = store.build_session_kwargs(impersonate="chrome")
self.assertEqual(kwargs["impersonate"], "chrome")
self.assertEqual(kwargs["proxy"], "http://legacy.example:8080")
def test_runtime_proxy_is_limited_to_upstream_scope_by_default(self) -> None:
runtime = make_runtime(enabled=True, egress_mode="single_proxy", proxy_url="http://runtime.example:8080")
store = ProxySettingsStore(FakeConfig(legacy_proxy="http://legacy.example:8080", runtime=runtime))
self.assertEqual(
store.build_session_kwargs()["proxy"],
"http://legacy.example:8080",
)
self.assertEqual(
store.build_session_kwargs(upstream=True)["proxy"],
"http://runtime.example:8080",
)
def test_account_proxy_wins_over_runtime_and_global_proxy(self) -> None:
runtime = make_runtime(enabled=True, egress_mode="single_proxy", proxy_url="http://runtime.example:8080")
store = ProxySettingsStore(FakeConfig(legacy_proxy="http://legacy.example:8080", runtime=runtime))
kwargs = store.build_session_kwargs(account={"proxy": " socks://account.example:1080 "}, upstream=True)
self.assertEqual(kwargs["proxy"], "socks5h://account.example:1080")
def test_proxy_runtime_single_proxy_wins_over_explicit_and_legacy_proxy_when_enabled(self) -> None:
runtime = make_runtime(enabled=True, egress_mode="single_proxy", proxy_url=" socks5://runtime.example:1080 ")
store = ProxySettingsStore(FakeConfig(legacy_proxy="http://legacy.example:8080", runtime=runtime))
kwargs = store.build_session_kwargs(proxy="http://explicit.example:8080", upstream=True)
self.assertEqual(kwargs["proxy"], "socks5h://runtime.example:1080")
def test_explicit_proxy_wins_over_legacy_global_proxy_when_runtime_disabled(self) -> None:
store = ProxySettingsStore(FakeConfig(legacy_proxy="http://legacy.example:8080"))
kwargs = store.build_session_kwargs(proxy=" socks5://explicit.example:1080 ")
self.assertEqual(kwargs["proxy"], "socks5h://explicit.example:1080")
def test_resource_requests_use_resource_proxy_url_when_configured(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
resource_proxy_url=" socks5://resource.example:1080 ",
)
store = ProxySettingsStore(FakeConfig(legacy_proxy="http://legacy.example:8080", runtime=runtime))
kwargs = store.build_session_kwargs(resource=True, upstream=True)
self.assertEqual(kwargs["proxy"], "socks5h://resource.example:1080")
def test_manual_clearance_merges_cookies_and_preserves_explicit_user_agent(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
clearance={
"enabled": True,
"mode": "manual",
"cf_cookies": "foo=bar; session=ok",
"cf_clearance": "manual-token",
"user_agent": "Manual UA",
},
)
store = ProxySettingsStore(FakeConfig(runtime=runtime))
headers = store.build_headers(headers={"Cookie": "existing=1"}, target_url="https://chatgpt.com/backend-api")
self.assertEqual(headers["User-Agent"], "Manual UA")
self.assertEqual(headers["Cookie"], "existing=1; foo=bar; session=ok; cf_clearance=manual-token")
headers_with_ua = store.build_headers(
headers={"User-Agent": "Caller UA", "Cookie": "cf_clearance=caller-token"},
target_url="https://chatgpt.com/backend-api",
)
self.assertEqual(headers_with_ua["User-Agent"], "Caller UA")
self.assertEqual(headers_with_ua["Cookie"], "cf_clearance=caller-token; foo=bar; session=ok")
self.assertNotIn("cf_clearance=manual-token", headers_with_ua["Cookie"])
def test_flaresolverr_provider_parses_solution_and_filters_cookies_by_host(self) -> None:
calls: list[tuple[str, dict[str, object], dict[str, str], float]] = []
def fake_request(endpoint: str, body: bytes, headers: dict[str, str], timeout: float) -> bytes:
payload = json.loads(body.decode("utf-8"))
calls.append((endpoint, payload, headers, timeout))
return json.dumps(
{
"status": "ok",
"solution": {
"userAgent": "Flare UA",
"cookies": [
{"name": "cf_clearance", "value": "host-token", "domain": ".chatgpt.com"},
{"name": "no_domain", "value": "kept"},
{"name": "wrong_host", "value": "dropped", "domain": "example.net"},
],
},
}
).encode("utf-8")
provider = FlareSolverrClearanceProvider("http://flare.local/", request_method=fake_request)
bundle = provider.get_clearance(
"https://chatgpt.com/backend-api/conversation",
proxy_url="socks5h://proxy.example:1080",
timeout_sec=12,
)
self.assertIsNotNone(bundle)
assert bundle is not None
endpoint, payload, headers, timeout = calls[0]
self.assertEqual(endpoint, "http://flare.local/v1")
self.assertEqual(payload["cmd"], "request.get")
self.assertEqual(payload["url"], "https://chatgpt.com/backend-api/conversation")
self.assertEqual(payload["maxTimeout"], 12000)
self.assertEqual(payload["proxy"], {"url": "socks5h://proxy.example:1080"})
self.assertEqual(headers["Content-Type"], "application/json")
self.assertEqual(timeout, 12)
self.assertEqual(bundle.user_agent, "Flare UA")
self.assertEqual(bundle.cookies, {"cf_clearance": "host-token", "no_domain": "kept"})
def test_flaresolverr_provider_keeps_only_matching_or_no_domain_cookies(self) -> None:
def fake_request(endpoint: str, body: bytes, headers: dict[str, str], timeout: float) -> bytes:
return json.dumps(
{
"status": "ok",
"solution": {
"userAgent": "Filtered UA",
"cookies": [
{"name": "wrong_host", "value": "dropped", "domain": "example.net"},
{"name": "also_wrong", "value": "dropped-too", "domain": ".example.org"},
{"name": "no_domain", "value": "kept"},
],
},
}
).encode("utf-8")
provider = FlareSolverrClearanceProvider("http://flare.local", request_method=fake_request)
bundle = provider.get_clearance("https://chatgpt.com", timeout_sec=5)
self.assertIsNotNone(bundle)
assert bundle is not None
self.assertEqual(bundle.cookies, {"no_domain": "kept"})
self.assertEqual(bundle.user_agent, "Filtered UA")
def test_flaresolverr_provider_drops_all_wrong_domain_cookies(self) -> None:
def fake_request(endpoint: str, body: bytes, headers: dict[str, str], timeout: float) -> bytes:
return json.dumps(
{
"status": "ok",
"solution": {
"cookies": [
{"name": "wrong_host", "value": "dropped", "domain": "example.net"},
],
},
}
).encode("utf-8")
provider = FlareSolverrClearanceProvider("http://flare.local", request_method=fake_request)
self.assertIsNone(provider.get_clearance("https://chatgpt.com", timeout_sec=5))
def test_cached_flaresolverr_bundle_is_merged_by_build_headers_and_can_be_invalidated(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
clearance={
"enabled": True,
"mode": "flaresolverr",
"flaresolverr_url": "http://flare.local",
"timeout_sec": 5,
},
)
bundle = ClearanceBundle(
target_host="chatgpt.com",
proxy_url="http://runtime.example:8080",
cookies={"cf_clearance": "flare-token"},
user_agent="Flare UA",
)
class FakeProvider:
def get_clearance(self, target_url: str, proxy_url: str = "", timeout_sec: int = 60) -> ClearanceBundle | None:
return bundle
store = ProxySettingsStore(FakeConfig(runtime=runtime), clearance_provider_factory=lambda _url: FakeProvider())
store.refresh_clearance(target_url="https://chatgpt.com", force=True)
headers = store.build_headers(headers={"Cookie": "existing=1"}, target_url="https://chatgpt.com/backend-api")
self.assertEqual(headers["User-Agent"], "Flare UA")
self.assertEqual(headers["Cookie"], "existing=1; cf_clearance=flare-token")
store.invalidate_clearance(target_url="https://chatgpt.com")
self.assertEqual(
store.build_headers(headers={"Cookie": "existing=1"}, target_url="https://chatgpt.com/backend-api"),
{"Cookie": "existing=1"},
)
def test_flaresolverr_refresh_failure_keeps_old_cached_bundle(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
clearance={
"enabled": True,
"mode": "flaresolverr",
"flaresolverr_url": "http://flare.local",
"timeout_sec": 5,
},
)
first_bundle = ClearanceBundle(
target_host="chatgpt.com",
proxy_url="http://runtime.example:8080",
cookies={"cf_clearance": "old-token"},
user_agent="Old UA",
)
class FakeProvider:
def __init__(self) -> None:
self.calls = 0
def get_clearance(self, target_url: str, proxy_url: str = "", timeout_sec: int = 60) -> ClearanceBundle | None:
self.calls += 1
return first_bundle if self.calls == 1 else None
provider = FakeProvider()
store = ProxySettingsStore(FakeConfig(runtime=runtime), clearance_provider_factory=lambda _url: provider)
refreshed = store.refresh_clearance(target_url="https://chatgpt.com", force=True)
fallback = store.refresh_clearance(target_url="https://chatgpt.com", force=True)
self.assertIsNotNone(refreshed)
assert refreshed is not None
self.assertEqual(refreshed.cookies, first_bundle.cookies)
self.assertIsNotNone(refreshed.expires_at)
assert refreshed.expires_at is not None
self.assertAlmostEqual(refreshed.expires_at - refreshed.created_at, 3600, places=2)
self.assertIs(fallback, refreshed)
self.assertEqual(provider.calls, 2)
def test_flaresolverr_bundle_expires_after_refresh_interval(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
clearance={
"enabled": True,
"mode": "flaresolverr",
"flaresolverr_url": "http://flare.local",
"timeout_sec": 5,
"refresh_interval": 60,
},
)
class FakeProvider:
def get_clearance(self, target_url: str, proxy_url: str = "", timeout_sec: int = 60) -> ClearanceBundle | None:
return ClearanceBundle(
target_host="chatgpt.com",
proxy_url=proxy_url,
cookies={"cf_clearance": "expiring-token"},
user_agent="Expiring UA",
created_at=1000,
)
store = ProxySettingsStore(FakeConfig(runtime=runtime), clearance_provider_factory=lambda _url: FakeProvider())
with patch("services.proxy_service.time.time", return_value=1000):
bundle = store.refresh_clearance(target_url="https://chatgpt.com", force=True)
self.assertIsNotNone(bundle)
assert bundle is not None
self.assertEqual(bundle.expires_at, 1060)
self.assertTrue(bundle.is_valid_for("chatgpt.com", "http://runtime.example:8080", now=1059))
self.assertFalse(bundle.is_valid_for("chatgpt.com", "http://runtime.example:8080", now=1060))
def test_profile_repr_does_not_expose_clearance_secrets(self) -> None:
runtime = make_runtime(
enabled=True,
clearance={
"enabled": True,
"mode": "manual",
"cf_cookies": "foo=secret-cookie",
"cf_clearance": "secret-clearance",
},
)
profile = ProxySettingsStore(FakeConfig(runtime=runtime)).get_profile()
text = repr(profile)
self.assertNotIn("secret-cookie", text)
self.assertNotIn("secret-clearance", text)
def test_proxy_test_error_redacts_proxy_credentials(self) -> None:
class FailingSession:
def __init__(self, **kwargs: object) -> None:
pass
def get(self, *args: object, **kwargs: object) -> object:
raise RuntimeError("proxy failed for http://user:pass@proxy.example:8080")
def close(self) -> None:
pass
with patch("services.proxy_service.Session", FailingSession):
result = __import__("services.proxy_service", fromlist=["test_proxy"]).test_proxy(
"http://user:pass@proxy.example:8080"
)
self.assertFalse(result["ok"])
self.assertIn("[REDACTED]", result["error"])
self.assertNotIn("user:pass", result["error"])
def test_concurrent_flaresolverr_refresh_uses_single_flight_per_proxy_and_host(self) -> None:
runtime = make_runtime(
enabled=True,
egress_mode="single_proxy",
proxy_url="http://runtime.example:8080",
clearance={
"enabled": True,
"mode": "flaresolverr",
"flaresolverr_url": "http://flare.local",
"timeout_sec": 5,
},
)
class SlowProvider:
def __init__(self) -> None:
self.calls = 0
self.lock = threading.Lock()
def get_clearance(self, target_url: str, proxy_url: str = "", timeout_sec: int = 60) -> ClearanceBundle | None:
with self.lock:
self.calls += 1
time.sleep(0.15)
return ClearanceBundle(
target_host=urlparse(target_url).hostname or "",
proxy_url=proxy_url,
cookies={"cf_clearance": "thread-token"},
user_agent="Thread UA",
)
provider = SlowProvider()
store = ProxySettingsStore(FakeConfig(runtime=runtime), clearance_provider_factory=lambda _url: provider)
workers = 6
start = threading.Barrier(workers)
def refresh() -> ClearanceBundle | None:
start.wait(timeout=5)
return store.refresh_clearance(target_url="https://chatgpt.com", force=True)
with ThreadPoolExecutor(max_workers=workers) as executor:
results = list(executor.map(lambda _index: refresh(), range(workers)))
self.assertEqual(provider.calls, 1)
self.assertTrue(all(result is results[0] for result in results))
self.assertIsNotNone(results[0])
if __name__ == "__main__":
unittest.main()