"""test_server_proxy_override.py — динамический прокси из тела /fetch (#2164 P4). Проверяет: 1. _resolve_proxy_override — берёт body["proxy"] (kind http/https), None если нет / если proxy_kind несовместим (socks5 → camoufox не умеет); 2. политика relaunch «только при реальной смене прокси» через _ensure_browser: - инстанса нет → launch с proxy_override; - тот же прокси → НЕ релончим (launch не зовётся повторно); - другой прокси → close + relaunch с новым; - proxy_override=None + инстанс на env → НЕ релончим (golden-parity). camoufox НЕ запускается: _launch_browser мокается заглушкой, которая ставит _browsers/_launched_proxy как реальный launch. Запуск (из tradein-mvp/browser/):: python -m pytest test_server_proxy_override.py -q """ from __future__ import annotations import asyncio import importlib.util from pathlib import Path import pytest _SERVER_PATH = Path(__file__).resolve().parent / "server.py" _spec = importlib.util.spec_from_file_location("tradein_browser_server", _SERVER_PATH) assert _spec is not None and _spec.loader is not None server = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(server) @pytest.fixture(autouse=True) def _reset_state(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(server, "_browsers", {}) monkeypatch.setattr(server, "_browser_cms", {}) monkeypatch.setattr(server, "_page_counters", {}) monkeypatch.setattr(server, "_locks", {}) monkeypatch.setattr(server, "_retry_tasks", {}) monkeypatch.setattr(server, "_last_goto_at", {}) monkeypatch.setattr(server, "_launched_proxy", {}) monkeypatch.setattr(server, "_locks_guard", asyncio.Lock()) # ── _resolve_proxy_override ────────────────────────────────────────────────────── def test_resolve_proxy_override_http() -> None: body = {"proxy": "http://u:p@pool:8080", "proxy_kind": "http"} assert server._resolve_proxy_override(body, "avito") == "http://u:p@pool:8080" def test_resolve_proxy_override_missing_returns_none() -> None: assert server._resolve_proxy_override({"url": "x"}, "avito") is None assert server._resolve_proxy_override({"proxy": ""}, "avito") is None def test_resolve_proxy_override_kind_defaults_http_when_absent() -> None: """proxy без proxy_kind трактуем как http (в пуле kind=http).""" assert server._resolve_proxy_override({"proxy": "http://pool:8080"}, "cian") == ( "http://pool:8080" ) def test_resolve_proxy_override_socks5_ignored() -> None: """socks5 camoufox не умеет → None (env-fallback).""" body = {"proxy": "socks5://u:p@pool:1080", "proxy_kind": "socks5"} assert server._resolve_proxy_override(body, "avito") is None # ── relaunch policy: _ensure_browser ───────────────────────────────────────────── def _install_fake_launch(monkeypatch: pytest.MonkeyPatch) -> list[tuple[str, str | None]]: """Мок _launch_browser: пишет _browsers/_launched_proxy как реальный, пишет calls.""" calls: list[tuple[str, str | None]] = [] async def _fake_launch(provider: str, proxy_override: str | None = None) -> None: calls.append((provider, proxy_override)) server._browsers[provider] = object() server._launched_proxy[provider] = ( proxy_override if proxy_override is not None else server._provider_proxy(provider) ) monkeypatch.setattr(server, "_launch_browser", _fake_launch) return calls def test_ensure_browser_launches_with_override(monkeypatch: pytest.MonkeyPatch) -> None: calls = _install_fake_launch(monkeypatch) async def _run() -> bool: return await server._ensure_browser("avito", proxy_override="http://p1:8080") ok = asyncio.run(_run()) assert ok is True assert calls == [("avito", "http://p1:8080")] assert server._launched_proxy["avito"] == "http://p1:8080" def test_ensure_browser_same_proxy_no_relaunch(monkeypatch: pytest.MonkeyPatch) -> None: calls = _install_fake_launch(monkeypatch) async def _run() -> None: await server._ensure_browser("avito", proxy_override="http://p1:8080") await server._ensure_browser("avito", proxy_override="http://p1:8080") asyncio.run(_run()) # Только один launch — второй вызов с тем же прокси НЕ релончит. assert calls == [("avito", "http://p1:8080")] def test_ensure_browser_changed_proxy_relaunches(monkeypatch: pytest.MonkeyPatch) -> None: calls = _install_fake_launch(monkeypatch) async def _run() -> None: await server._ensure_browser("avito", proxy_override="http://p1:8080") await server._ensure_browser("avito", proxy_override="http://p2:8080") asyncio.run(_run()) assert calls == [("avito", "http://p1:8080"), ("avito", "http://p2:8080")] assert server._launched_proxy["avito"] == "http://p2:8080" def test_ensure_browser_none_override_env_no_relaunch(monkeypatch: pytest.MonkeyPatch) -> None: """proxy_override=None (флаг off) + инстанс на env → НЕ релончим (golden-parity).""" for var in ("BROWSER_PROXY_AVITO", "AVITO_PROXY_URL"): monkeypatch.setenv(var, "http://env:8080") calls = _install_fake_launch(monkeypatch) async def _run() -> None: await server._ensure_browser("avito") # lazy launch на env await server._ensure_browser("avito") # второй раз — env тот же asyncio.run(_run()) assert calls == [("avito", None)] assert server._launched_proxy["avito"] == "http://env:8080" def test_ensure_browser_env_then_pool_relaunches(monkeypatch: pytest.MonkeyPatch) -> None: """env-launched инстанс + пришёл pool-proxy → relaunch (реальная смена).""" for var in ("BROWSER_PROXY_AVITO", "AVITO_PROXY_URL"): monkeypatch.setenv(var, "http://env:8080") calls = _install_fake_launch(monkeypatch) async def _run() -> None: await server._ensure_browser("avito") # env await server._ensure_browser("avito", proxy_override="http://pool:8080") # смена asyncio.run(_run()) assert calls == [("avito", None), ("avito", "http://pool:8080")]