"""Avito SERP city-slug filter (#2487 oblast rollout).
`_parse_html` drops padding cards ("по всей России") when `avito_serp_ekb_only`
is on. The kept city-slug used to be hardcoded `/ekaterinburg/`, so an oblast
per-city sweep (`_job_avito_city_sweep` with a `city` param) discarded 100% of the
target city's cards. The slug is now parameterized via `AvitoScraper.target_city_slug`
(default → "ekaterinburg", i.e. ЕКБ behavior unchanged when no target is set).
No network / no DB — `_parse_html` runs on inline HTML with a stub config.
"""
from __future__ import annotations
import os
from types import SimpleNamespace
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from scraper_kit.providers.avito.serp import AvitoScraper
_BASE_URL = "https://www.avito.ru/ekaterinburg/kvartiry/prodam-ASgBAgICAUSSA8YQ?p=1"
def _card(slug: str, item_id: str, price: int = 5_000_000) -> str:
"""Minimal Avito SERP card whose per-card href carries `slug` as the city segment."""
href = f"/{slug}/kvartiry/2k_kvartira_50_m_5_5et_{item_id}"
return (
f'
"
)
def _html(*cards: str) -> str:
return "" + "".join(cards) + ""
def _ekb_only_cfg() -> SimpleNamespace:
# _parse_html only reads config.avito_serp_ekb_only; a stub keeps the test
# deterministic regardless of AVITO_SERP_EKB_ONLY env in the runner.
return SimpleNamespace(avito_serp_ekb_only=True)
def test_parse_keeps_target_oblast_city_drops_off_target_padding() -> None:
"""Sweep targeting Н.Тагил keeps its cards; ЕКБ/Москва padding is dropped."""
s = AvitoScraper(_ekb_only_cfg(), target_city_slug="nizhniy_tagil") # type: ignore[arg-type]
html = _html(
_card("nizhniy_tagil", "nt1"), # target city → KEPT
_card("ekaterinburg", "ekb1"), # off-target padding → DROPPED
_card("moskva", "msk1"), # off-target padding → DROPPED
)
lots = s._parse_html(html, source_url_base=_BASE_URL)
urls = [lot.source_url or "" for lot in lots]
assert len(lots) == 1, urls
assert "/nizhniy_tagil/" in urls[0]
assert all("/ekaterinburg/" not in u for u in urls)
assert all("/moskva/" not in u for u in urls)
def test_parse_default_target_is_ekb_and_drops_oblast_padding() -> None:
"""No target (ЕКБ sweep/full-load): kept slug defaults to 'ekaterinburg' — behavior
identical to the previous hardcode. ЕКБ card kept, oblast padding dropped."""
s = AvitoScraper(_ekb_only_cfg(), target_city_slug=None) # type: ignore[arg-type]
html = _html(
_card("ekaterinburg", "e1"), # ЕКБ → KEPT
_card("nizhniy_tagil", "nt1"), # off-target padding → DROPPED
)
lots = s._parse_html(html, source_url_base=_BASE_URL)
urls = [lot.source_url or "" for lot in lots]
assert len(lots) == 1, urls
assert "/ekaterinburg/" in urls[0]
assert all("/nizhniy_tagil/" not in u for u in urls)
def test_parse_ekb_only_disabled_keeps_all_cities() -> None:
"""avito_serp_ekb_only=False → no city filter at all, every card is kept."""
cfg = SimpleNamespace(avito_serp_ekb_only=False)
s = AvitoScraper(cfg, target_city_slug="nizhniy_tagil") # type: ignore[arg-type]
html = _html(_card("nizhniy_tagil", "nt1"), _card("ekaterinburg", "e1"))
lots = s._parse_html(html, source_url_base=_BASE_URL)
assert len(lots) == 2