"""Гео-скоуп DomClick — параметр региона, а не зашитый Екатеринбург. До этого GUID/aids/страница прогрева/bbox жили модульными константами `_EKB_*`, и прогон с region_code 77/50 собрал бы ЕКБ под чужим регионом. Тесты стерегут две вещи: профиль 66 не изменил НИ БАЙТА в запросах (прод-поведение прежнее), а профили Москвы и области действительно меняют скоуп — без aids и без сверки имени региона, которое вне ЕКБ ненадёжно. """ from types import SimpleNamespace import pytest from scraper_kit.providers.domclick.serp import ( DOMCLICK_GEO_PROFILES, DomClickScraper, _build_count_url, _build_offers_url, geo_profile_for_region, ) # Литералы снятые с кода ДО параметризации: любое расхождение = смена прод-запроса. _EKB_OFFERS_URL_BEFORE = ( "https://bff-search-web.domclick.ru/api/offers/v1" "?address=0d475b79-88de-4054-818c-37d8f9d0d440&aids=20561" "&deal_type=sale&category=living&offer_type=flat&rooms=5%2B" "&sort=qi&sort_dir=desc&offset=40&limit=20" "&sale_price__gte=1000000&sale_price__lte=5000000" ) _EKB_COUNT_URL_BEFORE = ( "https://bff-search-web.domclick.ru/api/offers/count/v1" "?address=0d475b79-88de-4054-818c-37d8f9d0d440&aids=20561" "&deal_type=sale&category=living&offer_type=flat&rooms=2" "&sort=qi&sort_dir=desc" ) def _url_for(region_code: int, *, count: bool) -> str: profile = geo_profile_for_region(region_code) if count: return _build_count_url( "2", None, None, address_guid=profile.address_guid, aids=profile.aids ) return _build_offers_url( "5+", 1_000_000, 5_000_000, 40, address_guid=profile.address_guid, aids=profile.aids, ) def test_ekb_offers_url_unchanged_byte_for_byte() -> None: assert _url_for(66, count=False) == _EKB_OFFERS_URL_BEFORE def test_ekb_count_url_unchanged_byte_for_byte() -> None: assert _url_for(66, count=True) == _EKB_COUNT_URL_BEFORE @pytest.mark.parametrize("region_code", [77, 50]) @pytest.mark.parametrize("count", [False, True]) def test_msk_and_oblast_urls_carry_no_aids(region_code: int, count: bool) -> None: """aids сужает выдачу до агрегатора; у Москвы и области счётчик без него совпадает с сайтом (замер 12.09.2026), поэтому параметра быть не должно.""" url = _url_for(region_code, count=count) assert "aids=" not in url assert f"address={geo_profile_for_region(region_code).address_guid}" in url def test_profiles_are_distinct_and_ekb_keeps_aids() -> None: guids = {code: p.address_guid for code, p in DOMCLICK_GEO_PROFILES.items()} assert len(set(guids.values())) == len(guids) assert DOMCLICK_GEO_PROFILES[66].aids == "20561" # Имя региона сверяется ТОЛЬКО у ЕКБ. assert DOMCLICK_GEO_PROFILES[66].region_name == "Екатеринбург" assert DOMCLICK_GEO_PROFILES[77].region_name is None assert DOMCLICK_GEO_PROFILES[50].region_name is None # Имя города для listings.city: у области его нет и угадывать нечего. assert DOMCLICK_GEO_PROFILES[66].city_name == "Екатеринбург" assert DOMCLICK_GEO_PROFILES[77].city_name == "Москва" assert DOMCLICK_GEO_PROFILES[50].city_name is None def _geo_ok(region_code: int, item: dict[str, object]) -> bool: """_is_geo_ok без конструирования скрейпера: метод читает только self._geo.""" fake = SimpleNamespace(_geo=geo_profile_for_region(region_code)) return DomClickScraper._is_geo_ok(fake, item) # type: ignore[arg-type] def test_msk_accepts_novaya_moskva_with_foreign_region_name() -> None: """Новая Москва приходит как «г. Говорово» — сверка по имени вырезала бы законную выдачу, поэтому вне 66 гард только по bbox.""" item = {"offerRegionName": "г. Говорово", "location": {"lat": 55.62, "lon": 37.35}} assert _geo_ok(77, item) is True def test_msk_rejects_point_outside_bbox() -> None: ekb_point = {"offerRegionName": "Москва", "location": {"lat": 56.83, "lon": 60.60}} assert _geo_ok(77, ekb_point) is False def test_ekb_still_rejects_foreign_region_name() -> None: item = {"offerRegionName": "г. Говорово", "location": {"lat": 56.83, "lon": 60.60}} assert _geo_ok(66, item) is False def test_oblast_bbox_accepts_podmoskovie_point() -> None: item = {"offerRegionName": "г. Подольск", "location": {"lat": 55.43, "lon": 37.55}} assert _geo_ok(50, item) is True def test_unknown_region_code_raises() -> None: with pytest.raises(ValueError, match="region_code=99"): geo_profile_for_region(99)