gendesign/tradein-mvp/backend/tests/test_yandex_valuation_save.py
lekss361 c8675ce6e2
All checks were successful
Deploy Trade-In / deploy (push) Successful in 36s
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 44s
feat(yandex-valuation): link history rows to houses via match_or_create_house (#531)
2026-05-24 14:28:30 +00:00

194 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.

"""Tests for _save_yandex_history_items — house_id linking + new columns."""
from __future__ import annotations
import os
# Settings requires DATABASE_URL at init time. Set dummy DSN before any app import.
os.environ.setdefault("DATABASE_URL", "postgresql://test:test@localhost/test_db")
from datetime import date
from unittest.mock import MagicMock, patch
import pytest
from app.services.estimator import _save_yandex_history_items
from app.services.scrapers.yandex_valuation import (
ValuationHistoryItem,
ValuationHouseMeta,
YandexValuationResult,
)
def _make_result(items: list[ValuationHistoryItem]) -> YandexValuationResult:
return YandexValuationResult(
address="Россия, Свердловская область, Екатеринбург, улица Куйбышева, 106",
offer_category="APARTMENT",
offer_type="SELL",
page=1,
source_url=(
"https://realty.yandex.ru/otsenka-kvartiry-po-adresu-onlayn/?address=test&page=1"
),
house=ValuationHouseMeta(year_built=2005, total_floors=25, has_lift=True),
history_items=items,
)
def _two_items() -> list[ValuationHistoryItem]:
return [
ValuationHistoryItem(
area_m2=50.0,
rooms=2,
floor=3,
start_price=9_000_000,
last_price=9_000_000,
publish_date=date(2024, 5, 10),
removed_date=None,
exposure_days=30,
status="В продаже",
),
ValuationHistoryItem(
area_m2=55.0,
rooms=2,
floor=5,
start_price=10_000_000,
last_price=9_500_000,
publish_date=date(2024, 3, 1),
removed_date=date(2024, 6, 1),
exposure_days=92,
status="Снято",
),
]
def test_save_resolves_house_once_per_page():
"""match_or_create_house must be called exactly once per page (not per item)."""
db = MagicMock()
result = _make_result(_two_items())
with patch(
"app.services.estimator.match_or_create_house",
return_value=(12345, 0.9, "fingerprint"),
) as m:
saved = _save_yandex_history_items(db, result)
assert m.call_count == 1, "match_or_create_house must be called once per page"
assert saved == 2
def test_save_row_contains_house_id_and_confidence():
"""Each INSERT row dict carries house_id and source_confidence."""
db = MagicMock()
result = _make_result(_two_items())
with patch(
"app.services.estimator.match_or_create_house",
return_value=(54321, 0.9, "fingerprint"),
):
_save_yandex_history_items(db, result)
# db.execute called twice (one per item)
assert db.execute.call_count == 2
for c in db.execute.call_args_list:
row = c.args[1]
assert row["house_id"] == 54321
assert row["confidence"] == pytest.approx(0.9)
assert row["notes"] == "match_method=fingerprint"
assert row["total_floors"] == 25
def test_save_row_contains_removed_date():
"""Item with removed_date set → row dict has removed_date populated."""
db = MagicMock()
items = [
ValuationHistoryItem(
area_m2=60.0,
rooms=2,
floor=4,
start_price=11_000_000,
last_price=10_500_000,
publish_date=date(2024, 1, 15),
removed_date=date(2024, 5, 20),
exposure_days=125,
status="Снято",
),
]
result = _make_result(items)
with patch(
"app.services.estimator.match_or_create_house",
return_value=(99, 1.0, "new"),
):
_save_yandex_history_items(db, result)
row = db.execute.call_args_list[0].args[1]
assert row["removed_date"] == date(2024, 5, 20)
def test_save_row_removed_date_none_when_active():
"""Active listing (status В продаже) → removed_date=None in row."""
db = MagicMock()
items = [
ValuationHistoryItem(
area_m2=42.0,
rooms=1,
floor=2,
start_price=7_500_000,
last_price=7_500_000,
publish_date=date(2024, 6, 1),
removed_date=None,
exposure_days=10,
status="В продаже",
),
]
result = _make_result(items)
with patch(
"app.services.estimator.match_or_create_house",
return_value=(7, 1.0, "new"),
):
_save_yandex_history_items(db, result)
row = db.execute.call_args_list[0].args[1]
assert row["removed_date"] is None
def test_save_handles_match_failure_gracefully():
"""If match_or_create_house raises, save continues with house_id=None, confidence=0."""
db = MagicMock()
items = [
ValuationHistoryItem(
area_m2=42.0,
rooms=1,
floor=2,
start_price=7_500_000,
last_price=7_500_000,
publish_date=date(2024, 6, 1),
removed_date=None,
exposure_days=10,
status="В продаже",
),
]
result = _make_result(items)
with patch(
"app.services.estimator.match_or_create_house",
side_effect=RuntimeError("simulated lookup failure"),
):
saved = _save_yandex_history_items(db, result)
assert saved == 1
row = db.execute.call_args_list[0].args[1]
assert row["house_id"] is None
assert row["confidence"] == pytest.approx(0.0)
assert row["notes"] is None
def test_save_match_called_with_address_and_year():
"""match_or_create_house receives correct address and year_built from result.house."""
db = MagicMock()
result = _make_result(_two_items())
with patch(
"app.services.estimator.match_or_create_house",
return_value=(1, 1.0, "new"),
) as m:
_save_yandex_history_items(db, result)
kwargs = m.call_args.kwargs
assert kwargs["address"] == result.address
assert kwargs["year_built"] == result.house.year_built
assert kwargs["ext_source"] == "yandex_valuation"