gendesign/tradein-mvp/backend/tests/test_extval_house_id_write_path.py
bot-backend 27bbd579c3
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 47s
chore(tradein/scrapers): удалить весь legacy scrapers/ каталог — final E (#2397, #2277)
Удаляет весь `app/services/scrapers/` (16 файлов, ~7100 строк) — Part D (D1-D5)
убрал всех внешних вызывающих, 0 runtime importers подтверждено grep'ом на main.

Заодно:
- удалены 7 осиротевших локальных probe/sweep-скриптов (tradein-mvp/scripts/),
  импортировавших уже-удалённые или удаляемые сейчас legacy-модули
- тест-хирургия по 65+ файлам: DELETE прямых legacy-юнит-тестов, RETARGET
  тестов, тестирующих ещё живую бизнес-логику (переключены на scraper_kit.*
  эквиваленты, включая quality-gate #781/#753/#754/#755/#773/#740), partial-delete
  golden-parity тестов, потерявших legacy-оракл
- kit save_listings/AvitoScraper/etc. требуют инжектируемые matcher/config —
  ретаргетированные тесты обновлены под новую сигнатуру (RealScraperConfig(),
  MagicMock HouseMatcher, region_code=66)

Полный pytest suite зелёный (2255 passed, 6 skipped) кроме известного флейка
#2208 (test_search_cache_hit, не связан со scrapers).
2026-07-04 15:58:15 +03:00

161 lines
6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Write-path tests: external_valuations.house_id резолвится при сохранении (#2236).
Cian и Yandex valuation-кэши теперь принимают house_id (уже разрезолвленный
estimate-путём через match_house_readonly) и пишут его в external_valuations.
Best-effort: house_id=None → колонка NULL, сохранение НЕ падает.
DB — MagicMock, записываем параметры db.execute (никакого реального Postgres,
как в test_yandex_valuation_save / test_backfill_house_coords).
Легаси `app.services.scrapers.yandex_valuation` / `cian_valuation` удалены (#2277 /
#2397 финальный шаг E, 0 runtime-импортёров) — `_yandex_result()` и cian-импорты
переведены на kit-эквиваленты `scraper_kit.providers.yandex.valuation` /
`scraper_kit.providers.cian.valuation` (те же типы, что `estimator.py` реально
использует).
"""
from __future__ import annotations
import os
# Settings требует DATABASE_URL на import. Ставим фиктивный DSN заранее.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
from unittest.mock import AsyncMock, MagicMock, patch
from scraper_kit.providers.cian.valuation import CianValuationResult, _save_to_cache
# ---------------------------------------------------------------------------
# Cian valuation — _save_to_cache прокидывает house_id в INSERT params
# ---------------------------------------------------------------------------
def _cian_result() -> CianValuationResult:
return CianValuationResult(
sale_price_rub=10_000_000,
sale_accuracy=85.0,
sale_price_from=9_000_000,
sale_price_to=11_000_000,
rent_price_rub=40_000,
rent_accuracy=70.0,
chart=[],
chart_change_pct=None,
chart_change_direction=None,
external_house_id=54016,
filters_hash="abc",
raw_state=None,
)
def _cian_save(db: MagicMock, house_id: int | None) -> dict:
_save_to_cache(
db,
cache_key="ck",
address="Екатеринбург, улица Малышева, 51",
total_area=50.0,
rooms_count=2,
floor=3,
total_floors=9,
repair_type="cosmetic",
deal_type="sale",
result=_cian_result(),
house_id=house_id,
)
# _save_to_cache делает один db.execute(INSERT ...) + commit
return db.execute.call_args_list[0].args[1]
def test_cian_save_persists_house_id():
"""Резолвленный house_id попадает в INSERT-параметры."""
db = MagicMock()
params = _cian_save(db, house_id=777)
assert params["hid"] == 777
db.commit.assert_called_once()
def test_cian_save_null_house_id_no_exception():
"""house_id=None → hid=None в params, сохранение не падает."""
db = MagicMock()
params = _cian_save(db, house_id=None)
assert params["hid"] is None
db.commit.assert_called_once()
# ---------------------------------------------------------------------------
# Yandex valuation — _get_or_fetch_yandex_valuation_cached пишет house_id
# ---------------------------------------------------------------------------
def _make_db_cache_miss() -> MagicMock:
"""MagicMock db, у которого cache-lookup даёт miss (first() → None)."""
db = MagicMock()
db.execute.return_value.mappings.return_value.first.return_value = None
return db
def _yandex_result():
from scraper_kit.providers.yandex.valuation import (
ValuationHouseMeta,
YandexValuationResult,
)
return YandexValuationResult(
address="Екатеринбург, улица Куйбышева, 106",
offer_category="APARTMENT",
offer_type="SELL",
page=1,
source_url="https://realty.yandex.ru/otsenka/?address=test&page=1",
house=ValuationHouseMeta(year_built=2005, total_floors=25),
history_items=[],
)
def _insert_params(db: MagicMock) -> dict:
"""Найти params INSERT INTO external_valuations среди вызовов db.execute."""
for call in db.execute.call_args_list:
args = call.args
if len(args) >= 2 and "INSERT INTO external_valuations" in str(args[0]):
return args[1]
raise AssertionError("INSERT INTO external_valuations not found in db.execute calls")
async def test_yandex_save_persists_house_id():
"""Fresh fetch пишет house_id в INSERT external_valuations."""
from app.services import estimator
db = _make_db_cache_miss()
scraper = AsyncMock()
scraper.fetch_house_history = AsyncMock(return_value=_yandex_result())
cm = MagicMock()
cm.__aenter__ = AsyncMock(return_value=scraper)
cm.__aexit__ = AsyncMock(return_value=False)
with patch.object(estimator, "YandexValuationScraper", return_value=cm):
res = await estimator._get_or_fetch_yandex_valuation_cached(
db, address="Екатеринбург, улица Куйбышева, 106", house_id=4242
)
assert res is not None
params = _insert_params(db)
assert params["hid"] == 4242
async def test_yandex_save_null_house_id_no_exception():
"""house_id=None → hid=None, INSERT проходит без исключения."""
from app.services import estimator
db = _make_db_cache_miss()
scraper = AsyncMock()
scraper.fetch_house_history = AsyncMock(return_value=_yandex_result())
cm = MagicMock()
cm.__aenter__ = AsyncMock(return_value=scraper)
cm.__aexit__ = AsyncMock(return_value=False)
with patch.object(estimator, "YandexValuationScraper", return_value=cm):
res = await estimator._get_or_fetch_yandex_valuation_cached(
db, address="Екатеринбург, улица Куйбышева, 106"
)
assert res is not None
params = _insert_params(db)
assert params["hid"] is None