test(tradein/browser): домен инжектируемых кук — регистрируемый, не хост из url
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m27s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m27s
Новый test_server_cookie_domain.py: схлопывание поддомена, хост из двух лейблов без изменений, оба места инъекции зовут одну функцию. Три существующих теста закрепляли как раз то поведение, которое оказалось багом (.ekaterinburg.domclick.ru / .www.avito.ru / .realty.yandex.ru), — переведены на новый контракт, докстринги объясняют почему. Прогон сайдкара целиком: 206 passed.
This commit is contained in:
parent
14f2518fc4
commit
18ae509f98
3 changed files with 132 additions and 7 deletions
112
tradein-mvp/browser/test_server_cookie_domain.py
Normal file
112
tradein-mvp/browser/test_server_cookie_domain.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""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
|
||||
|
|
@ -240,7 +240,12 @@ def test_reuse_context_false_uses_new_page_every_time() -> None:
|
|||
def test_reuse_context_false_still_injects_cookies_into_page_context(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Golden-parity: cookies без reuse_context вливаются в page.context, как раньше."""
|
||||
"""Golden-parity: cookies без reuse_context вливаются в page.context, как раньше.
|
||||
|
||||
Домен с #3261 — регистрируемый (.yandex.ru), а не хост из url
|
||||
(.realty.yandex.ru): оба места инъекции считают его одной функцией, иначе
|
||||
поведение путей reuse_context=true/false разъехалось бы.
|
||||
"""
|
||||
|
||||
class _FakePageContext:
|
||||
def __init__(self) -> None:
|
||||
|
|
@ -269,7 +274,7 @@ def test_reuse_context_false_still_injects_cookies_into_page_context(
|
|||
)
|
||||
|
||||
assert browser.page.context.add_cookies_calls == [
|
||||
[{"name": "sid", "value": "x", "domain": ".realty.yandex.ru", "path": "/"}]
|
||||
[{"name": "sid", "value": "x", "domain": ".yandex.ru", "path": "/"}]
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -395,7 +395,14 @@ def test_fetch_once_single_goto_when_origin_none(monkeypatch: pytest.MonkeyPatch
|
|||
def test_fetch_once_injects_cookies_before_goto_with_derived_domain(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""cookies передан → context.add_cookies() ДО goto(url), domain = "." + hostname."""
|
||||
"""cookies передан → context.add_cookies() ДО goto(url), domain = регистрируемый.
|
||||
|
||||
Домен НЕ хост из url: для карточки на ekaterinburg.domclick.ru куки ложатся
|
||||
на .domclick.ru (#3261). Куки поддомена родительские не заменяют, а
|
||||
сосуществуют с ними, и площадка получала две куки с одним именем — первой
|
||||
нашу протухшую. Раньше этот тест закреплял ровно то поведение, которое
|
||||
оказалось багом.
|
||||
"""
|
||||
page = _OriginTrackingPage()
|
||||
server._browsers["cian"] = _OriginTrackingBrowser(page)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -417,13 +424,13 @@ def test_fetch_once_injects_cookies_before_goto_with_derived_domain(
|
|||
assert {
|
||||
"name": "CAS_ID",
|
||||
"value": "12345",
|
||||
"domain": ".ekaterinburg.domclick.ru",
|
||||
"domain": ".domclick.ru",
|
||||
"path": "/",
|
||||
} in injected
|
||||
assert {
|
||||
"name": "qrator_jsid2",
|
||||
"value": "abc",
|
||||
"domain": ".ekaterinburg.domclick.ru",
|
||||
"domain": ".domclick.ru",
|
||||
"path": "/",
|
||||
} in injected
|
||||
# Инъекция ДО навигации на целевой url — единственный goto остаётся ровно url.
|
||||
|
|
@ -452,7 +459,8 @@ def test_fetch_once_cookie_domain_derivation_is_generic_not_domclick_hardcoded(
|
|||
"""Domain derivation работает для ЛЮБОГО хоста, не только domclick.ru.
|
||||
|
||||
Доказывает, что механизм провайдер-агностичен: hardcode ".domclick.ru" НЕ
|
||||
используется — тот же код с avito.ru url выводит ".www.avito.ru".
|
||||
используется — тот же код с avito.ru url выводит ".avito.ru", схлопывая
|
||||
поддомен www ровно так же, как схлопывает ekaterinburg у Домклика (#3261).
|
||||
"""
|
||||
page = _OriginTrackingPage()
|
||||
server._browsers["avito"] = _OriginTrackingBrowser(page)
|
||||
|
|
@ -469,7 +477,7 @@ def test_fetch_once_cookie_domain_derivation_is_generic_not_domclick_hardcoded(
|
|||
|
||||
injected = page.context.add_cookies_calls[0]
|
||||
assert injected == [
|
||||
{"name": "sessionid", "value": "xyz", "domain": ".www.avito.ru", "path": "/"}
|
||||
{"name": "sessionid", "value": "xyz", "domain": ".avito.ru", "path": "/"}
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue