gendesign/tradein-mvp/backend/tests/test_3257_house_facts_endpoint.py
lekss361 1d45ac0747
Some checks failed
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m2s
Deploy Trade-In / build-backend (push) Successful in 1m4s
Deploy Trade-In / deploy (push) Has been cancelled
feat(mera/estimate): ручка фактов дома для предзаполнения формы + гейт «этаж не выше дома» (#3257)
2026-08-29 20:34:00 +00:00

166 lines
5.6 KiB
Python
Raw Permalink 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.

"""#3257: предзаполнение формы оценки фактами дома.
Две независимые правки:
1. TradeInEstimateInput — валидатор floor <= total_floors (когда оба заданы).
2. GET /api/v1/geocode/house-facts — предзаполнение total_floors/year_built/
house_type из справочника `houses` (переиспользует _lookup_house_facts).
Стиль моков БД/эндпоинта — как в test_geocode_reverse_api.py /
test_3234_house_facts_fallback.py.
"""
from __future__ import annotations
import os
import sys
from unittest.mock import MagicMock, patch
# DATABASE_URL required by config before any app import.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
# WeasyPrint stub — not installed in CI without GTK.
_wp_mock = MagicMock()
sys.modules.setdefault("weasyprint", _wp_mock)
import pytest # noqa: E402
from fastapi import FastAPI # noqa: E402
from fastapi.testclient import TestClient # noqa: E402
from pydantic import ValidationError # noqa: E402
from app.api.v1 import geocode as geocode_module # noqa: E402
from app.core.db import get_db # noqa: E402
from app.schemas.trade_in import TradeInEstimateInput # noqa: E402
from app.services.estimator import _HouseFacts # noqa: E402
# ── A. TradeInEstimateInput.floor <= total_floors ───────────────────────────
def _payload(**overrides: object) -> dict:
base: dict[str, object] = {"address": "ЕКБ, ул. Учителей, 18", "area_m2": 38.8, "rooms": 1}
base.update(overrides)
return base
def test_floor_equal_total_floors_is_allowed() -> None:
model = TradeInEstimateInput(**_payload(floor=9, total_floors=9))
assert model.floor == model.total_floors == 9
def test_floor_less_than_total_floors_is_allowed() -> None:
model = TradeInEstimateInput(**_payload(floor=4, total_floors=9))
assert model.floor == 4
@pytest.mark.parametrize(
"kwargs",
[
{"floor": 5}, # total_floors не задан
{"total_floors": 9}, # floor не задан
{}, # оба не заданы
],
)
def test_partial_input_skips_validator(kwargs: dict) -> None:
"""Частичный ввод легален — гейт не срабатывает, пока не заданы ОБА поля."""
model = TradeInEstimateInput(**_payload(**kwargs))
assert model is not None
def test_floor_greater_than_total_floors_is_rejected() -> None:
with pytest.raises(ValidationError) as exc_info:
TradeInEstimateInput(**_payload(floor=12, total_floors=9))
message = str(exc_info.value)
assert "12" in message
assert "9" in message
# ── B. GET /api/v1/geocode/house-facts ──────────────────────────────────────
@pytest.fixture
def app() -> FastAPI:
application = FastAPI()
application.include_router(geocode_module.router, prefix="/api/v1/geocode")
application.dependency_overrides[get_db] = lambda: MagicMock()
return application
def test_house_facts_not_found_returns_200_found_false(app: FastAPI) -> None:
client = TestClient(app)
with patch(
"app.api.v1.geocode._lookup_house_facts",
return_value=None,
):
r = client.get("/api/v1/geocode/house-facts?lat=56.838&lon=60.595")
assert r.status_code == 200
body = r.json()
assert body == {
"found": False,
"total_floors": None,
"year_built": None,
"house_type": None,
"source": None,
}
def test_house_facts_found_returns_facts(app: FastAPI) -> None:
facts = _HouseFacts(
house_id=42,
total_floors=9,
year_built=1975,
house_type="panel",
material_walls=None,
)
client = TestClient(app)
with patch(
"app.api.v1.geocode._lookup_house_facts",
return_value=facts,
) as lookup_mock:
r = client.get("/api/v1/geocode/house-facts?lat=56.838&lon=60.595")
assert r.status_code == 200
body = r.json()
assert body == {
"found": True,
"total_floors": 9,
"year_built": 1975,
"house_type": "panel",
"source": "houses",
}
# target_house_id не резолвился (fias_id не передан) → None прокинут дальше
assert lookup_mock.call_args.kwargs["target_house_id"] is None
assert lookup_mock.call_args.kwargs["lat"] == 56.838
assert lookup_mock.call_args.kwargs["lon"] == 60.595
def test_house_facts_resolves_house_id_by_fias(app: FastAPI) -> None:
"""fias_id передан и резолвится в houses.id → он уезжает в _lookup_house_facts
как target_house_id (обходя geo-фолбэк по lat/lon)."""
facts = _HouseFacts(
house_id=42,
total_floors=9,
year_built=1975,
house_type="panel",
material_walls=None,
)
client = TestClient(app)
with (
patch(
"app.api.v1.geocode._resolve_house_id_by_fias",
return_value=42,
),
patch(
"app.api.v1.geocode._lookup_house_facts",
return_value=facts,
) as lookup_mock,
):
r = client.get("/api/v1/geocode/house-facts?lat=56.838&lon=60.595&fias_id=some-guid")
assert r.status_code == 200
assert lookup_mock.call_args.kwargs["target_house_id"] == 42
def test_house_facts_missing_required_query_params_is_422(app: FastAPI) -> None:
client = TestClient(app)
r = client.get("/api/v1/geocode/house-facts?lat=56.838")
assert r.status_code == 422