diff --git a/myproject/tenants/tests/__init__.py b/myproject/tenants/tests/__init__.py new file mode 100644 index 0000000..612176f --- /dev/null +++ b/myproject/tenants/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +""" +Тесты для приложения tenants +""" diff --git a/myproject/tenants/tests/test_tenant_creation.py b/myproject/tenants/tests/test_tenant_creation.py new file mode 100644 index 0000000..a3e9f2c --- /dev/null +++ b/myproject/tenants/tests/test_tenant_creation.py @@ -0,0 +1,365 @@ +# -*- 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) + + completed_status = OrderStatus.objects.filter(code='completed').first() + self.assertIsNotNone(completed_status, "Статус 'completed' не создан") + self.assertTrue(completed_status.is_system) + + 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)