"""#3051 п.3 — параметризация import_rosreestr_dkp по региону (Москва, region_code=77). Чисто-юнит: ассертим SQL-текст, эмитируемый import_rosreestr_dkp (через inspect.getsource), тем же механизмом, что и test_rosreestr_dedup_key.py — без живого FDW / DB-фикстуры. Многострочные клозы матчатся regex с `\\s+`, а не подстрокой: SQL внутри text(...) переносится по строкам, точный пробельный рисунок хрупок. """ from __future__ import annotations import inspect import os import re from pathlib import Path from unittest.mock import MagicMock # Импорт app.services.scheduler тянет app.core.config.Settings → требует DATABASE_URL. os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") import pytest from app.services import scheduler _SQL_DIR = Path(__file__).resolve().parents[1] / "data" / "sql" _MIGRATION_289 = _SQL_DIR / "289_rosreestr_fdw_msk_columns_seed77.sql" _IMPORT_SRC = inspect.getsource(scheduler.import_rosreestr_dkp) def test_case_on_canonical_city_bind() -> None: """canonical_city — bind-параметр CASE-выражения (address/city/raw_payload), не Python if/else на конкретный код региона (#3051: одна ветка SQL решает и для 66 (None), и для 77 ('Москва'), и для любого будущего региона). """ assert _IMPORT_SRC.count("CAST(:canonical_city AS text) IS NOT NULL") >= 3 assert '"canonical_city": region.canonical_city' in _IMPORT_SRC def test_city_filter_bypassed_only_with_canonical_city() -> None: """WHERE-клоз: у региона С canonical_city city IS NOT NULL НЕ применяется — иначе для Москвы теряется ~10% строк с пустым city источника (округ/поселение не всегда заполнен). """ pattern = re.compile( r"\(\s*CAST\(:canonical_city AS text\) IS NOT NULL\s*" r"OR \(city IS NOT NULL AND trim\(city\) <> ''\)\s*\)" ) assert pattern.search(_IMPORT_SRC), "не найден WHERE-клоз city IS NOT NULL bypass" def test_raw_payload_jsonb_bind() -> None: """raw_payload (jsonb) — исходные city/okato/quarter_cad_number/district для регионов с canonical_city; bind через CAST(... AS jsonb), не литерал. """ assert "CAST(:raw_payload AS jsonb)" in _IMPORT_SRC assert "raw_payload = EXCLUDED.raw_payload" in _IMPORT_SRC assert "IS DISTINCT FROM EXCLUDED.raw_payload" in _IMPORT_SRC assert "json.dumps" in _IMPORT_SRC def test_doc_types_param_default_dkp() -> None: """doc_types — параметр (было: литерал 'ДКП'), дефолт ['ДКП'] сохраняет прежнее поведение (только вторичка). """ assert "ANY(CAST(:doc_types AS text[]))" in _IMPORT_SRC assert 'params.get("doc_types") or ["ДКП"]' in _IMPORT_SRC def test_unknown_region_raises() -> None: """region_code без записи в REGIONS → ValueError ДО любого db.execute — прогон не должен успеть тронуть БД с неизвестным региональным контекстом. """ db = MagicMock() with pytest.raises(ValueError, match="99"): scheduler.import_rosreestr_dkp(db, run_id=1, params={"region_code": 99}) db.execute.assert_not_called() def test_dkp_source_for_region() -> None: """66 → байт-в-байт прежнее имя _DKP_SOURCE; 77 → суффиксное имя, совпадающее с source seed-строки миграции 289 (изоляция чекпоинтов между регионами). """ assert scheduler._dkp_source_for_region(66) == scheduler._DKP_SOURCE assert scheduler._dkp_source_for_region(77) == "rosreestr_dkp_import_77" sql = _MIGRATION_289.read_text("utf-8") assert "'rosreestr_dkp_import_77'" in sql, "seed-source миграции 289 разъехался с кодом"