diff --git a/tradein-mvp/backend/tests/test_backfill_wave2.py b/tradein-mvp/backend/tests/test_backfill_wave2.py index 63c12f1c..bfe72803 100644 --- a/tradein-mvp/backend/tests/test_backfill_wave2.py +++ b/tradein-mvp/backend/tests/test_backfill_wave2.py @@ -235,6 +235,7 @@ async def test_backfill_yandex_addresses_error_on_non_200(): async def test_cian_price_history_saves_changes(): """Happy path: fetch returns DetailEnrichment with price_changes → saved.""" from app.services.cian_price_history import backfill_cian_price_history + from app.services.scraper_adapters import RealScraperConfig from app.services.scrapers.cian_detail import DetailEnrichment enrichment = DetailEnrichment( @@ -277,6 +278,12 @@ async def test_cian_price_history_saves_changes(): assert result.saved == 2 # 2 price_changes assert result.errors == 0 mock_fetch.assert_called_once() + # #2306 regression guard: kit fetch_detail silently drops the Cian proxy + # (direct connection, #806 ban risk) unless config=RealScraperConfig() is + # passed at the call site — assert_called_once() alone wouldn't catch someone + # dropping that kwarg later. + _, call_kwargs = mock_fetch.call_args + assert isinstance(call_kwargs.get("config"), RealScraperConfig) mock_save.assert_called_once() @@ -284,6 +291,7 @@ async def test_cian_price_history_saves_changes(): async def test_cian_price_history_skips_no_changes(): """Fetch succeeds but no price_changes → skipped (not error).""" from app.services.cian_price_history import backfill_cian_price_history + from app.services.scraper_adapters import RealScraperConfig from app.services.scrapers.cian_detail import DetailEnrichment enrichment = DetailEnrichment(cian_id=111, price_changes=[]) @@ -296,7 +304,7 @@ async def test_cian_price_history_skips_no_changes(): "app.services.cian_price_history.fetch_detail", new_callable=AsyncMock, return_value=enrichment, - ), + ) as mock_fetch, patch("app.services.cian_price_history.save_detail_enrichment") as mock_save, patch("app.services.cian_price_history.get_scraper_delay", return_value=0.0), ): @@ -309,12 +317,16 @@ async def test_cian_price_history_skips_no_changes(): assert result.skipped == 1 assert result.saved == 0 mock_save.assert_not_called() + # #2306 regression guard — see test_cian_price_history_saves_changes. + _, call_kwargs = mock_fetch.call_args + assert isinstance(call_kwargs.get("config"), RealScraperConfig) @pytest.mark.asyncio async def test_cian_price_history_handles_fetch_none(): """fetch_detail returns None → errors+1, no crash.""" from app.services.cian_price_history import backfill_cian_price_history + from app.services.scraper_adapters import RealScraperConfig mock_db = MagicMock() rows = [{"id": 30, "source_url": "https://ekb.cian.ru/sale/flat/999/"}] @@ -324,7 +336,7 @@ async def test_cian_price_history_handles_fetch_none(): "app.services.cian_price_history.fetch_detail", new_callable=AsyncMock, return_value=None, - ), + ) as mock_fetch, patch("app.services.cian_price_history.get_scraper_delay", return_value=0.0), ): mock_mappings = MagicMock() @@ -335,6 +347,9 @@ async def test_cian_price_history_handles_fetch_none(): assert result.errors == 1 assert result.saved == 0 + # #2306 regression guard — see test_cian_price_history_saves_changes. + _, call_kwargs = mock_fetch.call_args + assert isinstance(call_kwargs.get("config"), RealScraperConfig) # ============================================================================