From d4e4dd712428d63e93d8f22b0916299778744ced Mon Sep 17 00:00:00 2001 From: bot-backend Date: Tue, 4 Aug 2026 23:23:42 +0500 Subject: [PATCH] =?UTF-8?q?fix(tradein/scraper):=20=D0=B4=D0=BE=D0=B6?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20guard=20#2616=20=D0=B4=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B4-=D0=BF=D1=83=D1=82=D0=B5=D0=B9=20(=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D1=8C=D1=8E=20PR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ревью нашло: guard NoProxyAvailableError был мёртв ровно там, где нужен — (1) avito-пути pipeline.py конструируют BrowserFetcher напрямую, минуя build_browser_fetcher(), и не прокидывали environment → дефолт dev, отказ никогда не срабатывал; добавлен environment=getattr(config, ...) во все 3 construction-site + source-level тест-гард на новые сайты; (2) browser-сайдкар: ENVIRONMENT не задан в docker-compose.prod.yml → IS_PROD guard server.py читал dev; добавлен ENVIRONMENT: production в environment-блок. --- ...est_pipeline_browserfetcher_environment.py | 46 +++++++++++++++++++ tradein-mvp/docker-compose.prod.yml | 4 ++ .../src/scraper_kit/orchestration/pipeline.py | 9 ++++ 3 files changed, 59 insertions(+) create mode 100644 tradein-mvp/backend/tests/test_pipeline_browserfetcher_environment.py diff --git a/tradein-mvp/backend/tests/test_pipeline_browserfetcher_environment.py b/tradein-mvp/backend/tests/test_pipeline_browserfetcher_environment.py new file mode 100644 index 00000000..ae5fefe1 --- /dev/null +++ b/tradein-mvp/backend/tests/test_pipeline_browserfetcher_environment.py @@ -0,0 +1,46 @@ +"""#2616 шаг 1: каждый прямой BrowserFetcher(...) в pipeline.py обязан прокидывать +environment=. + +Ревью PR #2634 нашло: build_browser_fetcher() (providers/_base.py) — единственное +место, где environment прокидывался, а avito-пути pipeline.py конструируют +BrowserFetcher напрямую → guard NoProxyAvailableError был мёртв ровно для +источника, ради которого писался (#2613). Юнит-тесты этого не ловили, потому что +конструировали фетчер сами, а не путём pipeline. + +Source-level гард дешевле интеграционного прогона pipeline и падает на первом же +новом construction-site без environment=. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +_PIPELINE = ( + Path(__file__).resolve().parents[2] + / "packages" + / "scraper-kit" + / "src" + / "scraper_kit" + / "orchestration" + / "pipeline.py" +) + + +def test_every_direct_browserfetcher_construction_threads_environment() -> None: + src = _PIPELINE.read_text(encoding="utf-8") + # Все вызовы конструктора BrowserFetcher( ... ) — балансировку скобок не пишем, + # берём консервативное окно в 15 строк после открытия вызова. + sites = [m.start() for m in re.finditer(r"BrowserFetcher\(\n", src)] + assert sites, "pipeline.py больше не конструирует BrowserFetcher напрямую? Обнови/удали тест." + missing: list[str] = [] + for pos in sites: + window = src[pos : pos + 700] + if "environment=" not in window: + line_no = src[:pos].count("\n") + 1 + missing.append(f"pipeline.py:{line_no}") + assert not missing, ( + f"Прямые BrowserFetcher(...) без environment= ({missing}): guard " + "NoProxyAvailableError (#2616) для них мёртв — добавь " + 'environment=getattr(config, "environment", "dev").' + ) diff --git a/tradein-mvp/docker-compose.prod.yml b/tradein-mvp/docker-compose.prod.yml index 4d346c26..bb1e073d 100644 --- a/tradein-mvp/docker-compose.prod.yml +++ b/tradein-mvp/docker-compose.prod.yml @@ -67,6 +67,10 @@ services: # Сколько /fetch параллельно. Дефолт 4; ops поднимет до 8 в .env.runtime после смоука. BROWSER_CONCURRENCY: ${BROWSER_CONCURRENCY:-4} # SCRAPER_PROXY_URL читается из .env.runtime (см. env_file выше) + # #2616 шаг 1: server.py IS_PROD-guard (отказ 503 вместо direct-IP camoufox при + # отсутствии прокси) активен только при ENVIRONMENT=production — без этой + # строки guard молча спит (дефолт "dev"). + ENVIRONMENT: production expose: - "3000" restart: unless-stopped diff --git a/tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py b/tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py index beb03b03..a23113ad 100644 --- a/tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py +++ b/tradein-mvp/packages/scraper-kit/src/scraper_kit/orchestration/pipeline.py @@ -561,6 +561,9 @@ async def run_avito_pipeline( endpoint=config.browser_http_endpoint, proxy_provider=proxy_provider, use_pool=config.use_proxy_pool_browser, + # #2616 шаг 1: прямое конструирование обходит build_browser_fetcher() — + # без environment guard NoProxyAvailableError мёртв для avito. + environment=getattr(config, "environment", "dev"), ) await browser_fetcher.__aenter__() own_browser = True @@ -1039,6 +1042,9 @@ async def run_avito_city_sweep( endpoint=config.browser_http_endpoint, proxy_provider=proxy_provider, use_pool=config.use_proxy_pool_browser, + # #2616 шаг 1: см. run_avito_pipeline — environment обязателен + # при прямом конструировании (guard иначе мёртв). + environment=getattr(config, "environment", "dev"), ) ) else: @@ -1727,6 +1733,9 @@ async def run_avito_newbuilding_sweep( endpoint=config.browser_http_endpoint, proxy_provider=proxy_provider, use_pool=config.use_proxy_pool_browser, + # #2616 шаг 1: см. run_avito_pipeline — environment обязателен + # при прямом конструировании (guard иначе мёртв). + environment=getattr(config, "environment", "dev"), ) ) else: