fix(tradein): режим detail-добора Авито одинаков во всех контейнерах образа backend; убрана мёртвая YANDEX_COOKIES_FILE (#3182)
Прод 17.09, printenv: AVITO_DETAIL_BACKFILL_USE_CURL=false у tradein-scraper и true у tradein-backend/tgbot/browser. Источник true — хостовый backend/.env.runtime:37; false задан только environment'ом сервиса scraper. tradein-backend исполняет добор сам (ручка админки scrape_avito_detail_backfill), то есть ручной прогон шёл curl'ом, который QRATOR PoW не проходит. - docker-compose.prod.yml: AVITO_DETAIL_BACKFILL_USE_CURL "false" у backend и tgbot — у всех сервисов образа backend одно значение (environment перекрывает env_file). tgbot добор не исполняет; строка там, чтобы правило «одно значение на образ» проверялось без списка исключений. - Остальные флаги режима проверены на проде: SCRAPER_FETCH_MODE=browser и AVITO_IMV_USE_BROWSER_FETCHER=true одинаковы во всех четырёх контейнерах. - yandex_cookies_file удалён из Settings: его не читал ни один модуль (только мок в test_scraper_proxy.py), файла /app/yandex_cookies.json нет ни в одном контейнере, куки Яндекса давно живут в БД (#3192). extra="ignore" держит старт при оставшейся переменной в .env.runtime. Тест по значению: разобранный compose — у каждого сервиса образа backend значение "false"; Settings с YANDEX_COOKIES_FILE в env стартует без поля. Фальсификация: без строки у backend — «{'backend': None} != {'backend': 'false'}»; с возвращённым полем — «assert not True». Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
e42e370ac9
commit
19b6081242
4 changed files with 53 additions and 13 deletions
|
|
@ -1194,14 +1194,6 @@ class Settings(BaseSettings):
|
||||||
# False = старое поведение (без фильтра). ENV: AVITO_SERP_EKB_ONLY.
|
# False = старое поведение (без фильтра). ENV: AVITO_SERP_EKB_ONLY.
|
||||||
avito_serp_ekb_only: bool = Field(default=True, validation_alias="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) ────────────────────────────
|
# ── #639: Cian browser auto-login (Variant B) ────────────────────────────
|
||||||
# Провалидировано вживую 2026-05-31: email+пароль, без SMS/капчи. Флоу 2-шаговый
|
# Провалидировано вживую 2026-05-31: email+пароль, без SMS/капчи. Флоу 2-шаговый
|
||||||
# (после 1-го сабмита экран «Введите пароль» → повтор). Селекторы env-overridable.
|
# (после 1-го сабмита экран «Введите пароль» → повтор). Селекторы env-overridable.
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -40,10 +40,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def _mock_settings(
|
def _mock_settings(scraper_proxy_url: str | None) -> SimpleNamespace:
|
||||||
scraper_proxy_url: str | None,
|
|
||||||
yandex_cookies_file: str | None = None,
|
|
||||||
) -> SimpleNamespace:
|
|
||||||
"""Minimal settings stand-in with fields read by proxy helpers and scraper __aenter__."""
|
"""Minimal settings stand-in with fields read by proxy helpers and scraper __aenter__."""
|
||||||
return SimpleNamespace(
|
return SimpleNamespace(
|
||||||
scraper_proxy_url=scraper_proxy_url,
|
scraper_proxy_url=scraper_proxy_url,
|
||||||
|
|
@ -52,7 +49,6 @@ def _mock_settings(
|
||||||
# removed — dead mobileproxy accounts). Mirrored here for test compat.
|
# removed — dead mobileproxy accounts). Mirrored here for test compat.
|
||||||
cian_proxy_url=scraper_proxy_url,
|
cian_proxy_url=scraper_proxy_url,
|
||||||
yandex_proxy_url=scraper_proxy_url,
|
yandex_proxy_url=scraper_proxy_url,
|
||||||
yandex_cookies_file=yandex_cookies_file,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -340,6 +340,11 @@ services:
|
||||||
# деградация. У scraper те же значения с #2126/#2160.
|
# деградация. У scraper те же значения с #2126/#2160.
|
||||||
USE_PROXY_POOL_CURL: "true"
|
USE_PROXY_POOL_CURL: "true"
|
||||||
USE_PROXY_POOL_BROWSER: "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:
|
depends_on:
|
||||||
browser:
|
browser:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
|
@ -453,6 +458,8 @@ services:
|
||||||
# Settings()-объект образа где-то читает флаг при импорте (defense-in-depth,
|
# Settings()-объект образа где-то читает флаг при импорте (defense-in-depth,
|
||||||
# аналогично backend). tgbot_main.py не должен зависеть от этого значения.
|
# аналогично backend). tgbot_main.py не должен зависеть от этого значения.
|
||||||
SCHEDULER_ENABLE: "false"
|
SCHEDULER_ENABLE: "false"
|
||||||
|
# #3182: то же значение, что у backend/scraper (см. комментарий у backend).
|
||||||
|
AVITO_DETAIL_BACKFILL_USE_CURL: "false"
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue