# -*- coding: utf-8 -*- """ Тесты для способов оплаты (PaymentMethod). Проверяем: 1. Создание способов оплаты через команду create_payment_methods 2. Уникальность способов оплаты в рамках тенанта 3. Изоляцию данных между тенантами 4. Корректность работы с транзакциями """ from django.test import TestCase, TransactionTestCase from django.core.management import call_command from django_tenants.test.cases import TenantTestCase from django_tenants.test.client import TenantClient from django_tenants.utils import schema_context, get_tenant_model from orders.models import PaymentMethod, Order, Transaction, OrderStatus from customers.models import Customer from accounts.models import CustomUser class PaymentMethodCreationTest(TenantTestCase): """ Тесты создания способов оплаты через команду. TenantTestCase автоматически создаёт тестовый тенант и переключается на его схему. """ def setUp(self): """Очищаем способы оплаты перед каждым тестом""" PaymentMethod.objects.all().delete() def test_create_payment_methods_command(self): """ Тест: команда create_payment_methods создаёт все 5 способов оплаты """ # Проверяем что нет способов оплаты self.assertEqual(PaymentMethod.objects.count(), 0) # Вызываем команду call_command('create_payment_methods') # Проверяем что создано ровно 5 способов self.assertEqual(PaymentMethod.objects.count(), 5) # Проверяем наличие каждого способа expected_codes = [ 'account_balance', 'cash', 'card', 'online', 'legal_entity' ] for code in expected_codes: self.assertTrue( PaymentMethod.objects.filter(code=code).exists(), f"Способ оплаты '{code}' не создан" ) def test_payment_methods_are_system(self): """ Тест: все созданные способы оплаты помечены как системные """ call_command('create_payment_methods') # Все способы должны быть системными non_system_methods = PaymentMethod.objects.filter(is_system=False) self.assertEqual( non_system_methods.count(), 0, "Найдены несистемные способы оплаты" ) def test_payment_methods_are_active_by_default(self): """ Тест: все созданные способы оплаты активны по умолчанию """ call_command('create_payment_methods') # Все способы должны быть активными inactive_methods = PaymentMethod.objects.filter(is_active=False) self.assertEqual( inactive_methods.count(), 0, "Найдены неактивные способы оплаты" ) def test_payment_methods_order(self): """ Тест: способы оплаты создаются в правильном порядке """ call_command('create_payment_methods') # Проверяем порядок каждого способа expected_order = { 'account_balance': 0, 'cash': 1, 'card': 2, 'online': 3, 'legal_entity': 4, } for code, expected_pos in expected_order.items(): method = PaymentMethod.objects.get(code=code) self.assertEqual( method.order, expected_pos, f"Способ '{code}' имеет неверный порядок: {method.order} вместо {expected_pos}" ) def test_payment_methods_idempotent(self): """ Тест: повторный вызов команды не создаёт дубликаты """ # Первый вызов call_command('create_payment_methods') first_count = PaymentMethod.objects.count() # Второй вызов call_command('create_payment_methods') second_count = PaymentMethod.objects.count() # Количество должно остаться тем же self.assertEqual( first_count, second_count, "Команда создала дубликаты при повторном вызове" ) self.assertEqual(second_count, 5) def test_account_balance_payment_method_exists(self): """ Тест: способ оплаты 'account_balance' создаётся корректно Это критический тест для проверки исправления бага с отсутствием способа оплаты из кошелька. """ call_command('create_payment_methods') # Проверяем существование account_balance = PaymentMethod.objects.filter(code='account_balance') self.assertTrue( account_balance.exists(), "Способ оплаты 'account_balance' не создан!" ) # Проверяем атрибуты method = account_balance.first() self.assertEqual(method.name, 'С баланса счёта') self.assertEqual(method.order, 0) self.assertTrue(method.is_system) self.assertTrue(method.is_active) class PaymentMethodMultiTenantTest(TransactionTestCase): """ Тесты изоляции способов оплаты между тенантами. TransactionTestCase нужен для работы с несколькими тенантами в рамках одного теста. """ @classmethod def setUpClass(cls): """Создаём два тестовых тенанта""" super().setUpClass() from tenants.models import Client, Domain # Тенант 1 cls.tenant1 = Client.objects.create( schema_name='test_tenant1', name='Test Tenant 1', owner_email='tenant1@test.com' ) Domain.objects.create( domain='tenant1.test.localhost', tenant=cls.tenant1, is_primary=True ) # Тенант 2 cls.tenant2 = Client.objects.create( schema_name='test_tenant2', name='Test Tenant 2', owner_email='tenant2@test.com' ) Domain.objects.create( domain='tenant2.test.localhost', tenant=cls.tenant2, is_primary=True ) @classmethod def tearDownClass(cls): """Удаляем тестовые тенанты""" from django.db import connection # Удаляем схемы вручную with connection.cursor() as cursor: cursor.execute('DROP SCHEMA IF EXISTS test_tenant1 CASCADE') cursor.execute('DROP SCHEMA IF EXISTS test_tenant2 CASCADE') # Удаляем записи из public cls.tenant1.delete() cls.tenant2.delete() super().tearDownClass() def test_payment_methods_isolated_between_tenants(self): """ Тест: способы оплаты изолированы между тенантами """ # Создаём способы оплаты для tenant1 with schema_context('test_tenant1'): # Удаляем существующие способы оплаты если есть PaymentMethod.objects.all().delete() call_command('create_payment_methods') tenant1_count = PaymentMethod.objects.count() self.assertEqual(tenant1_count, 5) # Проверяем что в tenant2 ничего нет with schema_context('test_tenant2'): # Удаляем существующие способы оплаты если есть PaymentMethod.objects.all().delete() tenant2_count = PaymentMethod.objects.count() self.assertEqual(tenant2_count, 0, "Утечка данных между тенантами!") # Создаём способы для tenant2 with schema_context('test_tenant2'): call_command('create_payment_methods') tenant2_count = PaymentMethod.objects.count() self.assertEqual(tenant2_count, 5) # Проверяем что в tenant1 осталось 5 with schema_context('test_tenant1'): tenant1_count = PaymentMethod.objects.count() self.assertEqual(tenant1_count, 5) def test_custom_payment_method_in_one_tenant(self): """ Тест: кастомный способ оплаты в одном тенанте не виден в другом """ # Создаём системные способы в обоих тенантах with schema_context('test_tenant1'): # Удаляем существующие PaymentMethod.objects.all().delete() call_command('create_payment_methods') # Добавляем кастомный способ PaymentMethod.objects.create( code='custom_tenant1', name='Кастомный способ тенанта 1', is_system=False, order=100 ) tenant1_count = PaymentMethod.objects.count() self.assertEqual(tenant1_count, 6) # 5 системных + 1 кастомный with schema_context('test_tenant2'): # Удаляем существующие PaymentMethod.objects.all().delete() call_command('create_payment_methods') tenant2_count = PaymentMethod.objects.count() self.assertEqual(tenant2_count, 5) # Только системные # Проверяем что кастомного способа нет self.assertFalse( PaymentMethod.objects.filter(code='custom_tenant1').exists(), "Кастомный способ тенанта 1 виден в тенанте 2!" ) class PaymentMethodTransactionTest(TenantTestCase): """ Тесты связи PaymentMethod с Transaction. Проверяем исправление бага с obj.payments → obj.transactions """ def setUp(self): """Подготовка данных для тестов""" # Создаём способы оплаты call_command('create_payment_methods') # Создаём системный статус заказа self.status = OrderStatus.objects.create( code='new', name='Новый', is_system=True ) # Создаём тестового клиента self.customer = Customer.objects.create( name='Тестовый клиент', phone='+375291234567' ) # Создаём тестовый заказ (не указываем order_number, он создастся автоматически) self.order = Order.objects.create( customer=self.customer, status=self.status ) # Получаем способ оплаты self.cash_method = PaymentMethod.objects.get(code='cash') def test_payment_method_has_transactions_relation(self): """ Тест: у PaymentMethod есть связь 'transactions' """ # Проверяем что связь существует self.assertTrue( hasattr(self.cash_method, 'transactions'), "У PaymentMethod нет атрибута 'transactions'" ) def test_transactions_count_starts_at_zero(self): """ Тест: у нового способа оплаты нет транзакций """ count = self.cash_method.transactions.count() self.assertEqual(count, 0) def test_transaction_creates_relation(self): """ Тест: создание транзакции создаёт связь с PaymentMethod """ # Создаём транзакцию transaction = Transaction.objects.create( order=self.order, transaction_type='payment', amount=100.00, payment_method=self.cash_method ) # Проверяем что транзакция видна через связь count = self.cash_method.transactions.count() self.assertEqual(count, 1) # Проверяем что это наша транзакция related_transaction = self.cash_method.transactions.first() self.assertEqual(related_transaction.id, transaction.id) def test_multiple_transactions_same_method(self): """ Тест: несколько транзакций с одним способом оплаты """ # Создаём 3 транзакции наличными for i in range(3): Transaction.objects.create( order=self.order, transaction_type='payment', amount=50.00 * (i + 1), payment_method=self.cash_method ) # Проверяем количество count = self.cash_method.transactions.count() self.assertEqual(count, 3) def test_transactions_isolated_by_payment_method(self): """ Тест: транзакции изолированы по способу оплаты """ card_method = PaymentMethod.objects.get(code='card') # Создаём транзакции разными способами Transaction.objects.create( order=self.order, transaction_type='payment', amount=100.00, payment_method=self.cash_method ) Transaction.objects.create( order=self.order, transaction_type='payment', amount=200.00, payment_method=card_method ) # Проверяем изоляцию self.assertEqual(self.cash_method.transactions.count(), 1) self.assertEqual(card_method.transactions.count(), 1) def test_payment_method_deletion_protection(self): """ Тест: нельзя удалить способ оплаты с транзакциями (PROTECT) """ from django.db.models import ProtectedError # Создаём транзакцию Transaction.objects.create( order=self.order, transaction_type='payment', amount=100.00, payment_method=self.cash_method ) # Пытаемся удалить способ оплаты with self.assertRaises(ProtectedError): self.cash_method.delete() def test_account_balance_payment_method_usable(self): """ Тест: способ оплаты 'account_balance' можно использовать в транзакциях Проверка что исправление бага работает корректно. """ from decimal import Decimal account_balance = PaymentMethod.objects.get(code='account_balance') # Добавляем деньги в кошелёк клиента self.customer.wallet_balance = Decimal('500.00') self.customer.save() # Создаём транзакцию с оплатой из кошелька transaction = Transaction.objects.create( order=self.order, transaction_type='payment', amount=Decimal('150.00'), payment_method=account_balance ) # Проверяем что транзакция создана self.assertIsNotNone(transaction.id) self.assertEqual(transaction.payment_method.code, 'account_balance') # Проверяем связь self.assertEqual(account_balance.transactions.count(), 1) class PaymentMethodAdminTest(TenantTestCase): """ Тесты для проверки работы админки PaymentMethod. Проверяем исправление бага с obj.payments → obj.transactions """ def setUp(self): """Подготовка данных""" call_command('create_payment_methods') # Создаём суперпользователя self.admin_user = CustomUser.objects.create_superuser( email='admin@test.com', password='testpass123' ) # Логинимся self.client = TenantClient(self.tenant) self.client.force_login(self.admin_user) def test_payment_method_admin_list_view(self): """ Тест: список способов оплаты в админке загружается без ошибок """ response = self.client.get('/admin/orders/paymentmethod/') # Проверяем что нет ошибки AttributeError self.assertEqual(response.status_code, 200) self.assertNotContains(response, 'AttributeError') self.assertNotContains(response, "has no attribute 'payments'") def test_payment_method_admin_shows_all_methods(self): """ Тест: в админке отображаются все 5 способов оплаты """ response = self.client.get('/admin/orders/paymentmethod/') self.assertContains(response, 'С баланса счёта') self.assertContains(response, 'Наличными') self.assertContains(response, 'Картой') self.assertContains(response, 'Онлайн') self.assertContains(response, 'Безнал от ЮРЛИЦ') class PaymentMethodOrderingTest(TenantTestCase): """ Тесты сортировки способов оплаты. """ def setUp(self): """Создаём способы оплаты""" call_command('create_payment_methods') def test_payment_methods_ordered_by_order_field(self): """ Тест: способы оплаты сортируются по полю 'order' """ methods = PaymentMethod.objects.all() # Проверяем что порядок возрастающий previous_order = -1 for method in methods: self.assertGreater( method.order, previous_order, f"Нарушен порядок сортировки: {method.code}" ) previous_order = method.order def test_account_balance_is_first(self): """ Тест: 'account_balance' первый в списке (order=0) """ first_method = PaymentMethod.objects.first() self.assertEqual(first_method.code, 'account_balance') self.assertEqual(first_method.order, 0)