→ None."""
assert eh.extract_tpl_xlsx("пусто") is None
def test_extract_tpl_xlsx_bad_zip_returns_none() -> None:
"""data-file — валидный base64, но не ZIP → None (graceful)."""
b64 = base64.b64encode(b"not a zip at all").decode("ascii")
html = f'
'
assert eh.extract_tpl_xlsx(html) is None
# ── parse_heat_sheet: СТ №54 → 3.26, скип заголовков, правейшее число ─────────
def test_parse_heat_sheet_detects_system_and_rightmost_number() -> None:
"""«СТ №54 … 3.26» распознан; строки-заголовки без чисел скипнуты."""
systems, all_cells = eh.parse_heat_sheet(_heat_xlsx_bytes())
assert len(systems) == 1
assert systems[0]["system_name"] == "СТ №54"
assert systems[0]["reserve_gcal_h"] == 3.26
# all_cells содержит ВЕСЬ лист (3 строки) для raw.
assert len(all_cells) == 3
def test_parse_heat_sheet_rightmost_number_wins() -> None:
"""Если в строке несколько чисел — берётся правейшее."""
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "ТП"
ws.append(["зона Центральная", 100.0, 12.5]) # правейшее = 12.5 (резерв)
buf = io.BytesIO()
wb.save(buf)
systems, _ = eh.parse_heat_sheet(buf.getvalue())
assert len(systems) == 1
assert systems[0]["system_name"] == "зона Центральная"
assert systems[0]["reserve_gcal_h"] == 12.5
def test_parse_heat_sheet_zero_systems() -> None:
"""Лист без системных строк → systems пуст, но all_cells собран."""
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "ТП"
ws.append(["Совершенно посторонний заголовок", None])
ws.append(["ещё строка", 5.0]) # число есть, но нет системного маркера → скип
buf = io.BytesIO()
wb.save(buf)
systems, all_cells = eh.parse_heat_sheet(buf.getvalue())
assert systems == []
assert len(all_cells) == 2
# ── Fake DB (SAVEPOINT + execute), как в test_eesk_reserve_loader.py ──────────
class _FakeResult:
def __init__(self, scalar_value: Any = True) -> None:
self._scalar = scalar_value
def scalar(self) -> Any:
return self._scalar
class _FakeSession:
"""Мини-заглушка Session: собирает execute-параметры, поддерживает begin_nested."""
def __init__(self, scalar_value: Any = True) -> None:
self.calls: list[tuple[str, Any]] = []
self._scalar = scalar_value
self.committed = False
@contextmanager
def begin_nested(self): # type: ignore[no-untyped-def]
yield
def execute(self, stmt: Any, params: Any = None) -> _FakeResult:
self.calls.append((str(stmt), params))
return _FakeResult(self._scalar)
def commit(self) -> None:
self.committed = True
def rollback(self) -> None:
pass
def test_upsert_heat_rows_counters_and_params() -> None:
"""parsed/inserted счётчики; params корректны; raw содержит cells; CAST jsonb."""
db = _FakeSession(scalar_value=True) # xmax=0 → insert
systems = [
{"system_name": "СТ №54", "reserve_gcal_h": 3.26},
{"system_name": "зона Юг", "reserve_gcal_h": -1.2}, # дефицит: знак сохраняется
]
all_cells = [["СТ №54", "источник", 3.26], ["зона Юг", None, -1.2]]
counts = eh._upsert_heat_rows(db, "ЕТК", "2026-Q2", "guid-xyz", systems, all_cells)
assert counts == {"parsed": 2, "inserted": 2, "updated": 0, "skipped": 0}
assert len(db.calls) == 2
sql, first = db.calls[0]
assert first["org"] == "ЕТК"
assert first["system_name"] == "СТ №54"
assert first["reserve"] == 3.26
assert first["period"] == "2026-Q2"
assert first["source_guid"] == "guid-xyz"
# raw — JSON-строка с ключом cells (весь лист), CAST в jsonb в SQL.
assert '"cells"' in first["raw"]
assert "CAST(:raw AS jsonb)" in sql
assert "ON CONFLICT (org, sphere, system_name, period)" in sql
# negative reserve (дефицит) прокинут знаком.
assert db.calls[1][1]["reserve"] == -1.2
def test_upsert_heat_rows_update_counter() -> None:
"""scalar()=False (xmax≠0) → строка учтена как update, не insert."""
db = _FakeSession(scalar_value=False)
systems = [{"system_name": "СТ №1", "reserve_gcal_h": 1.0}]
counts = eh._upsert_heat_rows(
db, "Екатеринбургэнерго", "2026-Q2", "g1", systems, [["СТ №1", 1.0]]
)
assert counts == {"parsed": 1, "inserted": 0, "updated": 1, "skipped": 0}
def test_upsert_heat_rows_skip_on_error() -> None:
"""execute бросает → строка skipped, счётчик не растёт в inserted/updated."""
class _RaisingSession(_FakeSession):
def execute(self, stmt: Any, params: Any = None) -> _FakeResult:
raise RuntimeError("db boom")
db = _RaisingSession()
systems = [{"system_name": "СТ №9", "reserve_gcal_h": 2.0}]
counts = eh._upsert_heat_rows(db, "ЕТК", "2026-Q2", "g", systems, [["СТ №9", 2.0]])
assert counts == {"parsed": 1, "inserted": 0, "updated": 0, "skipped": 1}