gendesign/backend/tests/workers/test_ird_harvest.py
bot-backend d7e5f53789
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / changes (push) Successful in 7s
CI / backend-tests (push) Successful in 5m51s
CI / backend-tests (pull_request) Successful in 5m45s
feat(sf): ИРД-harvest Celery task → ird_overlays (#1067 B5)
Periodic harvest готовых НСПД ИРД-слоёв (ЗОУИТ 37577-581, ПЗЗ-границы 875838,
ОКН 875840, ООПТ/леса/вода 875845/866/832/835) по кварталам ЕКБ (66:41:* из
cad_parcels) → grid-walk → upsert ird_overlays (м.132) по (source_layer_id,
geom_data_id). Геометрия 3857→4326. content_* НЕ трогаем (резолвер D9). SAVEPOINT
per-feature; сбой квартала/слоя изолирован. Beat: ird-harvest-weekly (Mon 05:00 МСК).

Refs #1067 (B5). Тесты: 3 passed.
2026-06-06 21:45:32 +03:00

133 lines
4.6 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.

"""Тесты harvest_ird_overlays (#1067 B5) — НСПД ИРД-harvest → ird_overlays.
Сеть/БД не дёргаются: мокаем NSPDClient + SessionLocal, проверяем grid-walk loop,
skip битых фич и upsert-параметры.
"""
from __future__ import annotations
from contextlib import contextmanager
from typing import Any
from app.workers.tasks import ird_harvest
class _Feat:
def __init__(self, geometry: dict[str, Any] | None, properties: dict[str, Any]) -> None:
self.geometry = geometry
self.properties = properties
class _SearchRes:
def __init__(self, feat: _Feat | None) -> None:
self.first = feat
class _FakeClient:
"""NSPDClient-двойник: квартал → полигон (3857), grid-walk → 1 фича на слой."""
def __init__(self, *a: Any, **kw: Any) -> None:
self.grid_calls: list[int] = []
def search_by_cad(self, cad: str, thematic_id: int = 1) -> _SearchRes:
ring = [[0, 0], [100, 0], [100, 100], [0, 100], [0, 0]]
geom = {"type": "Polygon", "coordinates": [ring]}
return _SearchRes(_Feat(geom, {"cad": cad}))
def get_features_in_bbox_grid(self, layer_id: int, bbox: Any, **kw: Any) -> list[_Feat]:
self.grid_calls.append(layer_id)
return [
_Feat(
{"type": "Polygon", "coordinates": [[[1, 1], [2, 1], [2, 2], [1, 1]]]},
{
"geom_data_id": 1000 + layer_id,
"reg_numb_border": "66:41-6.1",
"category": 36940,
"subcategory": 5,
},
)
]
class _FakeDB:
"""Session-двойник: пишет execute-вызовы, begin_nested как no-op CM."""
def __init__(self) -> None:
self.executed: list[tuple[Any, dict[str, Any] | None]] = []
self.committed = False
self.closed = False
@contextmanager
def begin_nested(self) -> Any:
yield
def execute(self, sql: Any, params: dict[str, Any] | None = None) -> Any:
self.executed.append((sql, params))
return None
def commit(self) -> None:
self.committed = True
def close(self) -> None:
self.closed = True
def _patch(monkeypatch: Any, db: _FakeDB) -> _FakeClient:
client = _FakeClient()
monkeypatch.setattr(ird_harvest, "NSPDClient", lambda *a, **kw: client)
monkeypatch.setattr(ird_harvest, "SessionLocal", lambda: db)
return client
def test_harvest_explicit_quarters_upserts_all_layers(monkeypatch: Any) -> None:
"""1 квартал × N ИРД-слоёв → upsert по фиче на слой; commit + close."""
db = _FakeDB()
client = _patch(monkeypatch, db)
n_layers = len(ird_harvest.IRD_HARVEST_LAYER_KEYS)
result = ird_harvest.harvest_ird_overlays(quarters=["66:41:0204016"])
assert result == {"quarters": 1, "features": n_layers}
assert len(client.grid_calls) == n_layers # grid-walk по каждому слою
assert len(db.executed) == n_layers # upsert по фиче на слой
assert db.committed is True
assert db.closed is True
# параметры upsert: geom как JSON-строка, layer_kind из ключей
_, params = db.executed[0]
assert params is not None
assert params["layer_kind"] in ird_harvest.IRD_HARVEST_LAYER_KEYS
assert isinstance(params["geojson"], str)
assert params["reg_numb_border"] == "66:41-6.1"
def test_harvest_skips_empty_quarter(monkeypatch: Any) -> None:
"""Квартал без геометрии в НСПД → 0 кварталов/фич, без падения."""
db = _FakeDB()
client = _patch(monkeypatch, db)
monkeypatch.setattr(client, "search_by_cad", lambda cad, thematic_id=1: _SearchRes(None))
result = ird_harvest.harvest_ird_overlays(quarters=["66:41:9999999"])
assert result == {"quarters": 0, "features": 0}
assert db.executed == []
assert db.committed is True
def test_upsert_feature_skips_missing_geom_data_id(monkeypatch: Any) -> None:
"""Фича без geom_data_id / geometry → не пишется (defensive)."""
db = _FakeDB()
no_id = _Feat({"type": "Polygon", "coordinates": []}, {"reg_numb_border": "x"})
no_geom = _Feat(None, {"geom_data_id": 5})
assert (
ird_harvest._upsert_feature(
db, layer_id=37577, layer_kind="zouit_okn", feature=no_id, fetched_at="t"
)
is False
)
assert (
ird_harvest._upsert_feature(
db, layer_id=37577, layer_kind="zouit_okn", feature=no_geom, fetched_at="t"
)
is False
)
assert db.executed == []