"""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"