gendesign/backend/tests/workers/test_genplan_zones_sync.py
bot-backend 6bb9de92c2
Some checks failed
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Successful in 6m25s
CI / backend-tests (push) Has been cancelled
feat(sf): ingest функц.зон генплана ЕКБ-2045 из ГИСОГД-СО WFS → ekb_genplan_functional_zone (#1058)
2026-06-07 14:56:48 +03:00

201 lines
7.4 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.

"""Тесты sync_functional_zones (#1058) — ГИСОГД-СО WFS → ekb_genplan_functional_zone.
Сеть/БД не дёргаются: мокаем fetch_functional_zones + SessionLocal, проверяем
маппинг properties → колонки, globalid-дедуп, upsert-идемпотентность.
"""
from __future__ import annotations
from contextlib import contextmanager
from typing import Any
from app.workers.tasks import genplan_zones_sync
# ── Фейковые данные ──────────────────────────────────────────────────────────
def _make_feature(
globalid: str = "aaaa-1111",
full_name: str | None = "Жилые зоны",
classid: str | None = "Ж",
status: str | None = "Существующий",
area_ha: float | None = 12.5,
doc_ref: str | None = "REF-001",
) -> dict[str, Any]:
return {
"geometry": {
"type": "Polygon",
"coordinates": [[[60.5, 56.8], [60.6, 56.8], [60.5, 56.8]]],
},
"properties": {
"urban10_globalid": globalid,
"full_name": full_name,
"urban10_classid_v": "Жилые зоны (fallback)",
"urban10_classid": classid,
"urban10_status_v": status,
"urban10_area": area_ha,
"info_set_key_ref": doc_ref,
},
}
# ── Фейковая БД ──────────────────────────────────────────────────────────────
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 test_sync_maps_properties_to_columns(monkeypatch: Any) -> None:
"""Проверяем маппинг properties → параметры UPSERT."""
feat = _make_feature(
globalid="uuid-abc",
full_name="Жилые зоны",
classid="Ж",
status="Существующий",
area_ha=10.5,
doc_ref="DOC-777",
)
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [feat])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
assert result == {"zones": 1}
assert db.committed is True
assert db.closed is True
assert len(db.executed) == 1
_, params = db.executed[0]
assert params is not None
assert params["globalid"] == "uuid-abc"
assert params["zone_type"] == "Жилые зоны"
assert params["zone_class_code"] == "Ж"
assert params["status"] == "Существующий"
assert params["area_ha"] == 10.5
assert params["doc_ref"] == "DOC-777"
assert isinstance(params["geojson"], str)
assert isinstance(params["raw_props"], str)
def test_sync_fallback_to_classid_v_when_no_full_name(monkeypatch: Any) -> None:
"""full_name=None → zone_type берётся из urban10_classid_v."""
feat = _make_feature(full_name=None)
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [feat])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
genplan_zones_sync.sync_functional_zones()
_, params = db.executed[0]
assert params is not None
assert params["zone_type"] == "Жилые зоны (fallback)"
def test_sync_skips_feature_without_globalid(monkeypatch: Any) -> None:
"""Фича без globalid → пропускается (defensive), не вызывает UPSERT."""
feat: dict[str, Any] = {
"geometry": {"type": "Polygon", "coordinates": []},
"properties": {"full_name": "Жилые зоны"}, # нет urban10_globalid
}
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [feat])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
assert result == {"zones": 0}
assert db.executed == []
assert db.committed is True
def test_sync_skips_feature_without_geometry(monkeypatch: Any) -> None:
"""Фича без geometry → пропускается."""
feat: dict[str, Any] = {
"geometry": None,
"properties": {
"urban10_globalid": "uuid-xyz",
"full_name": "Производственные зоны",
},
}
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [feat])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
assert result == {"zones": 0}
assert db.executed == []
def test_sync_globalid_dedup_two_identical_features(monkeypatch: Any) -> None:
"""Две фичи с одинаковым globalid — оба UPSERT (ON CONFLICT дедуп на уровне БД)."""
feat = _make_feature(globalid="dup-uuid")
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [feat, feat])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
# Оба вызова UPSERT прошли (ON CONFLICT DO UPDATE разрулит на уровне БД).
assert result == {"zones": 2}
assert len(db.executed) == 2
def test_sync_savepoint_per_row_error_does_not_abort_batch(monkeypatch: Any) -> None:
"""SAVEPOINT per-row: ошибка в одной фиче не валит батч остальных."""
call_count = 0
class _FaultyDB(_FakeDB):
@contextmanager
def begin_nested(self) -> Any:
nonlocal call_count
call_count += 1
if call_count == 1:
raise Exception("upsert error row 1")
yield
features = [_make_feature(globalid=f"uuid-{i}") for i in range(3)]
db = _FaultyDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: features)
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
# Строка 1 упала (savepoint), строки 2 и 3 записались.
assert result == {"zones": 2}
assert db.committed is True
assert db.closed is True
def test_sync_empty_wfs_response(monkeypatch: Any) -> None:
"""WFS вернул 0 фич → zones=0, commit без ошибок."""
db = _FakeDB()
monkeypatch.setattr(genplan_zones_sync, "fetch_functional_zones", lambda: [])
monkeypatch.setattr(genplan_zones_sync, "SessionLocal", lambda: db)
result = genplan_zones_sync.sync_functional_zones()
assert result == {"zones": 0}
assert db.committed is True
assert db.closed is True