"""Тесты для Model Mixins.""" from django.db import connection, models from django.test import TestCase, TransactionTestCase from django.test.utils import isolate_apps from apps.core.mixins import ( AuditMixin, OrderableMixin, SlugMixin, SoftDeleteMixin, StatusMixin, TimestampMixin, ) from tests.apps.user.factories import UserFactory class TimestampMixinTest(TestCase): """Тесты для TimestampMixin.""" def test_created_at_auto_set(self): """Проверка автоматической установки created_at.""" # Используем BackgroundJob как пример модели с TimestampMixin from apps.core.models import BackgroundJob job = BackgroundJob.objects.create( task_id="test-task-1", task_name="test.task", ) self.assertIsNotNone(job.created_at) self.assertIsNotNone(job.updated_at) def test_updated_at_auto_update(self): """Проверка автоматического обновления updated_at.""" from apps.core.models import BackgroundJob job = BackgroundJob.objects.create( task_id="test-task-2", task_name="test.task", ) original_updated = job.updated_at # Обновляем запись job.progress = 50 job.save() job.refresh_from_db() self.assertGreaterEqual(job.updated_at, original_updated) class UUIDPrimaryKeyMixinTest(TestCase): """Тесты для UUIDPrimaryKeyMixin.""" def test_uuid_auto_generated(self): """Проверка автоматической генерации UUID.""" from apps.core.models import BackgroundJob job = BackgroundJob.objects.create( task_id="test-task-3", task_name="test.task", ) self.assertIsNotNone(job.id) # Проверяем что ID похож на UUID (строка 32+ символа с дефисами) self.assertEqual(len(str(job.id)), 36) def test_uuid_unique(self): """Проверка уникальности UUID.""" from apps.core.models import BackgroundJob job1 = BackgroundJob.objects.create( task_id="test-task-4a", task_name="test.task", ) job2 = BackgroundJob.objects.create( task_id="test-task-4b", task_name="test.task", ) self.assertNotEqual(job1.id, job2.id) class SoftDeleteMixinTest(TestCase): """Тесты для SoftDeleteMixin.""" def test_soft_delete_mixin_fields(self): """Проверка наличия полей is_deleted и deleted_at.""" # Проверяем что поля определены в миксине field_names = [f.name for f in SoftDeleteMixin._meta.get_fields()] self.assertIn("is_deleted", field_names) self.assertIn("deleted_at", field_names) def test_soft_delete_queryset_methods(self): """Проверка методов SoftDeleteQuerySet.""" from apps.core.mixins import SoftDeleteQuerySet # Проверяем что методы определены self.assertTrue(hasattr(SoftDeleteQuerySet, "alive")) self.assertTrue(hasattr(SoftDeleteQuerySet, "dead")) self.assertTrue(hasattr(SoftDeleteQuerySet, "hard_delete")) class StatusMixinTest(TestCase): """Тесты для StatusMixin.""" def test_status_choices(self): """Проверка наличия статусов.""" self.assertEqual(StatusMixin.Status.DRAFT, "draft") self.assertEqual(StatusMixin.Status.ACTIVE, "active") self.assertEqual(StatusMixin.Status.INACTIVE, "inactive") self.assertEqual(StatusMixin.Status.ARCHIVED, "archived") class OrderableMixinTest(TestCase): """Тесты для OrderableMixin.""" def test_orderable_mixin_has_order_field(self): """Проверка наличия поля order.""" self.assertTrue(hasattr(OrderableMixin, "order")) @isolate_apps("apps.core") class MixinsBehaviorTest(TransactionTestCase): class TestModel( SoftDeleteMixin, OrderableMixin, StatusMixin, SlugMixin, AuditMixin, TimestampMixin, models.Model, ): name = models.CharField(max_length=50) class Meta: app_label = "core" @classmethod def setUpClass(cls): super().setUpClass() table_name = cls.TestModel._meta.db_table existing_tables = connection.introspection.table_names() with connection.schema_editor() as schema_editor: if table_name in existing_tables: schema_editor.delete_model(cls.TestModel) schema_editor.create_model(cls.TestModel) @classmethod def tearDownClass(cls): table_name = cls.TestModel._meta.db_table with connection.schema_editor() as schema_editor: if table_name in connection.introspection.table_names(): schema_editor.delete_model(cls.TestModel) super().tearDownClass() def test_soft_delete_and_restore(self): user = UserFactory.create_user() obj = self.TestModel.objects.create( name=f"item-{user.id}", slug=f"slug-{user.id}", created_by=user, ) obj.delete() self.assertTrue(obj.is_deleted) self.assertIsNotNone(obj.deleted_at) self.assertEqual(self.TestModel.objects.count(), 0) self.assertEqual(self.TestModel.all_objects.count(), 1) obj.restore() self.assertFalse(obj.is_deleted) self.assertEqual(self.TestModel.objects.count(), 1) def test_soft_delete_queryset_methods(self): obj = self.TestModel.objects.create(name="alive", slug="alive-slug") self.TestModel.objects.filter(id=obj.id).delete() self.assertEqual(self.TestModel.objects.count(), 0) self.assertEqual(self.TestModel.objects.deleted_only().count(), 1) def test_hard_delete(self): obj = self.TestModel.objects.create(name="hard", slug="hard-slug") obj.hard_delete() self.assertEqual(self.TestModel.all_objects.count(), 0) def test_orderable_moves(self): obj = self.TestModel.objects.create(name="ord", slug="ord-slug", order=2) obj.move_up() obj.refresh_from_db() self.assertEqual(obj.order, 1) obj.move_down() obj.refresh_from_db() self.assertEqual(obj.order, 2) obj.move_to(5) obj.refresh_from_db() self.assertEqual(obj.order, 5) obj.move_to(-1) obj.refresh_from_db() self.assertEqual(obj.order, 5) def test_status_transitions(self): obj = self.TestModel.objects.create(name="status", slug="status-slug") self.assertTrue(obj.is_draft) obj.activate() obj.refresh_from_db() self.assertTrue(obj.is_active_status) obj.deactivate() obj.refresh_from_db() self.assertEqual(obj.status, obj.Status.INACTIVE) obj.archive() obj.refresh_from_db() self.assertTrue(obj.is_archived)