gendesign/backend/tests/services/scrapers/test_gisogd_so_client.py
lekss361 189ecc81f8
All checks were successful
Deploy / build-backend (push) Successful in 1m18s
Deploy / build-frontend (push) Has been skipped
Deploy / changes (push) Successful in 5s
Deploy / build-worker (push) Successful in 2m28s
Deploy / deploy (push) Successful in 1m17s
feat(sf): ingest функц.зон генплана ЕКБ-2045 из ГИСОГД-СО WFS → ekb_genplan_functional_zone (#1137)
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-07 12:03:39 +00:00

189 lines
6.9 KiB
Python
Raw 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.

"""Тесты gisogd_so_client (#1058) — каталог-резолв + WFS fetch.
Сеть не дёргается: мокаем _http_get_json на module-level.
"""
from __future__ import annotations
from typing import Any
from app.services.scrapers import gisogd_so_client
from app.services.scrapers.gisogd_so_client import (
_LAYER_FALLBACK,
_find_layer_in_tree,
fetch_functional_zones,
resolve_functional_zone_layer,
)
# ── _find_layer_in_tree ───────────────────────────────────────────────────────
_LAYER_KNOWN = "agate_ekbgo:agate_ekbgo_geo_urban_10_functional_zone_f6c5926"
_LAYER_ABC = "agate_ekbgo:agate_ekbgo_geo_urban_10_functional_zone_abc123"
_LAYER_XYZ = "agate_ekbgo:agate_ekbgo_geo_urban_10_functional_zone_xyz789"
def test_find_layer_direct_match() -> None:
"""Узел с name == 'Функциональные зоны' → возвращает value."""
node = {"name": "Функциональные зоны", "value": _LAYER_KNOWN}
result = _find_layer_in_tree(node)
assert result == _LAYER_KNOWN
def test_find_layer_in_nested_children() -> None:
"""Вложенный узел в children → резолвится рекурсивно."""
catalog = {
"name": "root",
"children": [
{
"name": "Зонирование",
"children": [
{"name": "Функциональные зоны", "value": _LAYER_ABC},
],
},
],
}
result = _find_layer_in_tree(catalog)
assert result == _LAYER_ABC
def test_find_layer_not_found_returns_none() -> None:
"""Узел не найден → None."""
catalog = {"name": "Другой слой", "value": "some:layer", "children": []}
result = _find_layer_in_tree(catalog)
assert result is None
def test_find_layer_non_dict_node_returns_none() -> None:
"""Нода не dict → None (безопасный обход)."""
assert _find_layer_in_tree(None) is None # type: ignore[arg-type]
assert _find_layer_in_tree("string") is None # type: ignore[arg-type]
assert _find_layer_in_tree([]) is None # type: ignore[arg-type]
# ── resolve_functional_zone_layer ─────────────────────────────────────────────
def test_resolve_layer_from_catalog(monkeypatch: Any) -> None:
"""Каталог доступен → корректный layer из дерева."""
catalog = {
"name": "root",
"children": [
{"name": "Функциональные зоны", "value": _LAYER_XYZ},
],
}
monkeypatch.setattr(gisogd_so_client, "_http_get_json", lambda *a, **kw: catalog)
result = resolve_functional_zone_layer()
assert result == _LAYER_XYZ
def test_resolve_layer_fallback_on_catalog_error(monkeypatch: Any) -> None:
"""Каталог недоступен → fallback-хардкод."""
def _raise(*a: Any, **kw: Any) -> None:
raise Exception("connection refused")
monkeypatch.setattr(gisogd_so_client, "_http_get_json", _raise)
result = resolve_functional_zone_layer()
assert result == _LAYER_FALLBACK
def test_resolve_layer_fallback_node_not_found(monkeypatch: Any) -> None:
"""Каталог пришёл но нужного узла нет → fallback-хардкод."""
catalog = {
"name": "root",
"children": [{"name": "Другой слой", "value": "some:other:layer"}],
}
monkeypatch.setattr(gisogd_so_client, "_http_get_json", lambda *a, **kw: catalog)
result = resolve_functional_zone_layer()
assert result == _LAYER_FALLBACK
# ── fetch_functional_zones ────────────────────────────────────────────────────
def _make_fc(n: int) -> dict[str, Any]:
"""FeatureCollection с n фичами."""
return {
"type": "FeatureCollection",
"features": [
{
"geometry": {"type": "Polygon", "coordinates": []},
"properties": {"urban10_globalid": f"uuid-{i}"},
}
for i in range(n)
],
}
def test_fetch_returns_features_single_request(monkeypatch: Any) -> None:
"""Успешный одиночный WFS-запрос → список фич."""
fc = _make_fc(6436)
calls: list[dict[str, Any]] = []
def _mock_get(url: str, params: dict[str, str] | None = None, **kw: Any) -> Any:
calls.append({"url": url, "params": params})
return fc
monkeypatch.setattr(gisogd_so_client, "_http_get_json", _mock_get)
features = fetch_functional_zones("agate_ekbgo:test_layer")
assert len(features) == 6436
assert len(calls) == 1 # один запрос
assert calls[0]["params"]["typeNames"] == "agate_ekbgo:test_layer"
def test_fetch_pagination_on_timeout(monkeypatch: Any) -> None:
"""Первый запрос — TimeoutException → постраничная загрузка."""
import httpx
first_call = True
def _mock_get(url: str, params: dict[str, str] | None = None, **kw: Any) -> Any:
nonlocal first_call
if first_call:
first_call = False
raise httpx.TimeoutException("timeout")
# Постраничные: 3 фичи на первой странице, пустая на второй.
start = int((params or {}).get("startIndex", 0))
if start == 0:
return _make_fc(3)
return {"type": "FeatureCollection", "features": []}
monkeypatch.setattr(gisogd_so_client, "_http_get_json", _mock_get)
features = fetch_functional_zones("agate_ekbgo:test_layer")
assert len(features) == 3
def test_fetch_empty_on_wfs_error(monkeypatch: Any) -> None:
"""WFS не отвечает → пустой список, без падения."""
def _raise(*a: Any, **kw: Any) -> None:
raise Exception("connection error")
monkeypatch.setattr(gisogd_so_client, "_http_get_json", _raise)
features = fetch_functional_zones("agate_ekbgo:test_layer")
assert features == []
def test_fetch_resolves_layer_when_none(monkeypatch: Any) -> None:
"""type_name=None → вызывается resolve_functional_zone_layer перед WFS."""
resolved: list[str] = []
def _mock_resolve() -> str:
resolved.append("called")
return "agate_ekbgo:resolved_layer"
def _mock_get(url: str, params: dict[str, str] | None = None, **kw: Any) -> Any:
return _make_fc(2)
monkeypatch.setattr(gisogd_so_client, "resolve_functional_zone_layer", _mock_resolve)
monkeypatch.setattr(gisogd_so_client, "_http_get_json", _mock_get)
features = fetch_functional_zones(type_name=None)
assert resolved == ["called"]
assert len(features) == 2