test(ptica): снятие SAVEPOINT теперь роняет тест во всех местах кластера A (#2464) #2938

Merged
bot-backend merged 1 commit from test/2464a-savepoint-regressions into main 2026-08-19 16:07:00 +00:00
Collaborator

Зачем

Мутационный прогон по кластеру A эпика: у трёх закрытых пунктов снятие with db.begin_nested(): не роняет ни одного теста.

saturation.py        снят SAVEPOINT -> 465 passed, rc=0
zone_regulation.py   снят SAVEPOINT -> 195 passed, rc=0
pat_lookup.py        снят SAVEPOINT ->  21 passed, rc=0

Код там правильный — я снимал защиту временно и возвращал. Беззащитны регрессии: любой будущий рефакторинг снимет SAVEPOINT, и CI промолчит.

Причина общая: проверки построены на MagicMock, у которого нет aborted-состояния. Второй execute в тесте проходит независимо от того, обёрнут первый в SAVEPOINT или нет — поймать можно только проглоченное исключение.

Двойник вместо мока

tests/support/pg_session.py воспроизводит семантику Postgres:

  • упавший execute переводит транзакцию в aborted;
  • следующий execute падает с тем же текстом, что отдаёт Postgres;
  • выход из begin_nested() с исключением = ROLLBACK TO SAVEPOINT, снимает aborted.

Проверяется свойство — «сессия вызывающего осталась рабочей», — а не наличие вызова begin_nested. Второе зафиксировало бы реализацию; первое переживёт любой корректный способ починки.

Плюс два контроля на сам двойник: без SAVEPOINT он обязан отравляться, с SAVEPOINT — восстанавливаться. Без них проверки были бы зелёными по построению, то есть ровно тем, что этот PR и лечит.

Заодно: пробел в connection_capacity_lookup

У него есть свой тест, и в прошлом комментарии к эпику я ошибочно пометил файл беззащитным — строгий прогон это опроверг. Но пробел там всё же есть: тест проверяет assert db.begin_nested.call_count >= 1, то есть «хоть один savepoint был», а не «этот запрос обёрнут». Снятие одного из пяти — обычный случай при рефакторинге одной функции — проходило незамеченным.

Новые проверки адресные, по одной на функцию.

Прицел пришлось поправить. Первая версия теста для _query_gas_city_grs роняла ПЕРВЫЙ execute — а он принадлежит _query_gas_outlet_counts, которую эта функция зовёт внутри себя. То есть тест сторожил соседа, и снятие savepoint'а самой city_grs он не ловил. Исправлено на fail_on=(2,).

Доказательство, что тесты имеют зубы

Мутация, не рассуждение:

модуль снят SAVEPOINT результат
saturation.py 1 (все) 1 failed
zone_regulation.py 1 (все) 1 failed
pat_lookup.py 1 (все) 1 failed
connection_capacity_lookup.py #0#4 по одному каждый раз 1 failed — 5 из 5 (до правки прицела было 4 из 5)

Каждый раз падает ровно своя проверка — значит гранулярность верная, а не «всё красное на всякий случай».

Все исходники после прогонов восстановлены, рабочее дерево чистое.

pytest tests/services: 3070 passed, 14 skipped, rc=0 (перепрогнано после правок pre-commit, а не до).

Refs #2464

## Зачем Мутационный прогон по кластеру A эпика: у трёх закрытых пунктов снятие `with db.begin_nested():` **не роняет ни одного теста**. ``` saturation.py снят SAVEPOINT -> 465 passed, rc=0 zone_regulation.py снят SAVEPOINT -> 195 passed, rc=0 pat_lookup.py снят SAVEPOINT -> 21 passed, rc=0 ``` Код там правильный — я снимал защиту временно и возвращал. Беззащитны **регрессии**: любой будущий рефакторинг снимет SAVEPOINT, и CI промолчит. Причина общая: проверки построены на `MagicMock`, у которого нет aborted-состояния. Второй `execute` в тесте проходит независимо от того, обёрнут первый в SAVEPOINT или нет — поймать можно только проглоченное исключение. ## Двойник вместо мока `tests/support/pg_session.py` воспроизводит семантику Postgres: - упавший `execute` переводит транзакцию в `aborted`; - следующий `execute` падает с тем же текстом, что отдаёт Postgres; - выход из `begin_nested()` с исключением = `ROLLBACK TO SAVEPOINT`, снимает `aborted`. Проверяется **свойство** — «сессия вызывающего осталась рабочей», — а не наличие вызова `begin_nested`. Второе зафиксировало бы реализацию; первое переживёт любой корректный способ починки. Плюс два контроля **на сам двойник**: без SAVEPOINT он обязан отравляться, с SAVEPOINT — восстанавливаться. Без них проверки были бы зелёными по построению, то есть ровно тем, что этот PR и лечит. ## Заодно: пробел в connection_capacity_lookup У него **есть** свой тест, и в прошлом комментарии к эпику я ошибочно пометил файл беззащитным — строгий прогон это опроверг. Но пробел там всё же есть: тест проверяет `assert db.begin_nested.call_count >= 1`, то есть «хоть один savepoint был», а не «этот запрос обёрнут». Снятие **одного** из пяти — обычный случай при рефакторинге одной функции — проходило незамеченным. Новые проверки адресные, по одной на функцию. **Прицел пришлось поправить.** Первая версия теста для `_query_gas_city_grs` роняла ПЕРВЫЙ `execute` — а он принадлежит `_query_gas_outlet_counts`, которую эта функция зовёт внутри себя. То есть тест сторожил соседа, и снятие savepoint'а самой city_grs он не ловил. Исправлено на `fail_on=(2,)`. ## Доказательство, что тесты имеют зубы Мутация, не рассуждение: | модуль | снят SAVEPOINT | результат | |---|---|---| | `saturation.py` | 1 (все) | **1 failed** | | `zone_regulation.py` | 1 (все) | **1 failed** | | `pat_lookup.py` | 1 (все) | **1 failed** | | `connection_capacity_lookup.py` | #0 … #4 **по одному** | **каждый раз 1 failed** — 5 из 5 (до правки прицела было 4 из 5) | Каждый раз падает ровно своя проверка — значит гранулярность верная, а не «всё красное на всякий случай». Все исходники после прогонов восстановлены, рабочее дерево чистое. `pytest tests/services`: **3070 passed, 14 skipped, rc=0** (перепрогнано после правок pre-commit, а не до). Refs #2464
bot-backend added 1 commit 2026-08-19 15:42:15 +00:00
test(ptica): снятие SAVEPOINT теперь роняет тест во всех местах кластера A (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
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 / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m17s
CI / backend-tests (pull_request) Successful in 17m21s
32ad2419ec
Мутационный прогон 19.08 показал, что у трёх закрытых пунктов кластера снятие
`with db.begin_nested():` не роняет НИ ОДНОГО теста:

    saturation.py        снят SAVEPOINT -> 465 passed
    zone_regulation.py   снят SAVEPOINT -> 195 passed
    pat_lookup.py        снят SAVEPOINT ->  21 passed

Код там правильный — беззащитны регрессии. Причина общая: существующие проверки
построены на MagicMock, у которого нет aborted-состояния, поэтому второй execute
проходит при любом коде. Такая проверка ловит только проглоченное исключение.

Добавлен двойник tests/support/pg_session.py, воспроизводящий семантику Postgres:
упавший запрос переводит транзакцию в aborted; следующий execute падает с тем же
текстом, что отдаёт Postgres; выход из begin_nested с исключением = ROLLBACK TO
SAVEPOINT и снимает aborted.

Проверяется СВОЙСТВО «сессия вызывающего осталась рабочей», а не наличие вызова
begin_nested: второе зафиксировало бы реализацию, первое переживёт любой
корректный способ починки. Плюс два контроля на сам двойник — без них проверки
были бы зелёными по построению.

Заодно закрыт пробел в connection_capacity_lookup. Его собственный тест краснеет
только при снятии ВСЕХ пяти SAVEPOINT, потому что проверяет
`begin_nested.call_count >= 1` — «хоть один был», а не «этот запрос обёрнут».
Снятие одного из пяти (обычный случай при рефакторинге одной функции) проходило
незамеченным. Новые проверки адресные: по одной на функцию.

Прицел важен: первая версия теста для _query_gas_city_grs роняла ПЕРВЫЙ execute,
а он принадлежит _query_gas_outlet_counts, которую эта функция зовёт внутри себя —
то есть тест сторожил соседа. Исправлено на fail_on=(2,).

Проверено мутацией: снятие SAVEPOINT в любом из трёх модулей роняет ровно свою
проверку; в connection_capacity_lookup поштучное снятие каждого из пяти — тоже
(5 из 5, до правки 4 из 5).

pytest tests/services: 3070 passed, 14 skipped, rc=0
bot-backend merged commit f3626540fc into main 2026-08-19 16:07:00 +00:00
bot-backend deleted branch test/2464a-savepoint-regressions 2026-08-19 16:07:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2938
No description provided.