"""Тесты harvest_planning_projects (#1085) — геопортал-WFS ППТ/ПМТ → planning_projects. Сеть/БД не дёргаются: мокаем EKBGeoportalClient + SessionLocal, проверяем фетч обоих слоёв, project_type-маппинг, skip битых фич, нормализацию года и upsert-параметры. """ from __future__ import annotations from contextlib import contextmanager from typing import Any from app.workers.tasks import planning_harvest class _Feat: def __init__(self, geometry: dict[str, Any] | None, properties: dict[str, Any]) -> None: self.geometry = geometry self.properties = properties class _FakeClient: """EKBGeoportalClient-двойник: layer → 1 фича на слой.""" def __init__(self, *a: Any, **kw: Any) -> None: self.bbox_calls: list[str] = [] def features_in_bbox(self, layer: str, bbox: Any) -> list[_Feat]: self.bbox_calls.append(layer) return [ _Feat( {"type": "Polygon", "coordinates": [[[60, 56], [60.1, 56], [60, 56.1], [60, 56]]]}, { "key": 1000010173321902 if layer == "ppt" else 1000010209297292, "full_name": f"ПАГЕ № 1 ({layer})", "doc_status": 1, "doc_status_name": "действующий", "project_name": "Тестовый проект", "dmd_actual_year": "2021", }, ) ] class _FakeDB: 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(planning_harvest, "EKBGeoportalClient", lambda *a, **kw: client) monkeypatch.setattr(planning_harvest, "SessionLocal", lambda: db) return client def test_harvest_both_layers(monkeypatch: Any) -> None: """ППТ + ПМТ → upsert по фиче на слой; project_type-маппинг; year нормализован в int.""" db = _FakeDB() client = _patch(monkeypatch, db) result = planning_harvest.harvest_planning_projects() assert result == {"features": 2} assert sorted(client.bbox_calls) == ["pmt", "ppt"] assert len(db.executed) == 2 types = {params["project_type"] for _, params in db.executed if params} assert types == {"ppt", "pmt"} _, p0 = db.executed[0] assert p0 is not None assert p0["dmd_actual_year"] == 2021 # строка "2021" → int assert p0["doc_status"] == "1" # приведён к строке assert isinstance(p0["geojson"], str) def test_skips_feature_without_key_or_geom() -> None: """Фича без key / geometry → не пишется (defensive).""" db = _FakeDB() no_key = _Feat({"type": "Polygon", "coordinates": []}, {"full_name": "x"}) no_geom = _Feat(None, {"key": 5}) assert ( planning_harvest._upsert_feature(db, project_type="ppt", feature=no_key, fetched_at="t") is False ) assert ( planning_harvest._upsert_feature(db, project_type="pmt", feature=no_geom, fetched_at="t") is False ) assert db.executed == [] def test_year_normalization() -> None: assert planning_harvest._to_int("2021") == 2021 assert planning_harvest._to_int(2021) == 2021 assert planning_harvest._to_int(None) is None assert planning_harvest._to_int("н/д") is None