gendesign/tradein-mvp/backend/tests/test_estimator_radius_m_2044.py
bot-backend 3bf2c1a48a
All checks were successful
CI / changes (pull_request) Successful in 11s
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
feat(tradein/api): #2044 BE-2 — optional radius_m on house-analytics + sell-time-sensitivity
radius_m (schema + estimate_quality comp-search) уже в main (e23dabe4). Здесь —
остаток: два derived-роута принимают radius_m как optional query-param.

- GET /estimate/{id}/house-analytics и /sell-time-sensitivity: += radius_m
  (int|None). None → байт-идентичная авто-логика (расширение до 300 м только при
  <8 in-house записей). Задан → явный радиус расширения выборки домов, клампится
  в [100,5000], возвращается в radius_m ответа.

Refs #2044
2026-07-02 16:51:11 +03:00

61 lines
2.4 KiB
Python

"""Unit tests for #2044 (BE-2) optional radius_m input.
The schema field + estimate_quality wiring (base_radius_m / fallback_radius_m)
landed in e23dabe4; these tests lock the contract: radius_m is an optional,
bounded field whose None value preserves the byte-identical two-tier default
radii, while an explicit value overrides both search radii.
NOTE: importing app.services.estimator pulls app.core.config.Settings which
requires DATABASE_URL. Set it BEFORE importing app modules.
"""
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
import pytest
from pydantic import ValidationError
from app.schemas.trade_in import TradeInEstimateInput
from app.services import estimator
def test_radius_m_defaults_to_none() -> None:
payload = TradeInEstimateInput(address="ул. Тестовая, 1", area_m2=50, rooms=2)
assert payload.radius_m is None
def test_radius_m_accepts_valid_int() -> None:
payload = TradeInEstimateInput(address="ул. Тестовая, 1", area_m2=50, rooms=2, radius_m=1500)
assert payload.radius_m == 1500
def test_radius_m_out_of_range_rejected() -> None:
with pytest.raises(ValidationError):
TradeInEstimateInput(address="ул. Тестовая, 1", area_m2=50, rooms=2, radius_m=50)
with pytest.raises(ValidationError):
TradeInEstimateInput(address="ул. Тестовая, 1", area_m2=50, rooms=2, radius_m=9000)
def test_radius_none_preserves_default_radii() -> None:
# estimate_quality resolves `payload.radius_m or DEFAULT_RADIUS_M`. None must
# keep the byte-identical two-tier defaults; an explicit value overrides both.
assert (None or estimator.DEFAULT_RADIUS_M) == estimator.DEFAULT_RADIUS_M
assert (None or estimator.FALLBACK_RADIUS_M) == estimator.FALLBACK_RADIUS_M
assert (500 or estimator.DEFAULT_RADIUS_M) == 500
assert (500 or estimator.FALLBACK_RADIUS_M) == 500
def test_api_radius_expand_clamp_bounds() -> None:
# The house-analytics / sell-time endpoints clamp an explicit radius into
# [100, 5000] before ST_DWithin. Mirror that guard so the contract is locked.
def _clamp(r: int) -> int:
return max(100, min(r, 5000))
assert _clamp(50) == 100
assert _clamp(300) == 300
assert _clamp(9000) == 5000
if __name__ == "__main__": # pragma: no cover
raise SystemExit(pytest.main([__file__, "-q"]))