From fea054d24f83cde528798ca40112d9436ac1eae4 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 4 Jul 2026 02:55:44 +0300 Subject: [PATCH 1/2] =?UTF-8?q?fix(tradein/scraper-kit):=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=BE=D1=81=20config=3D=20=D0=B2=20avito=20b?= =?UTF-8?q?uild=5Fwarmed=5Fsession=20(#2330)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._avito_detail_kit_warmed_session_config.py | 80 +++++++++++++++++++ .../src/scraper_kit/providers/avito/detail.py | 10 ++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py diff --git a/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py b/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py new file mode 100644 index 00000000..8c350d44 --- /dev/null +++ b/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py @@ -0,0 +1,80 @@ +"""Regression-тест для issue #2330 (Group F2 эпика #2277 scraper_kit migration). + +Баг: `scraper_kit/providers/avito/detail.py::build_warmed_session()` строил detail- +сессию через `_build_detail_session()` БЕЗ параметра `config` вообще — в отличие от +`fetch_detail()` в том же файле, у которой `config=` есть и штатно пробрасывает +sticky-прокси в `_build_detail_session(config)`. Т.к. kit-`_build_detail_session` (в +отличие от legacy `app/services/scrapers/avito_detail.py`, который читает +`settings.scraper_proxy_url` напрямую) читает прокси ТОЛЬКО из инжектируемого +`config.scraper_proxy_url`, отсутствие параметра означало, что `build_warmed_session` +физически не могло получить прокси ни при каких условиях — ломая прод-дефолт путь +(avito_detail_backfill_use_curl=True) тихой потерей sticky backconnect-proxy на +warm-batch операциях. См. `test_scraper_kit_group_c_backfill_kit_parity.py` (issue +#2310), который явно документирует этот баг как flagged follow-up. + +Этот файл доказывает РАЗНИЦУ в поведении, а не просто "функция не падает": + 1. `test_build_warmed_session_signature_now_accepts_config` — до фикса вызов + `build_warmed_session(config=...)` был бы `TypeError` (функция принимала 0 + параметров). Сигнатура теперь содержит `config`. + 2. `test_build_warmed_session_with_config_propagates_real_proxy_to_session` — + реальный (не замоканный) `curl_cffi.requests.AsyncSession`, построенный + `build_warmed_session(config=...)`, содержит РЕАЛЬНЫЙ `proxy_url` из config в + `session.proxies` — то же самое поведение, что и `fetch_detail(config=...)`. + 3. `test_build_warmed_session_without_config_has_no_proxy` — вызов без `config` + (дефолт `None`, как было ДО фикса единственно возможное поведение) сессия + остаётся без прокси — документирует, что caller обязан явно передать `config`, + иначе прокси молча теряется (это и есть остающийся риск в + `app/tasks/avito_detail_backfill.py`, отдельный будущий шаг вне scope #2330). + +`warm_up_session` патчится no-op'ом — тест проверяет ТОЛЬКО построение сессии +(`_build_detail_session`), не сетевой прогрев. +""" + +from __future__ import annotations + +import inspect +from types import SimpleNamespace +from unittest.mock import AsyncMock, patch + +from scraper_kit.providers.avito import detail as kit_detail +from scraper_kit.providers.avito.detail import build_warmed_session + + +def _fake_config(proxy_url: str | None) -> SimpleNamespace: + """Минимальный duck-typed ScraperConfig — _build_detail_session читает только + `.scraper_proxy_url` (structural typing, `Protocol` не требует явной реализации).""" + return SimpleNamespace(scraper_proxy_url=proxy_url) + + +def test_build_warmed_session_signature_now_accepts_config() -> None: + """До фикса #2330 `build_warmed_session()` не принимала параметров вовсе — + `build_warmed_session(config=cfg)` падал бы `TypeError: unexpected keyword + argument 'config'`. Проверяем, что параметр реально появился в сигнатуре.""" + params = inspect.signature(build_warmed_session).parameters + assert "config" in params + assert params["config"].default is None + + +async def test_build_warmed_session_with_config_propagates_real_proxy_to_session() -> None: + """С `config.scraper_proxy_url` заданным — построенная curl_cffi-сессия реально + несёт этот proxy_url (не мок: инспектируем настоящий AsyncSession.proxies).""" + proxy_url = "http://user:pass@mproxy.local:14619" + config = _fake_config(proxy_url) + + with patch.object(kit_detail, "warm_up_session", AsyncMock(return_value=True)): + session = await build_warmed_session(config=config) # type: ignore[arg-type] + try: + assert session.proxies == {"http": proxy_url, "https": proxy_url} + finally: + await session.close() + + +async def test_build_warmed_session_without_config_has_no_proxy() -> None: + """Без `config` (дефолт, единственное поведение ДО фикса) сессия строится без + прокси — ровно баг #2330: caller, не передающий config, молча теряет sticky-proxy.""" + with patch.object(kit_detail, "warm_up_session", AsyncMock(return_value=True)): + session = await build_warmed_session() + try: + assert not session.proxies + finally: + await session.close() diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py index 17523dc7..349ed96f 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py @@ -324,11 +324,17 @@ async def research_in_session(session: AsyncSession) -> bool: return ok -async def build_warmed_session() -> AsyncSession: +async def build_warmed_session(config: ScraperConfig | None = None) -> AsyncSession: """Построить detail-сессию (_build_detail_session) и прогреть её. Возвращает прогретую сессию (даже если warm-up soft-failed — caller может всё равно пробовать). + + Strangler-инжекция (#2330): config пробрасывается в _build_detail_session тем же + способом, каким это делает fetch_detail — до этого фикса build_warmed_session + ВСЕГДА строил сессию без прокси (config игнорировался), что тихо теряло sticky + backconnect-proxy на warm-batch пути (avito_detail_backfill_use_curl=True, PROD + DEFAULT). См. issue #2330 / vault Scraper_Kit_Legacy_Dependency_Audit_0703. """ - session = _build_detail_session() + session = _build_detail_session(config) await warm_up_session(session) return session From ecec9ff6d7a9474b9d3d5159abcec24b2f986a8a Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 4 Jul 2026 03:01:27 +0300 Subject: [PATCH 2/2] =?UTF-8?q?fix(tradein/scraper-kit):=20=D1=82=D0=BE?= =?UTF-8?q?=D1=87=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B4=D0=BE=D0=BA=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B8=D0=BD=D0=B3=D0=BE=D0=B2=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B4-=D0=BF=D1=83=D1=82=D1=8C=20build=5F?= =?UTF-8?q?warmed=5Fsession?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit code-reviewer отметил: формулировка "PROD DEFAULT" в докстрингах могла ввести в заблуждение — прод-путь сегодня всё ещё легаси (avito_detail_backfill.py импортирует build_warmed_session из legacy-модуля), этот фикс — advance-prep, не изменение живого прод-поведения. --- ...test_avito_detail_kit_warmed_session_config.py | 11 +++++++---- ...est_scraper_kit_group_c_backfill_kit_parity.py | 15 ++++++++------- .../src/scraper_kit/providers/avito/detail.py | 9 ++++++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py b/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py index 8c350d44..f84b4dcb 100644 --- a/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py +++ b/tradein-mvp/backend/tests/scrapers/test_avito_detail_kit_warmed_session_config.py @@ -7,10 +7,13 @@ sticky-прокси в `_build_detail_session(config)`. Т.к. kit-`_build_detai отличие от legacy `app/services/scrapers/avito_detail.py`, который читает `settings.scraper_proxy_url` напрямую) читает прокси ТОЛЬКО из инжектируемого `config.scraper_proxy_url`, отсутствие параметра означало, что `build_warmed_session` -физически не могло получить прокси ни при каких условиях — ломая прод-дефолт путь -(avito_detail_backfill_use_curl=True) тихой потерей sticky backconnect-proxy на -warm-batch операциях. См. `test_scraper_kit_group_c_backfill_kit_parity.py` (issue -#2310), который явно документирует этот баг как flagged follow-up. +физически не могло получить прокси ни при каких условиях. Сегодняшний единственный +caller (`avito_detail_backfill.py`) всё ещё импортирует эту функцию из legacy-модуля, +так что прод-путь этим фиксом не затронут — это advance-prep: как только caller +переключится на kit (avito_detail_backfill_use_curl=True — прод-дефолт режим), без +этого фикса он тихо терял бы sticky backconnect-proxy на warm-batch операциях. См. +`test_scraper_kit_group_c_backfill_kit_parity.py` (issue #2310), который явно +документирует этот баг как flagged follow-up. Этот файл доказывает РАЗНИЦУ в поведении, а не просто "функция не падает": 1. `test_build_warmed_session_signature_now_accepts_config` — до фикса вызов diff --git a/tradein-mvp/backend/tests/test_scraper_kit_group_c_backfill_kit_parity.py b/tradein-mvp/backend/tests/test_scraper_kit_group_c_backfill_kit_parity.py index 04c067ad..4d7d37a3 100644 --- a/tradein-mvp/backend/tests/test_scraper_kit_group_c_backfill_kit_parity.py +++ b/tradein-mvp/backend/tests/test_scraper_kit_group_c_backfill_kit_parity.py @@ -10,13 +10,14 @@ Migrates legacy scraper imports of these files to their `scraper_kit` equivalent - app/tasks/avito_detail_backfill.py → AvitoScraper/BrowserFetcher/avito exceptions/fetch_detail/research_in_session/save_detail_enrichment now from `scraper_kit.*`. `build_warmed_session`/`_AVITO_WARM_SEARCH_URL` stay legacy — - kit's `build_warmed_session()` builds its curl_cffi session via - `_build_detail_session()` with NO config parameter at all (unlike - `fetch_detail`, which does), so there is no caller-side way to inject - `settings.scraper_proxy_url` — migrating it would silently drop the sticky - MGTS-proxy egress on the warm-batch path (avito_detail_backfill_use_curl=True, - the PROD DEFAULT). Same bug category as issue #2322 (scraper_kit provider - missing a config hook), different function — flagged as a new follow-up. + at the time this file was written, kit's `build_warmed_session()` built its + curl_cffi session via `_build_detail_session()` with NO config parameter at all + (unlike `fetch_detail`, which does), so there was no caller-side way to inject + `settings.scraper_proxy_url`. Fixed in issue #2330 (Group F2) — kit's + `build_warmed_session()` now accepts `config`, but THIS caller still imports it + from legacy, so migrating this specific call site remains a separate follow-up + before the sticky MGTS-proxy egress on the warm-batch path + (avito_detail_backfill_use_curl=True, the prod default) can move to kit. - app/tasks/cian_history_backfill.py → fetch_detail/save_detail_enrichment (listings block) + BrowserFetcher now from `scraper_kit.*`. The houses block (cian_newbuilding.fetch_newbuilding/save_newbuilding_enrichment) and the diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py index 349ed96f..de358d46 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/providers/avito/detail.py @@ -330,9 +330,12 @@ async def build_warmed_session(config: ScraperConfig | None = None) -> AsyncSess Strangler-инжекция (#2330): config пробрасывается в _build_detail_session тем же способом, каким это делает fetch_detail — до этого фикса build_warmed_session - ВСЕГДА строил сессию без прокси (config игнорировался), что тихо теряло sticky - backconnect-proxy на warm-batch пути (avito_detail_backfill_use_curl=True, PROD - DEFAULT). См. issue #2330 / vault Scraper_Kit_Legacy_Dependency_Audit_0703. + вообще не принимал config (был бы сломан любой caller, попытавшийся его передать). + Сегодняшний единственный caller (`avito_detail_backfill.py`) всё ещё импортирует + build_warmed_session из legacy-модуля, так что прод-путь этим фиксом не затронут — + это advance-prep для будущей миграции call-site на kit (avito_detail_backfill_use_curl=True + станет прод-дефолтом ТОГДА, когда caller переключится). См. issue #2330 / vault + Scraper_Kit_Legacy_Dependency_Audit_0703. """ session = _build_detail_session(config) await warm_up_session(session)