"""Три места, где код делал не то, что говорил (#2464). 1. `load_water_reserves_from_docx` (vodokanal): собирал `result` с ключом `period`, печатал полный словарь в лог, а возвращал `{k: v for … if isinstance(v, int)}` — период это строка или None, поэтому он выбрасывался ВСЕГДА. По логам казалось, что период отдаётся; вызывающий не получал его ни разу. 2. `get_sqlite_info` (objective_etl): TOCTOU — `p.exists()`, затем незащищённый `p.stat()`. try/except покрывал только `sqlite3.connect` ниже, поэтому OSError из stat улетал наружу и превращал диагностическую функцию в источник отказа. 3. `place_program` (placement): предупреждение «участок мал» печатало КАТАЛОЖНЫЕ `house.footprint_*`, хотя ставили по `fp_w`/`fp_d`. Если элемент программы переопределил габарит, сообщение называло размер, которым никто не пытался ставить, — диагностика уводила от причины. """ from __future__ import annotations import inspect from pathlib import Path from unittest.mock import MagicMock, patch import pytest # ── 1. period больше не выбрасывается ──────────────────────────────────────── def test_water_result_keeps_period() -> None: """Головной: период обязан доехать до вызывающего, раз он попал в лог. На origin/main фильтр `isinstance(v, int)` выбрасывает его всегда. """ from app.services.site_finder import vodokanal_reserve_loader as mod with ( patch.object(mod, "parse_docx_table_rows", lambda _x: [["шапка"]]), patch.object(mod, "extract_water_rows", lambda _m: [{"name": "ЦСВ-1"}]), patch.object(mod, "_dedupe_names", lambda r: r), patch.object(mod, "infer_period", lambda _u: "III кв. 2025"), patch.object(mod, "_upsert_water_rows", lambda *a, **k: {"inserted": 1, "updated": 0}), patch.object(mod.zipfile, "ZipFile", MagicMock()), ): res = mod.load_water_reserves_from_docx(MagicMock(), "supply", b"", "http://x") assert res.get("period") == "III кв. 2025", ( f"период выброшен из ответа: {res} — по логам он есть, у вызывающего нет" ) assert res.get("records") == 1 and res.get("inserted") == 1, res def test_water_return_annotation_allows_non_int() -> None: """Контроль: фильтр стоял ради аннотации `dict[str, int]` — она тоже исправлена. Иначе следующий проход mypy вернул бы фильтр обратно. """ from app.services.site_finder.vodokanal_reserve_loader import load_water_reserves_from_docx ann = inspect.signature(load_water_reserves_from_docx).return_annotation assert "int]" not in str(ann), f"аннотация всё ещё требует только int: {ann}" # ── 2. TOCTOU в get_sqlite_info ────────────────────────────────────────────── def test_sqlite_info_survives_file_vanishing_between_exists_and_stat() -> None: """Головной: файл исчез между exists() и stat() — функция не падает. На origin/main OSError улетает наружу: try/except покрывает только connect. """ from app.services import objective_etl as mod настоящий_stat = Path.stat def _stat(self, *a, **k): if str(self).endswith("исчезающий.sqlite"): raise FileNotFoundError(2, "No such file or directory") return настоящий_stat(self, *a, **k) with ( patch.object(Path, "exists", lambda self: True), patch.object(Path, "stat", _stat), ): info = mod.get_sqlite_info("/tmp/исчезающий.sqlite") assert info["exists"] is True assert "stat_error" in info, f"ошибка stat не отражена в ответе: {info}" assert "size_bytes" not in info, "размер выдуман при отсутствующем файле" def test_sqlite_info_missing_file_unchanged() -> None: """Контроль: отсутствующий файл по-прежнему даёт exists=False без ошибок.""" from app.services.objective_etl import get_sqlite_info info = get_sqlite_info("/tmp/такого-файла-нет-2464.sqlite") assert info["exists"] is False assert "stat_error" not in info, info # ── 3. предупреждение placement называет фактические габариты ──────────────── def test_placement_warning_uses_actual_footprint_not_catalog() -> None: """Головной: в сообщении должны стоять fp_w/fp_d, а не house.footprint_*. На origin/main при переопределённом габарите печатается каталожный размер. """ from app.services.generative import placement as mod src = inspect.getsource(mod.place_program) хвост = src[src.index("участок мал") :] assert "fp_w," in хвост and "fp_d," in хвост, ( f"в предупреждении не фактические габариты:\n{хвост[:320]}" ) assert "house.footprint_w_m," not in хвост and "house.footprint_d_m," not in хвост, ( f"в предупреждении остался каталожный размер:\n{хвост[:320]}" ) @pytest.mark.parametrize("имя", ["fp_w", "fp_d"]) def test_placement_uses_those_names_for_placement(имя: str) -> None: """Контроль: fp_w/fp_d — это действительно те размеры, которыми ставят. Без него тест выше проверял бы совпадение имён, а не смысл. """ from app.services.generative import placement as mod src = inspect.getsource(mod.place_program) assert "_centered_footprint(cell.cx, cell.cy, fp_w, fp_d)" in src, src[:200] assert имя in src