"""Tests for marketing_intelligence.evasion.proxy.""" import pytest from marketing_intelligence.evasion.proxy import ProxyManager, ProxySession _PROXY_GB = "http://gb.decodo.com:9999" _PROXY_US = "http://us.decodo.com:9999" _PROXY_AUTH = "http://user:secret@proxy.example.com:8080" _PROXY_PLAIN = "http://proxy.example.com:8080" # ─────────────────────────── ProxySession ────────────────────────────────── class TestProxySessionToPlaywrightDict: def test_server_format(self) -> None: s = ProxySession(proxy_url=_PROXY_PLAIN, provider="test") d = s.to_playwright_dict() assert d["server"] == "http://proxy.example.com:8080" def test_no_auth_keys_without_credentials(self) -> None: s = ProxySession(proxy_url=_PROXY_PLAIN, provider="test") d = s.to_playwright_dict() assert "username" not in d assert "password" not in d def test_username_extracted_from_url(self) -> None: s = ProxySession(proxy_url=_PROXY_AUTH, provider="test") assert s.to_playwright_dict()["username"] == "user" def test_password_extracted_from_url(self) -> None: s = ProxySession(proxy_url=_PROXY_AUTH, provider="test") assert s.to_playwright_dict()["password"] == "secret" def test_server_excludes_credentials(self) -> None: s = ProxySession(proxy_url=_PROXY_AUTH, provider="test") server = s.to_playwright_dict()["server"] assert "user" not in server assert "secret" not in server def test_country_and_sticky_id_stored(self) -> None: s = ProxySession( proxy_url=_PROXY_GB, provider="brightdata", country="gb", sticky_id="abc" ) assert s.country == "gb" assert s.sticky_id == "abc" # ─────────────────────────── ProxyManager._country_of ────────────────────── class TestCountryOf: def test_two_letter_prefix_extracted(self) -> None: assert ProxyManager._country_of(_PROXY_GB) == "gb" def test_us_extracted(self) -> None: assert ProxyManager._country_of(_PROXY_US) == "us" def test_long_prefix_returns_none(self) -> None: assert ProxyManager._country_of(_PROXY_PLAIN) is None def test_invalid_url_returns_none(self) -> None: assert ProxyManager._country_of("not-a-url") is None # ─────────────────────────── ProxyManager.available_countries ────────────── class TestAvailableCountries: def test_returns_sorted_country_codes(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") assert m.available_countries() == ["gb", "us"] def test_deduplicates(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_GB], provider="test") assert m.available_countries().count("gb") == 1 def test_excludes_none(self) -> None: m = ProxyManager(proxies=[_PROXY_PLAIN], provider="test") assert m.available_countries() == [] # ─────────────────────────── ProxyManager.acquire ────────────────────────── class TestAcquire: def test_returns_proxy_session(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire() assert isinstance(s, ProxySession) def test_proxy_url_from_list(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire() assert s.proxy_url == _PROXY_GB def test_raises_when_no_proxies(self) -> None: m = ProxyManager(proxies=[], provider="test") with pytest.raises(RuntimeError, match="No proxies configured"): m.acquire() def test_country_filter_selects_matching(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") s = m.acquire(country="gb") assert "gb" in s.proxy_url def test_country_filter_falls_back_when_no_match(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire(country="us") assert isinstance(s, ProxySession) def test_sticky_false_gives_no_sticky_id(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire(sticky=False) assert s.sticky_id is None def test_sticky_true_gives_sticky_id(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire(sticky=True) assert s.sticky_id is not None def test_prefers_unused_proxy(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") s1 = m.acquire() s2 = m.acquire() assert s1.proxy_url != s2.proxy_url def test_reuses_all_when_all_in_use(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s1 = m.acquire() s2 = m.acquire() assert s1.proxy_url == s2.proxy_url == _PROXY_GB # ─────────────────────────── ProxyManager.release ────────────────────────── class TestRelease: def test_removes_from_in_use(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire() assert _PROXY_GB in m._in_use m.release(s) assert _PROXY_GB not in m._in_use def test_double_release_is_safe(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire() m.release(s) m.release(s) # ─────────────────────────── ProxyManager.rotate_on_signal ───────────────── class TestRotateOnSignal: def test_challenge_returns_new_session(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") s = m.acquire() new_s = m.rotate_on_signal(s, "challenge") assert isinstance(new_s, ProxySession) def test_rate_limit_returns_new_session(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") s = m.acquire() new_s = m.rotate_on_signal(s, "rate_limit") assert isinstance(new_s, ProxySession) def test_success_returns_none(self) -> None: m = ProxyManager(proxies=[_PROXY_GB], provider="test") s = m.acquire() assert m.rotate_on_signal(s, "success") is None def test_challenge_releases_and_reacquires(self) -> None: m = ProxyManager(proxies=[_PROXY_GB, _PROXY_US], provider="test") s = m.acquire() new_s = m.rotate_on_signal(s, "challenge") # Exactly one proxy in use after rotation; a new session object returned assert len(m._in_use) == 1 assert new_s is not None assert new_s is not s