diff --git a/tradein-mvp/backend/app/core/config.py b/tradein-mvp/backend/app/core/config.py index a6f5a5d8..46e1df2a 100644 --- a/tradein-mvp/backend/app/core/config.py +++ b/tradein-mvp/backend/app/core/config.py @@ -1194,14 +1194,6 @@ class Settings(BaseSettings): # False = старое поведение (без фильтра). ENV: AVITO_SERP_EKB_ONLY. avito_serp_ekb_only: bool = Field(default=True, validation_alias="AVITO_SERP_EKB_ONLY") - # ── Yandex SERP cookies (#801/T4) ─────────────────────────────────────── - # Путь к JSON-файлу с cookies браузера (формат: [{name, value, ...}, ...]). - # Если задан и файл существует — cookies передаются в curl_cffi-сессию при - # Yandex SERP-запросах; снижает вероятность captcha на datacenter IP. - # Пусто / файл не найден = запросы без cookies (не падаем, только warning). - # ENV: YANDEX_COOKIES_FILE. - yandex_cookies_file: str | None = None - # ── #639: Cian browser auto-login (Variant B) ──────────────────────────── # Провалидировано вживую 2026-05-31: email+пароль, без SMS/капчи. Флоу 2-шаговый # (после 1-го сабмита экран «Введите пароль» → повтор). Селекторы env-overridable. diff --git a/tradein-mvp/backend/tests/test_3182_detail_transport_one_value.py b/tradein-mvp/backend/tests/test_3182_detail_transport_one_value.py new file mode 100644 index 00000000..446a74dd --- /dev/null +++ b/tradein-mvp/backend/tests/test_3182_detail_transport_one_value.py @@ -0,0 +1,45 @@ +"""#3182: режим detail-добора Авито не зависит от того, какой контейнер взял задачу. + +Добор исполняют два процесса образа backend: планировщик (tradein-scraper) и ручка +админки scrape_avito_detail_backfill (tradein-backend). Хостовый backend/.env.runtime +задаёт AVITO_DETAIL_BACKFILL_USE_CURL=true, а environment сервиса перекрывает env_file — +до правки "false" стояло только у scraper, и printenv на проде давал scraper=false, +backend=true. Проверка по значению: разобранный compose, каждый сервис образа backend. +""" + +import os +from pathlib import Path + +os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") + +import pytest +import yaml + +from app.core.config import Settings + +_COMPOSE = Path(__file__).resolve().parents[2] / "docker-compose.prod.yml" + + +def test_every_backend_image_service_pins_browser_transport() -> None: + services = yaml.safe_load(_COMPOSE.read_text())["services"] + backend_image = { + name: svc + for name, svc in services.items() + if "gendesign-tradein-backend" in svc.get("image", "") + } + # Контроль разбора: без него пустой словарь дал бы зелёный тест ни о чём. + assert {"backend", "scraper"} <= set(backend_image), sorted(backend_image) + + values = { + name: (svc.get("environment") or {}).get("AVITO_DETAIL_BACKFILL_USE_CURL") + for name, svc in backend_image.items() + } + assert values == dict.fromkeys(backend_image, "false"), values + + +def test_leftover_yandex_cookies_env_is_ignored(monkeypatch: pytest.MonkeyPatch) -> None: + """YANDEX_COOKIES_FILE удалён из Settings (никто не читал); хостовый .env.runtime + его ещё несёт — старт не должен падать, а поле не должно всплыть обратно.""" + monkeypatch.setenv("YANDEX_COOKIES_FILE", "/app/yandex_cookies.json") + + assert not hasattr(Settings(_env_file=None), "yandex_cookies_file") diff --git a/tradein-mvp/backend/tests/test_scraper_proxy.py b/tradein-mvp/backend/tests/test_scraper_proxy.py index 165539d8..b202ffdc 100644 --- a/tradein-mvp/backend/tests/test_scraper_proxy.py +++ b/tradein-mvp/backend/tests/test_scraper_proxy.py @@ -40,10 +40,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -def _mock_settings( - scraper_proxy_url: str | None, - yandex_cookies_file: str | None = None, -) -> SimpleNamespace: +def _mock_settings(scraper_proxy_url: str | None) -> SimpleNamespace: """Minimal settings stand-in with fields read by proxy helpers and scraper __aenter__.""" return SimpleNamespace( scraper_proxy_url=scraper_proxy_url, @@ -52,7 +49,6 @@ def _mock_settings( # removed — dead mobileproxy accounts). Mirrored here for test compat. cian_proxy_url=scraper_proxy_url, yandex_proxy_url=scraper_proxy_url, - yandex_cookies_file=yandex_cookies_file, ) diff --git a/tradein-mvp/docker-compose.prod.yml b/tradein-mvp/docker-compose.prod.yml index 191dfd8a..5aa3ef03 100644 --- a/tradein-mvp/docker-compose.prod.yml +++ b/tradein-mvp/docker-compose.prod.yml @@ -340,6 +340,11 @@ services: # деградация. У scraper те же значения с #2126/#2160. USE_PROXY_POOL_CURL: "true" USE_PROXY_POOL_BROWSER: "true" + # #3182: ручка админки scrape_avito_detail_backfill исполняет добор В ЭТОМ + # процессе. Хостовый backend/.env.runtime задаёт true (curl не проходит QRATOR + # PoW, см. scraper ниже) — environment перекрывает env_file. Значение одно на + # все сервисы образа backend, чтобы режим не зависел от того, кто взял задачу. + AVITO_DETAIL_BACKFILL_USE_CURL: "false" depends_on: browser: condition: service_started @@ -453,6 +458,8 @@ services: # Settings()-объект образа где-то читает флаг при импорте (defense-in-depth, # аналогично backend). tgbot_main.py не должен зависеть от этого значения. SCHEDULER_ENABLE: "false" + # #3182: то же значение, что у backend/scraper (см. комментарий у backend). + AVITO_DETAIL_BACKFILL_USE_CURL: "false" depends_on: postgres: condition: service_healthy