feat: add dead proxy reporting to source API
When report_url is configured, POST evicted proxy list as JSON after
each health test cycle. Fire-and-forget: failures are logged at debug
level. Payload format: {"dead": [{"proto": "socks5", "proxy": "host:port"}]}.
This commit is contained in:
@@ -257,6 +257,70 @@ class TestProxyPoolHealthTests:
|
||||
assert pool._proxies["socks5://10.0.0.1:1080"].alive is True
|
||||
|
||||
|
||||
class TestProxyPoolReport:
|
||||
"""Test dead proxy reporting."""
|
||||
|
||||
def test_report_called_on_eviction(self):
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
cfg = ProxyPoolConfig(sources=[], report_url="http://api:8081/report", max_fails=1)
|
||||
pool = ProxyPool(cfg, [], timeout=10.0)
|
||||
|
||||
now = time.time()
|
||||
hop = ChainHop(proto="socks5", host="10.0.0.1", port=1080)
|
||||
pool._proxies["socks5://10.0.0.1:1080"] = ProxyEntry(
|
||||
hop=hop, alive=False, last_seen=now, fails=0,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(pool, "_test_proxy", new_callable=AsyncMock, return_value=False),
|
||||
patch.object(pool, "_report_dead", new_callable=AsyncMock) as mock_report,
|
||||
):
|
||||
asyncio.run(pool._run_health_tests())
|
||||
# proxy should be evicted (fails=1 >= max_fails=1)
|
||||
assert "socks5://10.0.0.1:1080" not in pool._proxies
|
||||
mock_report.assert_called_once()
|
||||
keys = mock_report.call_args[0][0]
|
||||
assert "socks5://10.0.0.1:1080" in keys
|
||||
|
||||
def test_report_not_called_without_url(self):
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
cfg = ProxyPoolConfig(sources=[], max_fails=1) # no report_url
|
||||
pool = ProxyPool(cfg, [], timeout=10.0)
|
||||
|
||||
now = time.time()
|
||||
hop = ChainHop(proto="socks5", host="10.0.0.1", port=1080)
|
||||
pool._proxies["socks5://10.0.0.1:1080"] = ProxyEntry(
|
||||
hop=hop, alive=False, last_seen=now, fails=0,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(pool, "_test_proxy", new_callable=AsyncMock, return_value=False),
|
||||
patch.object(pool, "_report_dead", new_callable=AsyncMock) as mock_report,
|
||||
):
|
||||
asyncio.run(pool._run_health_tests())
|
||||
mock_report.assert_not_called()
|
||||
|
||||
def test_report_sync_payload(self):
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
cfg = ProxyPoolConfig(sources=[], report_url="http://api:8081/report")
|
||||
pool = ProxyPool(cfg, [], timeout=10.0)
|
||||
|
||||
dead = [{"proto": "socks5", "proxy": "10.0.0.1:1080"}]
|
||||
with patch("s5p.pool.urllib.request.urlopen", new_callable=MagicMock) as mock_open:
|
||||
mock_open.return_value.__enter__ = MagicMock()
|
||||
mock_open.return_value.__exit__ = MagicMock(return_value=False)
|
||||
pool._report_sync(dead)
|
||||
req = mock_open.call_args[0][0]
|
||||
assert req.method == "POST"
|
||||
assert req.full_url == "http://api:8081/report"
|
||||
assert b'"dead"' in req.data
|
||||
|
||||
|
||||
class TestProxyPoolStaleExpiry:
|
||||
"""Test stale proxy eviction."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user