gendesign/tradein-mvp/backend/tests/test_geocoder_nominatim_lookup.py
bot-backend feff55bea9
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Successful in 2m33s
chore(tradein/geocoder): удалить остатки скриптов Яндекс-геокодера, часть 3 (#2593)
Удалены мёртвые ops-скрипты Yandex Geocoder (уже недостижимы после #2593 частей 1-2):
- scripts/_yandex_reverse.py, scripts/audit_address_mismatch.py, scripts/backfill_house_coords.py
- их тесты + осиротевшая фикстура tests/fixtures/yandex_geocode_sample.json
- осиротевшие SQL-хелперы scripts/audit_address_sample.sql, scripts/address_audit_report.sql (использовались только audit_address_mismatch.py)

Обновлена документация (осиротевшие упоминания YANDEX_GEOCODER_API_KEY / удалённых
скриптов): scripts/README.md, tradein-mvp/DEPLOY.md, docs/Secrets_Rotation_Policy.md.

Добавлен tests/test_geocoder_nominatim_lookup.py — покрывает _nominatim_lookup
(единственный живой внешний геокодер) на предмет реальной передачи city_hint в
исходящий HTTP-запрос к Nominatim; закрывает дыру в coverage, оставленную
удалёнными yandex-тестами.

Refs #2593
2026-07-31 23:16:19 +03:00

91 lines
4.4 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.

"""Тесты `_nominatim_lookup` — city реально доходит до исходящего HTTP-запроса.
#2593 (часть 3): Yandex Geocoder полностью удалён из проекта, вместе с ним ушли
`_yandex_reverse.py` + `tests/test_audit_address_mismatch.py` +
`tests/test_backfill_house_coords.py` — они были единственной проверкой, что
`city`/`city_hint` реально передаётся во внешний геокодер, а не только влияет на
cache-ключ (см. `tests/test_geocoder_city_hint.py`, который мокает
`_nominatim_lookup`/`_nominatim_suggest` целиком и потому не видит их внутренности).
Nominatim теперь единственный живой внешний провайдер (`_nominatim_lookup`
docstring, `app/services/geocoder.py`) — этот файл закрывает получившуюся дыру:
мокает HTTP-транспорт (`httpx.MockTransport`, паттерн из `test_geocoder_bbox.py` /
`tests/services/test_dadata.py`) и проверяет параметр `q` реального исходящего
GET-запроса к `nominatim.openstreetmap.org/search`.
"""
from __future__ import annotations
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from unittest.mock import patch
import httpx
from app.services.geocoder import _nominatim_lookup
# EKB-центр (Плотинка) — внутри tight EKB bbox, `_nominatim_query` его примет
# без похода во второй (typo-variant) тир.
_SAMPLE_ITEM = {
"lat": "56.838",
"lon": "60.605",
"class": "building",
"display_name": "ул. Малышева, 30, Екатеринбург",
"address": {"state": "Свердловская область"},
}
# Snapshot реального httpx.AsyncClient ДО patch'а — фабрика ниже использует именно
# его с подменённым transport (паттерн tests/services/test_dadata.py: избегает
# recursion, если бы `httpx.AsyncClient` патчился поверх самого себя).
_REAL_ASYNC_CLIENT = httpx.AsyncClient
def _async_client_factory(transport: httpx.MockTransport):
def factory(*_: object, **__: object) -> httpx.AsyncClient:
return _REAL_ASYNC_CLIENT(transport=transport)
return factory
def _capturing_transport(captured_q: list[str]) -> httpx.MockTransport:
def handler(request: httpx.Request) -> httpx.Response:
captured_q.append(request.url.params.get("q", ""))
return httpx.Response(200, json=[_SAMPLE_ITEM])
return httpx.MockTransport(handler)
async def test_nominatim_lookup_sends_city_hint_in_query_param() -> None:
"""city_hint="Нижний Тагил" должен попасть в q= реального GET-запроса.
Регрессия, о которой явно предупреждает docstring `_nominatim_lookup` (#2580 C):
city_hint обязан влиять на сам запрос к провайдеру, не только на cache-ключ.
"""
captured_q: list[str] = []
transport = _capturing_transport(captured_q)
with patch("app.services.geocoder.httpx.AsyncClient", _async_client_factory(transport)):
result = await _nominatim_lookup("Ленина, 1", city_hint="Нижний Тагил")
assert captured_q, "запрос к Nominatim не был отправлен"
assert captured_q[0] == "Нижний Тагил, Ленина, 1"
assert result is not None
assert result.provider == "nominatim"
async def test_nominatim_lookup_no_city_sends_bare_address() -> None:
"""Без city_hint и без маркера города в тексте — q= остаётся bare-адресом.
Guard против регрессии в молчаливый дефолт на конкретный город (#2576/#2593)
— до фикса #2576 сюда молча подставлялся "Екатеринбург".
"""
captured_q: list[str] = []
transport = _capturing_transport(captured_q)
with patch("app.services.geocoder.httpx.AsyncClient", _async_client_factory(transport)):
result = await _nominatim_lookup("Малышева, 30")
assert captured_q == ["Малышева, 30"]
assert result is not None