"""Parity-proof: admin.py `scrape_cian_detail` (#2305, Group A) — legacy vs scraper_kit. `app/api/v1/admin.py::scrape_cian_detail` теперь зовёт `scraper_kit.providers.cian.detail.fetch_detail/save_detail_enrichment` вместо `app.services.scrapers.cian_detail`. `_extract_price_changes` — чистая, offline-тестируемая normalize-функция, которую `fetch_detail` вызывает при сборке `DetailEnrichment.price_changes` (см. `tests/support/README.md`: "Live network/DB в parity-тестах избегайте"). """ from __future__ import annotations import os # cian_detail.py импортирует app.core.config при module-level import — фиктивный # DSN мостит офлайн-запуск (mirror test_avito_detail_kit_parity.py). os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db") from scraper_kit.providers.cian.detail import _extract_price_changes as kit_extract_price_changes from app.services.scrapers.cian_detail import ( _extract_price_changes as legacy_extract_price_changes, ) from tests.support.parity import assert_parity # Три реальных формы priceChanges: curl_cffi flat, browser-rendered nested, оба-пусто. _OFFER_FLAT = { "priceChanges": [ {"changeTime": "2026-01-15T00:00:00", "price": 9000000, "diffPercent": -2.5}, {"changeTime": "2026-02-01T00:00:00", "priceRub": 8800000, "diff_percent": -2.2}, ] } _OFFER_DATA_NESTED = { "priceChanges": [ {"changeTime": "2026-01-15T00:00:00", "priceData": {"price": 9000000}}, ] } _STATE_EMPTY: dict[str, object] = {} def test_parity_cian_detail_price_changes() -> None: """`scrape_cian_detail` (admin.py) → fetch_detail → _extract_price_changes.""" assert_parity( legacy_fn=legacy_extract_price_changes, kit_fn=kit_extract_price_changes, fixtures=[ (_OFFER_FLAT, {}, _STATE_EMPTY), ({}, _OFFER_DATA_NESTED, _STATE_EMPTY), ({}, {}, _STATE_EMPTY), ], )