TXORDER-01: 7 тестов прошли, 8-й нашёл баг
Как domain state в одном тесте сделал видимым баг в порядке операций внутри транзакции — и что это говорит о том, что на самом деле проверяют “зелёные тесты”
7 тестов прошли.
8-й нашёл баг в production flow.
Не потому что был написан лучше. Потому что запустился с другим начальным состоянием системы.
Операция и транзакция
PATCH /reschedule — перенос appointment пациента на другой слот. Атомарная транзакция: освободить старый слот, занять новый, переместить запись. Плюс promoteFromWaitlist: если на освобождённом слоте есть очередь, первый из неё автоматически получает appointment.
Порядок операций в транзакции:
free_old_slot(slot1)
promoteFromWaitlist(slot1)
book_new_slot(slot2)
move_appointment(appointment → slot2)
Почему 7 тестов ничего не нашли
Тесты 1–7 проверяли стандартные сценарии: перенести pending, перенести confirmed, попытаться перенести на занятый слот. Ни в одном из них не было пациента в вейтлисте.promoteFromWaitlist в каждом тесте — no-op. Очередь пуста, функция вызывалась, ничего не делала, возвращала успех. Это важная деталь: функция не падала. Она просто не активировалась. Порядок операций вокруг неё не имел значения — потому что одна из операций ничего не делала.
7 зелёных тестов говорили: reschedule работает корректно. На самом деле они говорили: reschedule работает корректно когда вейтлист пуст.
Что нашёл 8-й тест
Пациент 2 встал в очередь на slot1. Пациент 1 запустил reschedule на slot2.
Ответ: 409 SLOT_IN_USE.
Слот был свободен. Пациент имел право переноса. Транзакция откатилась.
Механизм
free_old_slot(slot1) ← слот доступен
promoteFromWaitlist(slot1) ← пациент 2 получил pending на slot1
book_new_slot(slot2)
move_appointment → slot2 ← appointment пациента 1 ещё на slot1
После шага 2 на slot1 два active appointment одновременно: пациента 1 (ещё не переехал) и пациента 2 (только что из промоушна). UNIQUE constraint one_active_per_slot. Откат. 409.
Транзакция дисциплинированно выполняла логически неверную последовательность — и откатывалась на constraint.
Фикс
Appointment должен покинуть slot1 до того как promote вставляет нового пациента:
book_new_slot(slot2)
move_appointment → slot2
free_old_slot(slot1)
promoteFromWaitlist(slot1)
8-й тест прошёл
Что означают 7 зелёных тестов
Тест проверяет поведение системы при конкретном начальном состоянии. Если в наборе тестов нет нужного domain state — класс ошибок невидим, сколько бы тестов ни прошло.
В данном случае критическое условие — пациент в вейтлисте — отсутствовало во всех семи тестах. promoteFromWaitlist` был no-op в каждом из них. Баг в порядке операций существовал с момента написания — просто не было состояния которое его активировало.
Атомарность транзакции гарантирует: либо все операции выполнятся, либо ни одна. Она не гарантирует что операции написаны в правильном порядке. Это разные гарантии — и мы путали их семь тестов подряд.
Скрытое предположение “Я решилf что если транзакция атомарна — порядок операций внутри неё можно не тестировать. На самом деле транзакция защищает от частичных обновлений, но не от логически неверного порядка внутри.”
Код проекта: GitHub
Из серии “Тихие отказы в тест-автоматизации” Разборы таких кейсов — в Telegram-канале Тесты как система
