"""test_server_recycle.py — юниты для per-provider recycle_pages threshold (#3205). Проверяет ``_resolve_recycle_pages``: 1. per-provider env перебивает глобальный env; 2. глобальный env перебивает код-дефолт per-provider; 3. невалидное (не int) значение → фолбэк, без исключения; 4. значение <1 → приводится к 1; 5. код-дефолт per-provider перебивает общий фолбэк (#3212: словарь _RECYCLE_PAGES_DEFAULT_BY_PROVIDER сейчас ПУСТ — domclick=1 снят, потому что перезапуск процесса уничтожал пропуск QRATOR; уровень приоритета остался и проверяется через подставной словарь, а не через живого поставщика); 6. все поставщики без env → общий фолбэк 15. camoufox/Playwright НЕ поднимается: тестируется чистая функция резолвера. Запуск (из tradein-mvp/browser/):: python -m pytest test_server_recycle.py -q """ from __future__ import annotations import importlib.util from pathlib import Path # server.py — не пакет (отдельный сервис без __init__/pyproject). Грузим по пути. _SERVER_PATH = Path(__file__).resolve().parent / "server.py" _spec = importlib.util.spec_from_file_location("tradein_browser_server", _SERVER_PATH) assert _spec is not None and _spec.loader is not None server = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(server) def test_per_provider_env_overrides_global() -> None: """BROWSER_RECYCLE_PAGES_DOMCLICK=3 + глобал=10 → domclick=3.""" fake_env = {"BROWSER_RECYCLE_PAGES": "10", "BROWSER_RECYCLE_PAGES_DOMCLICK": "3"} assert server._resolve_recycle_pages("domclick", fake_env) == 3 def test_global_env_overrides_code_default(monkeypatch) -> None: """Нет per-provider env, но задан глобальный → перебивает код-дефолт поставщика.""" monkeypatch.setattr(server, "_RECYCLE_PAGES_DEFAULT_BY_PROVIDER", {"domclick": 2}) fake_env = {"BROWSER_RECYCLE_PAGES": "7"} assert server._resolve_recycle_pages("domclick", fake_env) == 7 def test_invalid_per_provider_env_falls_back() -> None: """Невалидный (не int) per-provider env → фолбэк на глобальный уровень.""" fake_env = {"BROWSER_RECYCLE_PAGES": "9", "BROWSER_RECYCLE_PAGES_CIAN": "abc"} assert server._resolve_recycle_pages("cian", fake_env) == 9 def test_invalid_global_env_falls_back_to_code_default(monkeypatch) -> None: """Невалидный глобальный env → фолбэк на код-дефолт per-provider (без исключения).""" monkeypatch.setattr(server, "_RECYCLE_PAGES_DEFAULT_BY_PROVIDER", {"domclick": 2}) fake_env = {"BROWSER_RECYCLE_PAGES": "not-a-number"} assert server._resolve_recycle_pages("domclick", fake_env) == 2 assert server._resolve_recycle_pages("cian", fake_env) == 15 def test_value_below_one_clamped_to_one() -> None: """Явный env <1 недопустим → приводится к 1, а не проваливается дальше по цепочке.""" assert server._resolve_recycle_pages("avito", {"BROWSER_RECYCLE_PAGES_AVITO": "0"}) == 1 assert server._resolve_recycle_pages("cian", {"BROWSER_RECYCLE_PAGES": "-5"}) == 1 def test_code_default_overrides_fallback(monkeypatch) -> None: """Код-дефолт поставщика (когда он есть) сильнее общего фолбэка 15.""" monkeypatch.setattr(server, "_RECYCLE_PAGES_DEFAULT_BY_PROVIDER", {"domclick": 2}) assert server._resolve_recycle_pages("domclick", {}) == 2 assert server._resolve_recycle_pages("cian", {}) == 15 def test_domclick_is_not_restarted_per_page() -> None: """#3212: у domclick НЕТ код-дефолта 1 — перезапуск на карточку уничтожал пропуск. Замер (прод, прод-прокси, 6 карточек): общий контекст без перезапуска — 6/6; новый контекст на карточку — 1/6. Порог 1 гарантировал второе, а не первое. """ assert "domclick" not in server._RECYCLE_PAGES_DEFAULT_BY_PROVIDER assert server._resolve_recycle_pages("domclick", {}) == 15 def test_all_providers_without_env_default_to_fifteen() -> None: """Без env все поставщики живут на общем фолбэке 15.""" for provider in ("avito", "cian", "yandex", "domclick"): assert server._resolve_recycle_pages(provider, {}) == 15 def test_generic_without_env_defaults_to_fifteen() -> None: assert server._resolve_recycle_pages("generic", {}) == 15