gendesign/backend/tests/test_eesk_loader_bind_params.py
bot-backend a9de9bedf6
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m1s
CI / backend-tests (pull_request) Successful in 17m30s
ЕЭСК-лоадер: бинд-параметр в SQL-комментарии ронял все 71 UPDATE с 18.08
Комментарий #2464-B, объясняющий, почему из UPDATE убрали :load_pct, сам
содержал «CAST(:load_pct AS text)» — а SQLAlchemy text() парсит бинды и
внутри SQL-комментариев. Параметр стал обязательным, params его не содержит,
КАЖДАЯ строка батча падала на компиляции, per-row SAVEPOINT-except глотал
это как «битую строку», задача оставалась зелёной. Резервы ПС 35-220 не
обновлялись две недели, и никакой сторож этого не видел.

Найдено армейским аудитом 01-02.09 (линза ptica-workers), подтверждено
скептиком воспроизведением на проде.

Правка — переписан комментарий БЕЗ упоминания снятого параметра в живом
синтаксисе бинда, с предупреждением, почему это запрещено.

Сторож на МЕХАНИЗМ: тест собирает text()-стейтмент из исходника и сверяет
его бинд-имена с ключами params. БД не нужна — дефект живёт на компиляции.
Фальсификация: возврат «:load_pct» в комментарий даёт красное по значению
(«стейтмент требует биндов ['load_pct']»).
2026-09-02 11:53:10 +05:00

68 lines
3.5 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.

"""Бинды SQL-стейтментов ЕЭСК-лоадера обязаны совпадать с передаваемыми params.
ПОЧЕМУ ЭТОТ ТЕСТ СУЩЕСТВУЕТ. 18.08-02.09.2026 все 71 UPDATE резервов ПС
падали, а задача оставалась зелёной: упоминание СНЯТОГО параметра load_pct
в SQL-КОММЕНТАРИИ (в синтаксисе «двоеточие+имя») SQLAlchemy text() распарсил
как обязательный бинд, params его не содержал, каждая строка падала на
compile, а per-row except глотал это как «битую строку». ЕЭСК-резервы не
обновлялись две недели, freshness этого не видел (задача done).
Тест ловит МЕХАНИЗМ: собирает все text()-стейтменты модуля и сверяет их
бинд-имена с ключами словаря params из того же контекста. Никакой БД не
нужно — дефект живёт на этапе компиляции стейтмента.
"""
from __future__ import annotations
import re
from pathlib import Path
from sqlalchemy import text
LOADER = (
Path(__file__).resolve().parents[1]
/ "app"
/ "services"
/ "site_finder"
/ "eesk_reserve_loader.py"
)
def _extract_update_sql() -> str:
"""Достаёт текст UPDATE power_supply_centers из исходника лоадера."""
src = LOADER.read_text(encoding="utf-8")
m = re.search(r'text\("""\s*(UPDATE power_supply_centers.*?)"""\)', src, re.S)
assert m, "UPDATE power_supply_centers не найден в лоадере"
return m.group(1)
def _extract_params_keys() -> set[str]:
"""Ключи словаря params, который передаётся в этот execute."""
src = LOADER.read_text(encoding="utf-8")
m = re.search(r"params = \{(.*?)\}", src, re.S)
assert m, "словарь params не найден"
return set(re.findall(r'"([a-z_]+)":', m.group(1)))
def test_update_binds_are_subset_of_params() -> None:
"""Каждый бинд стейтмента обязан приходить из params — иначе UPDATE падает
на КАЖДОЙ строке, и per-row SAVEPOINT-except прячет это под «битую строку»."""
stmt = text(_extract_update_sql())
binds = {p.key for p in stmt._bindparams.values()}
params = _extract_params_keys()
missing = binds - params
assert not missing, (
f"стейтмент требует биндов {sorted(missing)}, которых нет в params — "
"все строки батча упадут молча. Частая причина: упоминание "
"«двоеточие+имя» в SQL-комментарии (text() парсит бинды и там)."
)
def test_comment_does_not_reintroduce_phantom_bind() -> None:
"""Регресс 18.08: снятый параметр упомянули в комментарии в живом
синтаксисе бинда. Проверяем ЗНАЧЕНИЕМ: компиляция с ровно теми params,
что собирает лоадер, не требует ничего лишнего."""
stmt = text(_extract_update_sql())
have = _extract_params_keys()
for bp in stmt._bindparams.values():
assert bp.key in have, f"фантомный бинд из комментария: {bp.key!r}"