All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m2s
Тесты пишутся ПОСЛЕ живой проверки функционала (правило от 08.09): импорт по 77 — run 6460 (212 937 строк), регион-фильтр коридора — smoke до/после импорта. 14 тестов: SQL-текст через inspect (regex \s+ для многострочных клозов), чистые функции, мок db по образцу 2846, subprocess bash с урезанным PATH (падение от валидации, не от отсутствия docker).
93 lines
4.2 KiB
Python
93 lines
4.2 KiB
Python
"""#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 разъехался с кодом"
|