"""test_house_imv_backfill_browser_flag.py — #915 Stage 3. Проверяет проводку флага avito_imv_use_browser_fetcher в backfill_house_imv: - флаг ON → конструируется один BrowserFetcher(source="avito") на весь батч, и он прокидывается в _process_one_house(browser_fetcher=...). - флаг OFF → BrowserFetcher НЕ конструируется, browser_fetcher=None (старый путь). DB и _process_one_house мокаются — реальный камуфокс/curl/Postgres не нужны. """ from __future__ import annotations import os from typing import Any, ClassVar from unittest.mock import MagicMock import pytest # settings нужен DSN на импорте (как в соседних тестах) — fake-db, статический. os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") from app.services import house_imv_backfill class _FakeBrowserFetcher: """Поддельный async-CM: считает конструкции/входы, возвращает себя из __aenter__.""" instances: ClassVar[list[_FakeBrowserFetcher]] = [] def __init__(self, source: str = "avito", **_: Any) -> None: self.source = source self.entered = False _FakeBrowserFetcher.instances.append(self) async def __aenter__(self) -> _FakeBrowserFetcher: self.entered = True return self async def __aexit__(self, *_: object) -> None: return None def _fake_db_with_one_row() -> MagicMock: """MagicMock-Session, чей execute(...).mappings().all() отдаёт один дом.""" db = MagicMock() row = {"id": 42, "address": "ЕКБ, ул. X, 1", "full_address": None, "lat": 56.8, "lon": 60.6} db.execute.return_value.mappings.return_value.all.return_value = [row] return db async def test_backfill_flag_on_constructs_browser_and_threads_it( monkeypatch: pytest.MonkeyPatch, ) -> None: _FakeBrowserFetcher.instances.clear() monkeypatch.setattr(house_imv_backfill.settings, "avito_imv_use_browser_fetcher", True) monkeypatch.setattr(house_imv_backfill, "BrowserFetcher", _FakeBrowserFetcher) captured: dict[str, Any] = {} async def _fake_process(db: Any, house: dict, *, browser_fetcher: Any = None) -> str: captured["browser_fetcher"] = browser_fetcher return "ok" monkeypatch.setattr(house_imv_backfill, "_process_one_house", _fake_process) db = _fake_db_with_one_row() await house_imv_backfill.backfill_house_imv(db, batch_size=10) # Ровно один BrowserFetcher(source="avito"), вошли в контекст. assert len(_FakeBrowserFetcher.instances) == 1 bf = _FakeBrowserFetcher.instances[0] assert bf.source == "avito" assert bf.entered is True # И он прокинут в _process_one_house. assert captured["browser_fetcher"] is bf async def test_backfill_flag_off_no_browser_and_none_threaded( monkeypatch: pytest.MonkeyPatch, ) -> None: _FakeBrowserFetcher.instances.clear() monkeypatch.setattr(house_imv_backfill.settings, "avito_imv_use_browser_fetcher", False) monkeypatch.setattr(house_imv_backfill, "BrowserFetcher", _FakeBrowserFetcher) captured: dict[str, Any] = {} async def _fake_process(db: Any, house: dict, *, browser_fetcher: Any = None) -> str: captured["browser_fetcher"] = browser_fetcher return "ok" monkeypatch.setattr(house_imv_backfill, "_process_one_house", _fake_process) db = _fake_db_with_one_row() await house_imv_backfill.backfill_house_imv(db, batch_size=10) # Флаг OFF → BrowserFetcher не конструируется, browser_fetcher=None. assert _FakeBrowserFetcher.instances == [] assert captured["browser_fetcher"] is None