Улучшения в тестах переходов статусов заказов
- Исправлены комментарии и форматирование в signals.py - Улучшена читаемость кода в models.py - Обновлены шаблоны форм статусов - Доработаны тесты переходов статусов
This commit is contained in:
@@ -902,3 +902,238 @@ class OrderStatusTransitionCriticalTest(TestCase):
|
||||
# Проверяем, что is_returned False (есть Sale)
|
||||
order.refresh_from_db()
|
||||
self.assertFalse(order.is_returned, "[COMPLETED AGAIN] is_returned должен быть False при наличии Sale")
|
||||
|
||||
# ==================== ТЕСТ 9: Хаотичная смена статусов (стресс-тест) ====================
|
||||
|
||||
def test_09_random_status_changes_stress_test(self):
|
||||
"""
|
||||
ТЕСТ #9: Хаотичная смена статусов заказа в разные стороны
|
||||
|
||||
Сценарий (симулирует реальное поведение пользователя):
|
||||
1. draft → completed (продажа)
|
||||
2. completed → draft (откат)
|
||||
3. draft → cancelled (отмена)
|
||||
4. cancelled → in_assembly (возобновление)
|
||||
5. in_assembly → completed (продажа снова)
|
||||
6. completed → cancelled (отмена выполненного)
|
||||
7. cancelled → completed (автоматический промежуточный переход через draft)
|
||||
8. completed → draft → cancelled (откат и финальная отмена)
|
||||
|
||||
Проверяем на каждом шаге:
|
||||
- Консистентность резервов
|
||||
- Консистентность Stock (quantity_available, quantity_reserved, quantity_free)
|
||||
- Отсутствие дублирования Sale
|
||||
- Корректность восстановления партий
|
||||
- Корректность флага is_returned
|
||||
"""
|
||||
with schema_context('test_order_status'):
|
||||
# Создаём заказ в draft
|
||||
order = self._create_order(self.status_draft, quantity=Decimal('10.00'))
|
||||
|
||||
# ========== ШАГ 1: draft → completed ==========
|
||||
order.status = self.status_completed
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после первой продажи
|
||||
self._assert_stock_state(
|
||||
available=Decimal('90.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 1: draft→completed] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'converted_to_sale', "[STEP 1] ")
|
||||
self._assert_sale_exists(order, should_exist=True)
|
||||
self.assertFalse(order.is_returned, "[STEP 1] is_returned должен быть False после продажи")
|
||||
|
||||
# Проверяем партии
|
||||
self.stock_batch.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.stock_batch.quantity,
|
||||
Decimal('90.00'),
|
||||
"[STEP 1] Партия должна уменьшиться на 10"
|
||||
)
|
||||
|
||||
# ========== ШАГ 2: completed → draft ==========
|
||||
order.status = self.status_draft
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после отката
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('10.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 2: completed→draft] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'reserved', "[STEP 2] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
self.assertTrue(order.is_returned, "[STEP 2] is_returned должен быть True (был completed, но Sale удалены)")
|
||||
|
||||
# Партии должны восстановиться
|
||||
self.stock_batch.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.stock_batch.quantity,
|
||||
Decimal('100.00'),
|
||||
"[STEP 2] Партия должна восстановиться"
|
||||
)
|
||||
|
||||
# ========== ШАГ 3: draft → cancelled ==========
|
||||
order.status = self.status_cancelled
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после отмены
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('100.00'),
|
||||
msg_prefix="[STEP 3: draft→cancelled] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'released', "[STEP 3] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
self.assertTrue(order.is_returned, "[STEP 3] is_returned остается True")
|
||||
|
||||
# ========== ШАГ 4: cancelled → in_assembly ==========
|
||||
order.status = self.status_in_assembly
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после возобновления
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('10.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 4: cancelled→in_assembly] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'reserved', "[STEP 4] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
self.assertTrue(order.is_returned, "[STEP 4] is_returned остается True (Sale еще нет)")
|
||||
|
||||
# ========== ШАГ 5: in_assembly → completed ==========
|
||||
order.status = self.status_completed
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после второй продажи
|
||||
self._assert_stock_state(
|
||||
available=Decimal('90.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 5: in_assembly→completed] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'converted_to_sale', "[STEP 5] ")
|
||||
self._assert_sale_exists(order, should_exist=True)
|
||||
self.assertFalse(order.is_returned, "[STEP 5] is_returned должен стать False (Sale созданы)")
|
||||
|
||||
# Проверяем, что НЕТ дублей Sale
|
||||
sales_count = Sale.objects.filter(order=order).count()
|
||||
self.assertEqual(sales_count, 1, "[STEP 5] Должна быть ровно 1 Sale (нет дублей)")
|
||||
|
||||
self.stock_batch.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.stock_batch.quantity,
|
||||
Decimal('90.00'),
|
||||
"[STEP 5] Партия должна снова уменьшиться на 10"
|
||||
)
|
||||
|
||||
# ========== ШАГ 6: completed → cancelled ==========
|
||||
order.status = self.status_cancelled
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверки после отмены выполненного
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('100.00'),
|
||||
msg_prefix="[STEP 6: completed→cancelled] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'released', "[STEP 6] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
self.assertTrue(order.is_returned, "[STEP 6] is_returned должен быть True (был completed, Sale удалены)")
|
||||
|
||||
self.stock_batch.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.stock_batch.quantity,
|
||||
Decimal('100.00'),
|
||||
"[STEP 6] Партия должна восстановиться после отмены"
|
||||
)
|
||||
|
||||
# ========== ШАГ 7: cancelled → completed (автоматический промежуточный переход) ==========
|
||||
# Должен произойти: cancelled → draft → completed
|
||||
order.status = self.status_completed
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Проверяем, что прошли через draft (автоматический промежуточный переход)
|
||||
history = order.history.all()
|
||||
self.assertGreaterEqual(history.count(), 2, "[STEP 7] Должна быть история переходов")
|
||||
|
||||
# Проверки после автоматического перехода
|
||||
self._assert_stock_state(
|
||||
available=Decimal('90.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 7: cancelled→completed auto] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'converted_to_sale', "[STEP 7] ")
|
||||
self._assert_sale_exists(order, should_exist=True)
|
||||
self.assertFalse(order.is_returned, "[STEP 7] is_returned должен быть False")
|
||||
|
||||
# Проверяем, что НЕТ дублей Sale после автоматического перехода
|
||||
sales_count = Sale.objects.filter(order=order).count()
|
||||
self.assertEqual(sales_count, 1, "[STEP 7] Должна быть ровно 1 Sale (нет дублей после auto-transition)")
|
||||
|
||||
# ========== ШАГ 8: completed → draft → cancelled ==========
|
||||
order.status = self.status_draft
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# Промежуточная проверка в draft
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('10.00'),
|
||||
free=Decimal('90.00'),
|
||||
msg_prefix="[STEP 8a: completed→draft] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'reserved', "[STEP 8a] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
|
||||
# Финальная отмена
|
||||
order.status = self.status_cancelled
|
||||
order.save()
|
||||
order.refresh_from_db()
|
||||
|
||||
# ========== ФИНАЛЬНЫЕ ПРОВЕРКИ ==========
|
||||
self._assert_stock_state(
|
||||
available=Decimal('100.00'),
|
||||
reserved=Decimal('0.00'),
|
||||
free=Decimal('100.00'),
|
||||
msg_prefix="[FINAL: cancelled] "
|
||||
)
|
||||
self._assert_reservation_status(order, 'released', "[FINAL] ")
|
||||
self._assert_sale_exists(order, should_exist=False)
|
||||
self.assertTrue(order.is_returned, "[FINAL] is_returned должен быть True в финале")
|
||||
|
||||
# Проверяем, что все партии восстановлены до исходного состояния
|
||||
self.stock_batch.refresh_from_db()
|
||||
self.assertEqual(
|
||||
self.stock_batch.quantity,
|
||||
Decimal('100.00'),
|
||||
"[FINAL] Партия должна полностью восстановиться до начального значения"
|
||||
)
|
||||
|
||||
# Проверяем, что НЕТ активных Sale
|
||||
sales = Sale.objects.filter(order=order)
|
||||
self.assertEqual(sales.count(), 0, "[FINAL] Не должно быть активных Sale после финальной отмены")
|
||||
|
||||
# Проверяем общую консистентность данных
|
||||
# Все резервы должны быть в released
|
||||
reservations = Reservation.objects.filter(order_item__order=order)
|
||||
for res in reservations:
|
||||
self.assertEqual(
|
||||
res.status,
|
||||
'released',
|
||||
f"[FINAL] Все резервы должны быть в 'released', но нашли {res.status}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user