"""test_server_referer.py — HTTP Referer при навигации на целевой url (#3247). Проблема: ``page.goto(url)`` playwright по умолчанию НЕ отправляет заголовок Referer. В проде каждая карточка DomClick запрашивается: сайдкар грузит origin (выдачу), затем ``goto(card_url)`` — площадка видит переход прямо на URL карточки без источника. Ручные прогоны с кликами (Referer уходил естественно) брали 91 и 40 карточек без отказов; прод-добор без Referer упирался в отказ на 11-й. Тестируется: 1. referer передаётся В page.goto() ТОЛЬКО для ЦЕЛЕВОГО url (не для origin/якоря); 2. без referer в вызове — page.goto(url) без kwarg'а referer (прежнее поведение); 3. _do_fetch пробрасывает referer сквозь retry-путь один-в-один с _fetch_once. camoufox НЕ запускается — browser/page подделаны. Запуск (из tradein-mvp/browser/):: python -m pytest test_server_referer.py -q """ from __future__ import annotations import asyncio import importlib.util from pathlib import Path from typing import Any 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, "_contexts", {}) monkeypatch.setattr(server, "_anchor_pages", {}) monkeypatch.setattr(server, "_page_counters", {}) monkeypatch.setattr(server, "_locks", {}) monkeypatch.setattr(server, "_last_goto_at", {}) monkeypatch.setattr( server, "_RECYCLE_PAGES_BY_PROVIDER", {p: 10_000 for p in server.PROVIDERS} ) class _FakePage: """Поддельная page: фиксирует (url, kwargs) каждого goto().""" def __init__(self) -> None: self.goto_calls: list[tuple[str, dict[str, Any]]] = [] self.closed = 0 def is_closed(self) -> bool: return self.closed > 0 async def route(self, pattern: str, handler: Any) -> None: return None async def goto(self, url: str, **kwargs: Any) -> None: self.goto_calls.append((url, kwargs)) async def wait_for_timeout(self, ms: int) -> None: return None async def content(self) -> str: return "ok" async def close(self) -> None: self.closed += 1 class _FakeBrowser: def __init__(self) -> None: self.pages: list[_FakePage] = [] async def new_page(self) -> _FakePage: page = _FakePage() self.pages.append(page) return page def test_referer_passed_only_to_target_goto() -> None: browser = _FakeBrowser() server._browsers["domclick"] = browser html = asyncio.run( server._fetch_once( "domclick", "https://domclick.ru/card/1", origin="https://domclick.ru/serp", referer="https://domclick.ru/serp", ) ) assert "__SSR_STATE__" in html assert len(browser.pages) == 1 page = browser.pages[0] # Два goto: origin (без referer), затем target (с referer). assert len(page.goto_calls) == 2 origin_url, origin_kwargs = page.goto_calls[0] target_url, target_kwargs = page.goto_calls[1] assert origin_url == "https://domclick.ru/serp" assert "referer" not in origin_kwargs, "origin-навигация НЕ должна получать referer" assert target_url == "https://domclick.ru/card/1" assert target_kwargs.get("referer") == "https://domclick.ru/serp" def test_referer_absent_by_default_no_kwarg() -> None: """Без referer в вызове — goto(url) без kwarg'а referer вовсе (прежнее поведение).""" browser = _FakeBrowser() server._browsers["avito"] = browser asyncio.run(server._fetch_once("avito", "https://www.avito.ru/card/1")) page = browser.pages[0] assert len(page.goto_calls) == 1 _, kwargs = page.goto_calls[0] assert "referer" not in kwargs def test_referer_none_explicit_no_kwarg() -> None: """referer=None явно — то же самое: без ключа в kwargs (falsy-check в _fetch_once).""" browser = _FakeBrowser() server._browsers["avito"] = browser asyncio.run(server._fetch_once("avito", "https://www.avito.ru/card/2", referer=None)) page = browser.pages[0] _, kwargs = page.goto_calls[0] assert "referer" not in kwargs def test_do_fetch_passes_referer_through() -> None: """_do_fetch (обёртка с retry) пробрасывает referer в _fetch_once один-в-один.""" browser = _FakeBrowser() server._browsers["domclick"] = browser asyncio.run( server._do_fetch( "domclick", "https://domclick.ru/card/9", referer="https://domclick.ru/serp", ) ) page = browser.pages[0] _, kwargs = page.goto_calls[0] assert kwargs.get("referer") == "https://domclick.ru/serp"