Проблема: - При смене статуса заказа на 'Выполнен' товар списывался со склада - Резервы обновлялись на статус 'converted_to_sale' - НО Stock.quantity_reserved не обновлялся автоматически - В результате резервы продолжали 'держать' товар, хотя он уже продан Решение: 1. Изменен сигнал create_sale_on_order_completion: - Используется .save(update_fields=[...]) вместо .update() - Это вызывает сигнал update_stock_on_reservation_change - Убран костыль с ручным вызовом refresh_from_batches() 2. Оптимизирован сигнал update_stock_on_reservation_change: - Stock обновляется ТОЛЬКО при изменении status или quantity - При изменении других полей (даты и т.д.) Stock НЕ пересчитывается - Предотвращены лишние пересчёты и улучшена производительность 3. Добавлены диагностические инструменты: - check_stock_103.py - для диагностики проблем с Stock - fix_stock_after_sale.py - команда для исправления старых заказов - diagnose_reservation_issue.py - универсальная диагностика Результат: - Элегантное решение без дублирования логики - Stock автоматически обновляется при изменении резервов - Работает везде, не только в заказах - Оптимизировано для производительности
407 lines
19 KiB
Python
407 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Интеграционные тесты создания тенантов.
|
||
|
||
Проверяем полный процесс онбординга нового тенанта:
|
||
1. Создание схемы БД
|
||
2. Создание домена
|
||
3. Создание суперпользователя
|
||
4. Создание способов оплаты
|
||
5. Создание системных статусов заказов
|
||
6. Создание системного клиента
|
||
"""
|
||
from django.test import TestCase, TransactionTestCase
|
||
from django.db import connection
|
||
from django.contrib.auth import get_user_model
|
||
from django.conf import settings
|
||
from django_tenants.utils import schema_context
|
||
|
||
from tenants.models import Client, Domain, TenantRegistration
|
||
from orders.models import PaymentMethod, OrderStatus
|
||
from customers.models import Customer
|
||
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
class TenantCreationIntegrationTest(TransactionTestCase):
|
||
"""
|
||
Интеграционный тест полного процесса создания тенанта.
|
||
|
||
TransactionTestCase используется потому что:
|
||
1. Нужно создавать реальные схемы БД
|
||
2. Нужно переключаться между схемами
|
||
3. Нужно очищать созданные схемы после теста
|
||
"""
|
||
|
||
def setUp(self):
|
||
"""Подготовка к каждому тесту"""
|
||
# Создаём администратора для каждого теста
|
||
self.admin_user = User.objects.create_superuser(
|
||
email='test_admin_per_test@example.com',
|
||
password='testpass123',
|
||
name='Test Admin'
|
||
)
|
||
|
||
def tearDown(self):
|
||
"""Очистка после каждого теста"""
|
||
# Удаляем все тестовые тенанты и их схемы
|
||
test_tenants = Client.objects.filter(schema_name__startswith='test_shop_')
|
||
|
||
for tenant in test_tenants:
|
||
schema_name = tenant.schema_name
|
||
|
||
# Удаляем схему вручную
|
||
with connection.cursor() as cursor:
|
||
cursor.execute(f'DROP SCHEMA IF EXISTS {schema_name} CASCADE')
|
||
|
||
# Удаляем запись тенанта
|
||
tenant.delete()
|
||
|
||
# Удаляем тестовые заявки
|
||
TenantRegistration.objects.filter(schema_name__startswith='test_shop_').delete()
|
||
|
||
def test_new_tenant_gets_all_5_payment_methods(self):
|
||
"""
|
||
КРИТИЧЕСКИЙ ТЕСТ: Новый тенант автоматически получает все 5 способов оплаты.
|
||
|
||
Это главный тест для проверки исправления бага с отсутствием account_balance.
|
||
"""
|
||
# 1. Создаём заявку на регистрацию
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин',
|
||
schema_name='test_shop_payment',
|
||
owner_email='owner@test.com',
|
||
owner_name='Тест Владелец',
|
||
phone='+375291111111',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
# 2. Активируем заявку (как в админке)
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# 3. Проверяем что тенант создан
|
||
self.assertIsNotNone(tenant)
|
||
self.assertEqual(tenant.schema_name, 'test_shop_payment')
|
||
|
||
# 4. Переключаемся на схему нового тенанта
|
||
with schema_context('test_shop_payment'):
|
||
# 5. ГЛАВНАЯ ПРОВЕРКА: Все 5 способов оплаты созданы
|
||
payment_methods = PaymentMethod.objects.all().order_by('order')
|
||
|
||
self.assertEqual(
|
||
payment_methods.count(),
|
||
5,
|
||
f"Ожидалось 5 способов оплаты, получено {payment_methods.count()}"
|
||
)
|
||
|
||
# 6. Проверяем каждый способ оплаты по отдельности
|
||
expected_methods = [
|
||
('account_balance', 'С баланса счёта', 0),
|
||
('cash', 'Наличными', 1),
|
||
('card', 'Картой', 2),
|
||
('online', 'Онлайн', 3),
|
||
('legal_entity', 'Безнал от ЮРЛИЦ', 4),
|
||
]
|
||
|
||
for code, name, order in expected_methods:
|
||
method = PaymentMethod.objects.filter(code=code).first()
|
||
|
||
self.assertIsNotNone(
|
||
method,
|
||
f"Способ оплаты '{code}' не создан!"
|
||
)
|
||
|
||
self.assertEqual(method.name, name)
|
||
self.assertEqual(method.order, order)
|
||
self.assertTrue(method.is_system)
|
||
self.assertTrue(method.is_active)
|
||
|
||
# 7. Проверяем что account_balance на первом месте
|
||
first_method = payment_methods.first()
|
||
self.assertEqual(
|
||
first_method.code,
|
||
'account_balance',
|
||
f"Первый способ оплаты должен быть 'account_balance', но получен '{first_method.code}'"
|
||
)
|
||
|
||
def test_new_tenant_gets_order_statuses(self):
|
||
"""
|
||
Тест: Новый тенант получает системные статусы заказов.
|
||
|
||
КРИТИЧЕСКИ ВАЖНО: Должен быть минимум ОДИН позитивный и ОДИН негативный финальный статус.
|
||
"""
|
||
# Создаём и активируем тенант
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин статусы',
|
||
schema_name='test_shop_statuses',
|
||
owner_email='owner2@test.com',
|
||
owner_name='Тест Владелец 2',
|
||
phone='+375291111112',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Проверяем статусы заказов
|
||
with schema_context('test_shop_statuses'):
|
||
statuses = OrderStatus.objects.all()
|
||
|
||
# Должно быть минимум 3 основных статуса
|
||
self.assertGreaterEqual(
|
||
statuses.count(),
|
||
3,
|
||
"Должно быть создано минимум 3 системных статуса"
|
||
)
|
||
|
||
# Проверяем наличие ключевых статусов
|
||
draft_status = OrderStatus.objects.filter(code='draft').first()
|
||
self.assertIsNotNone(draft_status, "Статус 'draft' не создан")
|
||
self.assertTrue(draft_status.is_system)
|
||
|
||
# КРИТИЧЕСКАЯ ПРОВЕРКА #1: Позитивный финальный статус
|
||
positive_statuses = OrderStatus.objects.filter(is_positive_end=True)
|
||
self.assertGreaterEqual(
|
||
positive_statuses.count(),
|
||
1,
|
||
"Должен быть минимум ОДИН позитивный финальный статус (is_positive_end=True)"
|
||
)
|
||
|
||
# Проверяем что есть 'completed' (основной позитивный статус)
|
||
completed_status = OrderStatus.objects.filter(code='completed').first()
|
||
self.assertIsNotNone(
|
||
completed_status,
|
||
"Статус 'completed' не создан (основной позитивный статус)"
|
||
)
|
||
self.assertTrue(
|
||
completed_status.is_system,
|
||
"'completed' должен быть системным"
|
||
)
|
||
self.assertTrue(
|
||
completed_status.is_positive_end,
|
||
"'completed' должен быть позитивным финальным статусом"
|
||
)
|
||
|
||
# КРИТИЧЕСКАЯ ПРОВЕРКА #2: Негативный финальный статус
|
||
negative_statuses = OrderStatus.objects.filter(is_negative_end=True)
|
||
self.assertGreaterEqual(
|
||
negative_statuses.count(),
|
||
1,
|
||
"Должен быть минимум ОДИН негативный финальный статус (is_negative_end=True)"
|
||
)
|
||
|
||
# Проверяем что есть 'cancelled' или другой негативный статус
|
||
# Проверяем первый найденный негативный статус
|
||
first_negative_status = negative_statuses.first()
|
||
self.assertTrue(
|
||
first_negative_status.is_system,
|
||
f"Негативный статус '{first_negative_status.code}' должен быть системным"
|
||
)
|
||
self.assertTrue(
|
||
first_negative_status.is_negative_end,
|
||
f"Статус '{first_negative_status.code}' должен быть негативным финальным"
|
||
)
|
||
|
||
def test_new_tenant_gets_system_customer(self):
|
||
"""
|
||
Тест: Новый тенант получает системного клиента для анонимных продаж.
|
||
"""
|
||
# Создаём и активируем тенант
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин клиенты',
|
||
schema_name='test_shop_customers',
|
||
owner_email='owner3@test.com',
|
||
owner_name='Тест Владелец 3',
|
||
phone='+375291111113',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Проверяем системного клиента
|
||
with schema_context('test_shop_customers'):
|
||
system_customer = Customer.objects.filter(is_system_customer=True).first()
|
||
|
||
self.assertIsNotNone(
|
||
system_customer,
|
||
"Системный клиент не создан"
|
||
)
|
||
|
||
self.assertTrue(system_customer.is_system_customer)
|
||
self.assertEqual(system_customer.name, 'Анонимный клиент')
|
||
|
||
def test_new_tenant_gets_superuser(self):
|
||
"""
|
||
Тест: Новый тенант получает суперпользователя для доступа к админке.
|
||
"""
|
||
# Создаём и активируем тенант
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин админ',
|
||
schema_name='test_shop_admin',
|
||
owner_email='owner4@test.com',
|
||
owner_name='Тест Владелец 4',
|
||
phone='+375291111114',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Проверяем суперпользователя
|
||
with schema_context('test_shop_admin'):
|
||
superuser = User.objects.filter(
|
||
email=settings.TENANT_ADMIN_EMAIL
|
||
).first()
|
||
|
||
self.assertIsNotNone(
|
||
superuser,
|
||
f"Суперпользователь с email {settings.TENANT_ADMIN_EMAIL} не создан"
|
||
)
|
||
|
||
self.assertTrue(superuser.is_superuser)
|
||
self.assertTrue(superuser.is_staff)
|
||
|
||
def test_new_tenant_gets_domain(self):
|
||
"""
|
||
Тест: Новый тенант получает домен для доступа.
|
||
"""
|
||
# Создаём и активируем тенант
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин домен',
|
||
schema_name='test_shop_domain',
|
||
owner_email='owner5@test.com',
|
||
owner_name='Тест Владелец 5',
|
||
phone='+375291111115',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Проверяем домен
|
||
domain = Domain.objects.filter(tenant=tenant).first()
|
||
|
||
self.assertIsNotNone(domain, "Домен не создан")
|
||
self.assertEqual(
|
||
domain.domain,
|
||
'test_shop_domain.localhost',
|
||
f"Ожидался домен 'test_shop_domain.localhost', получен '{domain.domain}'"
|
||
)
|
||
self.assertTrue(domain.is_primary)
|
||
|
||
def test_registration_status_changes_to_approved(self):
|
||
"""
|
||
Тест: После активации статус заявки меняется на APPROVED.
|
||
"""
|
||
# Создаём заявку
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Тестовый магазин статус',
|
||
schema_name='test_shop_status',
|
||
owner_email='owner6@test.com',
|
||
owner_name='Тест Владелец 6',
|
||
phone='+375291111116',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
# Проверяем начальный статус
|
||
self.assertEqual(registration.status, TenantRegistration.STATUS_PENDING)
|
||
|
||
# Активируем
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Обновляем объект из БД
|
||
registration.refresh_from_db()
|
||
|
||
# Проверяем изменённый статус
|
||
self.assertEqual(registration.status, TenantRegistration.STATUS_APPROVED)
|
||
self.assertIsNotNone(registration.processed_at)
|
||
self.assertEqual(registration.processed_by, self.admin_user)
|
||
self.assertEqual(registration.tenant, tenant)
|
||
|
||
def test_complete_tenant_onboarding(self):
|
||
"""
|
||
КОМПЛЕКСНЫЙ ТЕСТ: Проверяем весь процесс онбординга тенанта.
|
||
|
||
Это E2E тест, который проверяет что при создании нового тенанта
|
||
создаются ВСЕ необходимые сущности.
|
||
"""
|
||
# Создаём и активируем тенант
|
||
registration = TenantRegistration.objects.create(
|
||
shop_name='Полный тестовый магазин',
|
||
schema_name='test_shop_complete',
|
||
owner_email='owner_complete@test.com',
|
||
owner_name='Тест Владелец Полный',
|
||
phone='+375291111117',
|
||
status=TenantRegistration.STATUS_PENDING
|
||
)
|
||
|
||
from tenants.admin import TenantRegistrationAdmin
|
||
from django.contrib.admin.sites import AdminSite
|
||
|
||
admin = TenantRegistrationAdmin(TenantRegistration, AdminSite())
|
||
tenant = admin._approve_registration(registration, self.admin_user)
|
||
|
||
# Проверяем тенант
|
||
self.assertIsNotNone(tenant)
|
||
self.assertTrue(tenant.is_active)
|
||
|
||
# Проверяем домен
|
||
domain = Domain.objects.filter(tenant=tenant).first()
|
||
self.assertIsNotNone(domain)
|
||
|
||
# Переключаемся на схему тенанта для проверки
|
||
with schema_context('test_shop_complete'):
|
||
# 1. Способы оплаты
|
||
payment_methods_count = PaymentMethod.objects.count()
|
||
self.assertEqual(payment_methods_count, 5, "Должно быть 5 способов оплаты")
|
||
|
||
# 2. Статусы заказов
|
||
order_statuses_count = OrderStatus.objects.count()
|
||
self.assertGreaterEqual(order_statuses_count, 3, "Должно быть минимум 3 статуса")
|
||
|
||
# 3. Системный клиент
|
||
system_customer = Customer.objects.filter(is_system_customer=True).first()
|
||
self.assertIsNotNone(system_customer, "Должен быть системный клиент")
|
||
|
||
# 4. Суперпользователь
|
||
superuser = User.objects.filter(email=settings.TENANT_ADMIN_EMAIL).first()
|
||
self.assertIsNotNone(superuser, "Должен быть суперпользователь")
|
||
|
||
# Проверяем заявку
|
||
registration.refresh_from_db()
|
||
self.assertEqual(registration.status, TenantRegistration.STATUS_APPROVED)
|
||
|
||
print("\n" + "=" * 70)
|
||
print("✓ ПОЛНЫЙ ОНБОРДИНГ ТЕНАНТА ПРОШЁЛ УСПЕШНО")
|
||
print("=" * 70)
|
||
print(f"Тенант: {tenant.name}")
|
||
print(f"Схема: {tenant.schema_name}")
|
||
print(f"Домен: {domain.domain}")
|
||
print(f"Способов оплаты: {payment_methods_count}")
|
||
print(f"Статусов заказов: {order_statuses_count}")
|
||
print(f"Системный клиент: ✓")
|
||
print(f"Суперпользователь: ✓")
|
||
print("=" * 70)
|