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
Комментарий #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']»).
68 lines
3.5 KiB
Python
68 lines
3.5 KiB
Python
"""Бинды 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}"
|