"""Тесты контракта suggest→ФИАС (feat/tradein-fias-tier-matching). Покрывают: 1. geocoder._dadata_suggest — fias_id прокидывается ТОЛЬКО у house-level кандидата (DaData kind='house' ⇔ fias_level 8/9); у street/city/plot None. 2. SuggestItem — сериализует fias_id (house-тир) и None у не-DaData тиров. 3. TradeInEstimateInput — принимает target_fias_id и дефолтит None при отсутствии. """ import os # Settings требует DATABASE_URL на импорте — ставим dummy DSN до app-импортов. os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") from unittest.mock import AsyncMock, patch from app.api.v1.geocode import SuggestItem from app.schemas.trade_in import TradeInEstimateInput from app.services.dadata import DadataSuggestion from app.services.geocoder import GeocodeSuggestion, _dadata_suggest def _sugg(kind: str, fias_id: str | None) -> DadataSuggestion: return DadataSuggestion( value=f"тест {kind}", unrestricted_value=f"г Екатеринбург, тест {kind}", lat=56.84, lon=60.60, fias_id=fias_id, house="1" if kind == "house" else None, street="Тестовая", city="Екатеринбург", kind=kind, ) # ── 1. _dadata_suggest: fias_id только у house-тира ────────────────────────── async def test_dadata_suggest_fias_only_for_house_kind(): raw = [ _sugg("house", "0a1b2c3d-0000-4000-8000-00000000aaaa"), _sugg("street", "0a1b2c3d-0000-4000-8000-00000000bbbb"), _sugg("city", "0a1b2c3d-0000-4000-8000-00000000cccc"), _sugg("plot", "0a1b2c3d-0000-4000-8000-00000000dddd"), ] with patch( "app.services.geocoder.dadata.suggest_addresses", new=AsyncMock(return_value=raw), ): out = await _dadata_suggest("тест", limit=8) assert len(out) == 4 house = next(s for s in out if s.kind == "house") assert house.fias_id == "0a1b2c3d-0000-4000-8000-00000000aaaa" # У всех НЕ-house тиров fias_id обнулён, даже если DaData его прислал. for s in out: if s.kind != "house": assert s.fias_id is None async def test_dadata_suggest_house_without_fias_stays_none(): """house-кандидат без fias у DaData → fias_id None (не падаем).""" with patch( "app.services.geocoder.dadata.suggest_addresses", new=AsyncMock(return_value=[_sugg("house", None)]), ): out = await _dadata_suggest("тест", limit=8) assert len(out) == 1 assert out[0].fias_id is None # ── 2. SuggestItem сериализация ────────────────────────────────────────────── def test_suggest_item_serializes_fias_id(): item = SuggestItem( label="ул Малышева, д 30", full_address="г Екатеринбург, ул Малышева, д 30", lat=56.84, lon=60.60, kind="house", fias_id="0a1b2c3d-0000-4000-8000-00000000aaaa", ) dumped = item.model_dump() assert dumped["fias_id"] == "0a1b2c3d-0000-4000-8000-00000000aaaa" def test_suggest_item_fias_id_defaults_none_for_non_dadata_tier(): """Не-DaData тир (Yandex/Nominatim/cadastral) не передаёт fias_id → None.""" gs = GeocodeSuggestion( label="Малышева 30", full_address="г Екатеринбург, ул Малышева, д 30", lat=56.84, lon=60.60, kind="house", ) assert gs.fias_id is None item = SuggestItem( label=gs.label, full_address=gs.full_address, lat=gs.lat, lon=gs.lon, kind=gs.kind, fias_id=gs.fias_id, ) assert item.model_dump()["fias_id"] is None # ── 3. TradeInEstimateInput.target_fias_id ─────────────────────────────────── def test_estimate_input_accepts_target_fias_id(): payload = TradeInEstimateInput( address="ул Малышева 30, Екатеринбург", area_m2=50.0, rooms=2, target_fias_id="0a1b2c3d-0000-4000-8000-00000000aaaa", ) assert payload.target_fias_id == "0a1b2c3d-0000-4000-8000-00000000aaaa" def test_estimate_input_target_fias_id_defaults_none(): payload = TradeInEstimateInput( address="ул Малышева 30, Екатеринбург", area_m2=50.0, rooms=2, ) assert payload.target_fias_id is None