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: