"""Тесты 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 == []