247153575d
Tests / tests (map[TOXENV:py310], macos-latest, 3.10) (push) Has been cancelled
Tests / tests (map[TOXENV:py311], macos-latest, 3.11) (push) Has been cancelled
Tests / tests (map[TOXENV:py312], macos-latest, 3.12) (push) Has been cancelled
Tests / tests (map[TOXENV:py313], macos-latest, 3.13) (push) Has been cancelled
85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from curl_cffi.curl import CurlError
|
|
|
|
from scrapling.engines.static import _SyncSessionLogic as FetcherSession, FetcherClient
|
|
from scrapling.engines.toolbelt import ProxyRotator
|
|
|
|
|
|
class TestFetcherSession:
|
|
"""Test FetcherSession functionality"""
|
|
|
|
def test_fetcher_session_creation(self):
|
|
"""Test FetcherSession creation"""
|
|
session = FetcherSession(timeout=30, retries=3, stealthy_headers=True)
|
|
|
|
assert session._default_timeout == 30
|
|
assert session._default_retries == 3
|
|
|
|
def test_fetcher_session_context_manager(self):
|
|
"""Test FetcherSession as a context manager"""
|
|
session = FetcherSession()
|
|
|
|
with session as s:
|
|
assert s == session
|
|
assert session._curl_session is not None
|
|
|
|
# Session should be cleaned up
|
|
|
|
def test_fetcher_session_double_enter(self):
|
|
"""Test error on double entering"""
|
|
session = FetcherSession()
|
|
|
|
with session:
|
|
with pytest.raises(RuntimeError):
|
|
session.__enter__()
|
|
|
|
def test_fetcher_client_creation(self):
|
|
"""Test FetcherClient creation"""
|
|
client = FetcherClient()
|
|
|
|
# Should not have context manager methods
|
|
assert client.__enter__ is None
|
|
assert client.__exit__ is None
|
|
|
|
def test_session_level_proxy_is_applied(self):
|
|
"""Session-level proxy must reach the request, not be silently dropped (#295)"""
|
|
proxy = "http://10.255.255.1:9999"
|
|
|
|
with FetcherSession(proxy=proxy) as session:
|
|
with (
|
|
patch.object(session._curl_session, "request") as mocked_request,
|
|
patch("scrapling.engines.static.ResponseFactory.from_http_request", return_value=MagicMock()),
|
|
):
|
|
session.get("http://example.com")
|
|
|
|
assert mocked_request.call_args.kwargs["proxy"] == proxy
|
|
|
|
def test_per_request_proxy_overrides_session_proxy(self):
|
|
"""A per-request proxy must take precedence over the session-level proxy"""
|
|
request_proxy = "http://10.255.255.2:9999"
|
|
|
|
with FetcherSession(proxy="http://10.255.255.1:9999") as session:
|
|
with (
|
|
patch.object(session._curl_session, "request") as mocked_request,
|
|
patch("scrapling.engines.static.ResponseFactory.from_http_request", return_value=MagicMock()),
|
|
):
|
|
session.get("http://example.com", proxy=request_proxy)
|
|
|
|
assert mocked_request.call_args.kwargs["proxy"] == request_proxy
|
|
|
|
def test_proxy_rotates_per_retry_attempt(self):
|
|
"""With a rotator, every retry attempt must pull a fresh proxy"""
|
|
rotator = ProxyRotator(["http://p1:8080", "http://p2:8080"])
|
|
|
|
with FetcherSession(proxy_rotator=rotator, retries=2, retry_delay=0) as session:
|
|
with (
|
|
patch.object(session._curl_session, "request") as mocked_request,
|
|
patch("scrapling.engines.static.ResponseFactory.from_http_request", return_value=MagicMock()),
|
|
):
|
|
mocked_request.side_effect = [CurlError("transient"), MagicMock()]
|
|
session.get("http://example.com")
|
|
|
|
proxies_used = [call.kwargs["proxy"] for call in mocked_request.call_args_list]
|
|
assert proxies_used == ["http://p1:8080", "http://p2:8080"]
|