"""Тесты harvest_riasurt_sverdl_for_mo / harvest_all_riasurt_sverdl (#108). Сеть/БД не дёргаются: мокаем NSPDClient + SessionLocal. Проверяем delete-per-MO, insert-параметры (layer_topic-классификация, mo_name), skip битых фич, прогон по 5 МО. """ from __future__ import annotations from contextlib import contextmanager from typing import Any from app.workers.tasks import riasurt_sverdl_harvest class _Feat: def __init__(self, geometry: dict[str, Any] | None, properties: dict[str, Any]) -> None: self.geometry = geometry self.properties = properties class _FakeClient: """NSPDClient-двойник: bbox → 1 фича на каждый запрошенный слой.""" def __init__(self, *a: Any, **kw: Any) -> None: self.bbox_calls: list[Any] = [] def get_riasurt_sverdl_in_bbox( self, bbox: Any, layers: list[int] | None = None, **kw: Any ) -> dict[int, list[_Feat]]: self.bbox_calls.append(bbox) layer_ids = layers or list(riasurt_sverdl_harvest.RIASURT_SVERDL_LAYERS.keys()) return { lid: [ _Feat( {"type": "Polygon", "coordinates": [[[1, 1], [2, 1], [2, 2], [1, 1]]]}, {"label": f"zone-{lid}", "reg_numb_border": f"66:35-{lid}"}, ) ] for lid in layer_ids } class _FakeDB: def __init__(self) -> None: self.executed: list[tuple[Any, dict[str, Any] | None]] = [] self.commit_count = 0 self.rollback_count = 0 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.commit_count += 1 def rollback(self) -> None: self.rollback_count += 1 def close(self) -> None: self.closed = True def _patch(monkeypatch: Any, db: _FakeDB) -> _FakeClient: client = _FakeClient() monkeypatch.setattr(riasurt_sverdl_harvest, "NSPDClient", lambda *a, **kw: client) monkeypatch.setattr(riasurt_sverdl_harvest, "SessionLocal", lambda: db) return client def test_harvest_for_mo_deletes_then_inserts(monkeypatch: Any) -> None: """1 МО × 2 слоя → delete-MO + 2 insert; commit + close.""" db = _FakeDB() _patch(monkeypatch, db) res = riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo( "Берёзовский", (6770000.0, 7710000.0, 6782000.0, 7722000.0), [845274, 844478], ) assert res == {"features": 2, "layers": 2} assert db.commit_count == 1 assert db.closed is True # 1 delete + 2 insert assert len(db.executed) == 3 delete_params = db.executed[0][1] assert delete_params == {"mo_name": "Берёзовский"} def test_harvest_for_mo_inserts_correct_topic(monkeypatch: Any) -> None: """layer_topic денормализуется из riasurt_layer_topic; mo_name проставлен.""" db = _FakeDB() _patch(monkeypatch, db) riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo( "Сысерть", (0.0, 0.0, 100.0, 100.0), [845274, 845425] ) insert_params = [p for _, p in db.executed if p and "layer_topic" in p] topics = {p["source_layer_id"]: p["layer_topic"] for p in insert_params} assert topics[845274] == "territorial_zone" assert topics[845425] == "flood_zone" assert all(p["mo_name"] == "Сысерть" for p in insert_params) assert all(p["obshnz"] == f"66:35-{p['source_layer_id']}" for p in insert_params) def test_harvest_for_mo_skips_geomless_feature(monkeypatch: Any) -> None: """Фича без geometry не пишется (skip), счётчик не растёт.""" db = _FakeDB() client = _FakeClient() def only_empty(bbox: Any, layers: list[int] | None = None, **kw: Any) -> dict[int, list[_Feat]]: return {845274: [_Feat(None, {"label": "broken"})]} client.get_riasurt_sverdl_in_bbox = only_empty # type: ignore[method-assign] monkeypatch.setattr(riasurt_sverdl_harvest, "NSPDClient", lambda *a, **kw: client) monkeypatch.setattr(riasurt_sverdl_harvest, "SessionLocal", lambda: db) res = riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo("Арамиль", (0.0, 0.0, 10.0, 10.0)) assert res["features"] == 0 # только delete-MO, никаких insert assert len(db.executed) == 1 def test_harvest_all_iterates_5_mo(monkeypatch: Any) -> None: """harvest_all прогоняет все 5 МО агломерации (при включённом флаге).""" db = _FakeDB() _patch(monkeypatch, db) monkeypatch.setattr( riasurt_sverdl_harvest.settings, "enable_riasurt_harvest", True, raising=False ) res = riasurt_sverdl_harvest.harvest_all_riasurt_sverdl([845274]) assert res["mo"] == 5 # 5 МО × 1 фича каждая assert res["features"] == 5 def test_harvest_all_disabled_by_default(monkeypatch: Any) -> None: """Гейт #108: при выключенном флаге harvest_all возвращает early без WMS-вызовов.""" db = _FakeDB() client = _patch(monkeypatch, db) monkeypatch.setattr( riasurt_sverdl_harvest.settings, "enable_riasurt_harvest", False, raising=False ) res = riasurt_sverdl_harvest.harvest_all_riasurt_sverdl([845274]) assert res == {"mo": 0, "features": 0} # ни одного WMS-запроса, ни одной записи в БД assert client.bbox_calls == [] assert db.executed == [] def test_mo_bboxes_has_5_agglomeration_municipalities() -> None: """MO_BBOXES содержит ровно 5 МО окраин агломерации.""" assert set(riasurt_sverdl_harvest.MO_BBOXES) == { "Берёзовский", "Верхняя Пышма", "Среднеуральск", "Арамиль", "Сысерть", }