"""test_server_cookie_domain.py — на какой домен ложатся инжектируемые куки (#3261). Сайдкар клал куки на домен из url карточки: для ``ekaterinburg.domclick.ru`` это ``.ekaterinburg.domclick.ru``. Настоящие куки площадки живут на ``.domclick.ru``. Куки поддомена родительские НЕ заменяют, а сосуществуют с ними: как только площадка выдаёт свежий ``qrator_jsid2`` на ``.domclick.ru``, браузер шлёт в одном запросе ДВЕ куки с этим именем, и первой — более специфичную, нашу протухшую. QRATOR читает её и держит страницу на 274-байтном загрузчике. Замер 30.08 (прод, узлы 1 и 9, чередование плеч, по 4 пробы на плечо): прод-поведение — 0 успехов из 4, только шрифты — 0 из 4, куки на ``.domclick.ru`` — 3 из 4. Плечо «только шрифты» тут важно: оно показывает, что чинит именно домен кук, а не сопутствующая правка образа. Тестируется: 1. поддомен схлопывается до регистрируемого домена; 2. хост из двух лейблов остаётся как есть; 3. оба места инъекции (переиспользуемый context и одноразовая страница) зовут одну и ту же функцию, а не считают домен по-своему. camoufox НЕ запускается — browser/context/page подделаны. Запуск (из tradein-mvp/browser/):: python -m pytest test_server_cookie_domain.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, "_contexts", {}) @pytest.mark.parametrize( ("url", "expected"), [ ("https://ekaterinburg.domclick.ru/card/sale__flat__1", ".domclick.ru"), ("https://domclick.ru/card/sale__flat__1", ".domclick.ru"), ("https://www.avito.ru/ekaterinburg/kvartiry/1", ".avito.ru"), ("https://spb.cian.ru/sale/flat/1/", ".cian.ru"), # Хост без поддомена трогать нечего — возвращаем как есть. ("https://cian.ru/sale/flat/1/", ".cian.ru"), # Несколько уровней поддоменов схлопываются до тех же двух лейблов. ("https://a.b.c.domclick.ru/card/1", ".domclick.ru"), ], ) def test_cookie_domain_collapses_to_registrable(url: str, expected: str) -> None: assert server._cookie_domain(url) == expected class _FakeContext: def __init__(self) -> None: self.added: list[dict[str, str]] = [] async def add_cookies(self, cookies: list[dict[str, str]]) -> None: self.added.extend(cookies) async def new_page(self) -> Any: raise AssertionError("не должно вызываться в этом тесте") class _FakeBrowser: def __init__(self, context: _FakeContext) -> None: self._context = context async def new_context(self) -> _FakeContext: return self._context def test_reusable_context_puts_cookies_on_parent_domain() -> None: """Переиспользуемый context (#3118) — основной путь добора карточек.""" ctx = _FakeContext() browser = _FakeBrowser(ctx) asyncio.run( server._get_or_create_context( "domclick", browser, {"qrator_jsid2": "x", "qrator_jsr": "y"}, "https://ekaterinburg.domclick.ru/card/sale__flat__2078475983", ) ) assert {c["domain"] for c in ctx.added} == {".domclick.ru"} assert {c["name"] for c in ctx.added} == {"qrator_jsid2", "qrator_jsr"} def test_both_injection_sites_use_the_same_helper() -> None: """Второе место инъекции (reuse_context=false) не должно считать домен само. Проверка структурная: если кто-то вернёт в любое из двух мест ``f".{urlparse(url).hostname}"``, тест покраснеет — а поймать это поведенчески дороже, чем оно стоит. """ source = _SERVER_PATH.read_text(encoding="utf-8") assert source.count("cookie_domain = _cookie_domain(url)") == 2 assert 'f".{urlparse(url).hostname' not in source