Фиксируем модели, UserStory и тесты
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
Пакет базовых классов
|
||||
"""
|
||||
|
||||
from .core_command import CoreCommand
|
||||
from .core_enum import CoreEnum
|
||||
from .core_exception import CoreException
|
||||
from .core_model import CoreModel
|
||||
@@ -15,3 +16,4 @@ from .core_model import CoreModel
|
||||
from .core_persistent_model import CorePersistentModel
|
||||
from .core_persistent_model import CorePersistentModel
|
||||
from .core_response import CoreResponse
|
||||
from .core_test_case import CoreTestCase
|
||||
|
||||
37
net/xeaf/rack/core/core_command.py
Normal file
37
net/xeaf/rack/core/core_command.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса CoreCommand
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.core.management import call_command
|
||||
|
||||
|
||||
class CoreCommand(BaseCommand):
|
||||
"""
|
||||
Реализует базовые свойства всех обработчиков команд
|
||||
"""
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def _clear_database(self):
|
||||
"""
|
||||
Очищает базу данных
|
||||
"""
|
||||
|
||||
call_command("flush", "--no-input")
|
||||
|
||||
@classmethod
|
||||
def is_debug_or_test(cls):
|
||||
"""
|
||||
Возвращает признак режима отладки или тестов
|
||||
"""
|
||||
|
||||
return settings.DEBUG or 'test' in sys.argv
|
||||
25
net/xeaf/rack/core/core_test_case.py
Normal file
25
net/xeaf/rack/core/core_test_case.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса CoreTestCase
|
||||
"""
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
|
||||
class CoreTestCase(TestCase):
|
||||
"""
|
||||
Реализует инициализацию тестовой базы данных перед каждым тестом
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Подготовка к тестированию
|
||||
"""
|
||||
|
||||
call_command("rack_test_database")
|
||||
|
||||
9
net/xeaf/rack/management/__init__.py
Normal file
9
net/xeaf/rack/management/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Пакет классов управления
|
||||
"""
|
||||
10
net/xeaf/rack/management/commands/__init__.py
Normal file
10
net/xeaf/rack/management/commands/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Пакет классов команд управления
|
||||
"""
|
||||
|
||||
44
net/xeaf/rack/management/commands/rack_test_database.py
Normal file
44
net/xeaf/rack/management/commands/rack_test_database.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса для команды rack_test_database
|
||||
"""
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.core.management.base import no_translations
|
||||
|
||||
from net.xeaf.rack.core import CoreCommand
|
||||
from net.xeaf.rack.management.userstory import AccountTestData
|
||||
from net.xeaf.rack.management.userstory import SessionTestData
|
||||
|
||||
|
||||
class Command(CoreCommand):
|
||||
"""
|
||||
Инициализирует базу данных для тестирования
|
||||
"""
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Обрабатывает команду инициализации БД
|
||||
|
||||
:params args: Позиционные аргументы
|
||||
:params options: Дополнительные параметры
|
||||
"""
|
||||
|
||||
if self.is_debug_or_test():
|
||||
|
||||
# Готовимся
|
||||
self._clear_database()
|
||||
call_command("migrate")
|
||||
|
||||
# Инициализируем данные
|
||||
AccountTestData.init_accounts()
|
||||
SessionTestData.init_sessions()
|
||||
|
||||
else:
|
||||
self.stdout.write(self.style.ERROR("The rack_test_database command can only be executed in debug or testing mode."))
|
||||
33
net/xeaf/rack/management/commands/rack_wipe_database.py
Normal file
33
net/xeaf/rack/management/commands/rack_wipe_database.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса для команды rack_wipe_database
|
||||
"""
|
||||
|
||||
from django.core.management.base import no_translations
|
||||
|
||||
from net.xeaf.rack.core import CoreCommand
|
||||
|
||||
|
||||
class Command(CoreCommand):
|
||||
"""
|
||||
Очищает базу данных
|
||||
"""
|
||||
|
||||
@no_translations
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Обрабатывает команду инициализации БД
|
||||
|
||||
:params args: Позиционные аргументы
|
||||
:params options: Дополнительные параметры
|
||||
"""
|
||||
|
||||
if self.is_debug_or_test():
|
||||
self._clear_database()
|
||||
else:
|
||||
self.stdout.write(self.style.ERROR("The rack_wipe_database command can only be executed in debug or test mode."))
|
||||
12
net/xeaf/rack/management/userstory/__init__.py
Normal file
12
net/xeaf/rack/management/userstory/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Пакет классов создания тестовой БД
|
||||
"""
|
||||
|
||||
from .account_test_data import AccountTestData
|
||||
from .session_test_data import SessionTestData
|
||||
68
net/xeaf/rack/management/userstory/account_test_data.py
Normal file
68
net/xeaf/rack/management/userstory/account_test_data.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса AccountTestData
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from net.xeaf.rack.models import AccountModel
|
||||
|
||||
|
||||
class AccountTestData:
|
||||
"""
|
||||
Инициализирует модели данных тестовых Аккаунтов
|
||||
"""
|
||||
|
||||
I_IVANOV_ID = uuid.UUID("bd428358-6944-4657-8044-a3dc1c4dff8a")
|
||||
""" Идентификатор пользователя Иван И. Иванов """
|
||||
|
||||
P_PETROV_ID = uuid.UUID("9c5010cc-f3be-42ae-bc05-d142d68a610a")
|
||||
""" Идентификатор пользователя Петр П. Петров """
|
||||
|
||||
DEFAULT_PASSWORD = "manager123!"
|
||||
""" Пароль по умолчанию для тестов """
|
||||
|
||||
@classmethod
|
||||
def init_accounts(cls):
|
||||
"""
|
||||
Инициализирует Аккаунты пользователя
|
||||
"""
|
||||
|
||||
cls.init_i_ivanov_account()
|
||||
cls.init_p_petrov_account()
|
||||
|
||||
@classmethod
|
||||
def init_i_ivanov_account(cls):
|
||||
"""
|
||||
Инициализирует Аккаунт пользователя Иван И. Иванов
|
||||
"""
|
||||
|
||||
account = AccountModel()
|
||||
account.id = cls.I_IVANOV_ID
|
||||
account.username = "i.ivanov@xeaf.net"
|
||||
account.nick_name = "Иван"
|
||||
account.full_name = "Иван И. Иванов"
|
||||
account.email = "i.ivanov@xeaf.net"
|
||||
account.set_password(cls.DEFAULT_PASSWORD)
|
||||
account.save()
|
||||
|
||||
@classmethod
|
||||
def init_p_petrov_account(cls):
|
||||
"""
|
||||
Инициализирует Аккаунт пользователя Петр П. Петров
|
||||
"""
|
||||
|
||||
account = AccountModel()
|
||||
account.id = cls.P_PETROV_ID
|
||||
account.username = "p.petrov@xeaf.net"
|
||||
account.nick_name = "Петр"
|
||||
account.full_name = "Петр П. Петров"
|
||||
account.email = "p.petrov@xeaf.net"
|
||||
account.set_password(cls.DEFAULT_PASSWORD)
|
||||
account.save()
|
||||
|
||||
62
net/xeaf/rack/management/userstory/session_test_data.py
Normal file
62
net/xeaf/rack/management/userstory/session_test_data.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса SessionTestData
|
||||
"""
|
||||
|
||||
from net.xeaf.rack.models import SessionModel
|
||||
from .account_test_data import AccountTestData
|
||||
|
||||
|
||||
class SessionTestData:
|
||||
"""
|
||||
Инициализирует модели данных тестовых Сессий пользователей
|
||||
"""
|
||||
|
||||
I_IVANOV_SESSION_TOKEN = "ced4217bbd12b10a59a3f9342164793819a29238"
|
||||
""" Токен сессии пользователя Иван И. Иванов """
|
||||
|
||||
P_PETROV_SESSION_TOKEN = "1e20d0f236efdda2f31688b6c176bf817cb3357e"
|
||||
""" Токен сессии пользователя Петр П. Петров """
|
||||
|
||||
TEST_SESSION_TTL = 3600
|
||||
""" Время жизни тестовой сессии """
|
||||
|
||||
@classmethod
|
||||
def init_sessions(cls):
|
||||
"""
|
||||
Инициализирует сессии для тестовых пользователей
|
||||
"""
|
||||
|
||||
cls.init_i_ivanov_session()
|
||||
cls.init_p_petrov_session()
|
||||
|
||||
@classmethod
|
||||
def init_i_ivanov_session(cls):
|
||||
"""
|
||||
Создает Сессию для пользователя Иван И. Иванов
|
||||
"""
|
||||
|
||||
session = SessionModel()
|
||||
session.token = cls.I_IVANOV_SESSION_TOKEN
|
||||
session.account_id = AccountTestData.I_IVANOV_ID
|
||||
session.actual_account_id = AccountTestData.I_IVANOV_ID
|
||||
session.renew(cls.TEST_SESSION_TTL)
|
||||
session.save()
|
||||
|
||||
@classmethod
|
||||
def init_p_petrov_session(cls):
|
||||
"""
|
||||
Создает Сессию для пользователя Петр П. Петров
|
||||
"""
|
||||
|
||||
session = SessionModel()
|
||||
session.token = cls.P_PETROV_SESSION_TOKEN
|
||||
session.account_id = AccountTestData.P_PETROV_ID
|
||||
session.actual_account_id = AccountTestData.P_PETROV_ID
|
||||
session.renew(cls.TEST_SESSION_TTL)
|
||||
session.save()
|
||||
@@ -16,7 +16,7 @@ class CryptoManager:
|
||||
Реализует методы работы со случайными и криптографическими значениями
|
||||
"""
|
||||
|
||||
DEFAULT_TOKEN_LENGTH = 32
|
||||
DEFAULT_TOKEN_LENGTH = 40
|
||||
""" Длина токена по умолчанию """
|
||||
|
||||
DEFAULT_PIN_CODE_LENGTH = 4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 09:19
|
||||
# Generated by Django 6.0.7 on 2026-07-12 10:42
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.db.models.deletion
|
||||
@@ -34,7 +34,7 @@ class Migration(migrations.Migration):
|
||||
('full_name', models.CharField(blank=True, max_length=127, null=True, verbose_name='rack.models.account.full_name')),
|
||||
('joined_at', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.joined_at')),
|
||||
('last_login', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.last_login')),
|
||||
('extra', models.JSONField(default={}, verbose_name='rack.models.account.extra')),
|
||||
('extra', models.JSONField(default=dict, verbose_name='rack.models.account.extra')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='rack.mixin.created_at')),
|
||||
('expires_at', models.DateTimeField(verbose_name='rack.mixin.expires_at')),
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='rack.mixin.id')),
|
||||
('token', models.CharField(max_length=32, verbose_name='rack.models.session_model.token')),
|
||||
('token', models.CharField(max_length=40, verbose_name='rack.models.session_model.token')),
|
||||
('device_id', models.CharField(blank=True, default='WEB', max_length=36, null=True, verbose_name='rack.models.session_model.device_id')),
|
||||
('fcm_token', models.CharField(blank=True, default=None, max_length=256, null=True, verbose_name='rack.models.session_model.fcm_token')),
|
||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.account')),
|
||||
|
||||
@@ -75,7 +75,7 @@ class AccountModel(AbstractUser, UuidIdMixin, CreatedAtMixin, UpdatedAtMixin, De
|
||||
|
||||
extra = models.JSONField(
|
||||
null=False,
|
||||
default={},
|
||||
default=dict,
|
||||
blank=False,
|
||||
verbose_name=_("rack.models.account.extra"),
|
||||
)
|
||||
|
||||
@@ -9,64 +9,48 @@
|
||||
"""
|
||||
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
from net.xeaf.rack.core import CoreTestCase
|
||||
from net.xeaf.rack.management.userstory import AccountTestData
|
||||
from net.xeaf.rack.models import AccountModel
|
||||
|
||||
|
||||
class AccountModelTests(TestCase):
|
||||
class AccountModelTests(CoreTestCase):
|
||||
"""
|
||||
Тесты для класса AccountModel
|
||||
"""
|
||||
|
||||
def test_create_account_with_defaults(self):
|
||||
def test_user_story_accounts_exist(self):
|
||||
"""
|
||||
Проверяет корректное создание аккаунта с дефолтными полями
|
||||
Проверяет, что эталонные пользователи успешно созданы в User Story
|
||||
"""
|
||||
|
||||
email = "test_user@example.com"
|
||||
# Ищем Ивана Иванова по фиксированному UUID
|
||||
ivanov = AccountModel.objects.get(id=AccountTestData.I_IVANOV_ID)
|
||||
|
||||
# Создаем через чистый ORM, как ты любишь, построчно
|
||||
account = AccountModel()
|
||||
account.username = email
|
||||
account.save()
|
||||
self.assertEqual(ivanov.username, "i.ivanov@xeaf.net")
|
||||
self.assertEqual(ivanov.full_name, "Иван И. Иванов")
|
||||
self.assertEqual(ivanov.get_short_name(), "Иван")
|
||||
|
||||
# Проверяем контракт полей
|
||||
self.assertEqual(account.username, email)
|
||||
self.assertIsNone(account.full_name)
|
||||
self.assertIsNone(account.nick_name)
|
||||
# Проверяем, что наше исправление JSONField (default=dict) работает:
|
||||
# Поле extra должно быть пустым словарем, а не None, готовым к .update()
|
||||
self.assertEqual(ivanov.extra, {})
|
||||
|
||||
# Если ты исправил дефолт на dict, тест гарантирует безопасность:
|
||||
self.assertEqual(account.extra, {})
|
||||
def test_password_hashing_works(self):
|
||||
"""Проверяет, что пароли пользователей в User Story захешированы, а не лежат строкой"""
|
||||
ivanov = AccountModel.objects.get(id=AccountTestData.I_IVANOV_ID)
|
||||
|
||||
def test_compatibility_methods(self):
|
||||
"""
|
||||
Проверяет методы совместимости get_full_name и get_short_name
|
||||
"""
|
||||
|
||||
account = AccountModel()
|
||||
account.username = "names@example.com"
|
||||
account.full_name = "Иван Иванов"
|
||||
account.nick_name = "vanya"
|
||||
account.save()
|
||||
|
||||
self.assertEqual(account.get_full_name(), "Иван Иванов")
|
||||
self.assertEqual(account.get_short_name(), "vanya")
|
||||
# Проверяем, что стандартный джанговский механизм проверки пароля работает
|
||||
self.assertTrue(ivanov.check_password(AccountTestData.DEFAULT_PASSWORD))
|
||||
# Гарантируем, что в базе лежит хэш, а не сырая строка
|
||||
self.assertNotEqual(ivanov.password, AccountTestData.DEFAULT_PASSWORD)
|
||||
|
||||
def test_username_uniqueness_constraint(self):
|
||||
"""
|
||||
Проверяет жесткое ограничение уникальности на уровне UniqueConstraint
|
||||
"""
|
||||
|
||||
duplicate_email = "duplicate@example.com"
|
||||
|
||||
# Создаем первого
|
||||
account1 = AccountModel(username=duplicate_email)
|
||||
account1.save()
|
||||
|
||||
# Попытка создать второго с тем же username должна намертво упасть в БД
|
||||
account2 = AccountModel(username=duplicate_email)
|
||||
"""Проверяет жесткое ограничение уникальности email-username"""
|
||||
# Попытка создать пользователя с дублирующим email Ивана должна намертво упасть
|
||||
duplicate_account = AccountModel()
|
||||
duplicate_account.username = "i.ivanov@xeaf.net"
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
account2.save()
|
||||
duplicate_account.save()
|
||||
|
||||
|
||||
@@ -9,75 +9,56 @@
|
||||
"""
|
||||
|
||||
from django.db.utils import IntegrityError
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from net.xeaf.rack.managers import CryptoManager
|
||||
from net.xeaf.rack.models import AccountModel
|
||||
from net.xeaf.rack.core import CoreTestCase
|
||||
from net.xeaf.rack.management.userstory import AccountTestData
|
||||
from net.xeaf.rack.management.userstory import SessionTestData
|
||||
from net.xeaf.rack.models import SessionModel
|
||||
|
||||
|
||||
class SessionModelTests(TestCase):
|
||||
class SessionModelTests(CoreTestCase):
|
||||
"""
|
||||
Тесты для класса SessionModel
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
def test_user_story_sessions_exist_and_linked(self):
|
||||
"""
|
||||
Атомарно создаем тестовых пользователей для связей
|
||||
Проверяет, что сессии из User Story созданы и правильно связаны с аккаунтами
|
||||
"""
|
||||
|
||||
self.primary_user = AccountModel.objects.create_user(
|
||||
username="primary@example.com",
|
||||
password="secure_password_123"
|
||||
)
|
||||
self.actual_user = AccountModel.objects.create_user(
|
||||
username="actual@example.com",
|
||||
password="secure_password_456"
|
||||
)
|
||||
# Ищем сессию Ивана по фиксированному токену
|
||||
session = SessionModel.objects.get(token=SessionTestData.I_IVANOV_SESSION_TOKEN)
|
||||
|
||||
def test_create_session_with_defaults_and_relations(self):
|
||||
"""
|
||||
Проверяет успешное создание сессии, заполнение дефолтных полей и внешних ключей
|
||||
"""
|
||||
# Проверяем связи (включая твой крутой механизм имперсонации)
|
||||
self.assertEqual(session.account_id, AccountTestData.I_IVANOV_ID)
|
||||
self.assertEqual(session.actual_account_id, AccountTestData.I_IVANOV_ID)
|
||||
|
||||
token_str = CryptoManager.gen_token()
|
||||
|
||||
session = SessionModel()
|
||||
session.token = token_str
|
||||
session.account = self.primary_user
|
||||
session.actual_account = self.actual_user
|
||||
session.renew(3600)
|
||||
session.save()
|
||||
|
||||
# Проверяем контракт полей и связей
|
||||
self.assertEqual(session.token, token_str)
|
||||
self.assertEqual(session.account.id, self.primary_user.id)
|
||||
self.assertEqual(session.actual_account.id, self.actual_user.id)
|
||||
# Проверяем дефолты текстовых полей, которые мы закладывали в модель
|
||||
self.assertEqual(session.device_id, "WEB")
|
||||
self.assertIsNone(session.fcm_token)
|
||||
|
||||
def test_session_expiration_logic(self):
|
||||
"""
|
||||
Проверяет, что метод renew() правильно выставил expires_at в будущем
|
||||
"""
|
||||
|
||||
session = SessionModel.objects.get(token=SessionTestData.I_IVANOV_SESSION_TOKEN)
|
||||
|
||||
# Сессия должна быть активной прямо сейчас
|
||||
self.assertGreater(session.expires_at, timezone.now())
|
||||
|
||||
def test_token_uniqueness_constraint(self):
|
||||
"""
|
||||
Проверяет жесткое ограничение уникальности токена сессии в базе данных
|
||||
Проверяет жесткое ограничение уникальности токенов сессий
|
||||
"""
|
||||
|
||||
duplicate_token = "same_token_xyz"
|
||||
|
||||
# Создаем первую валидную сессию
|
||||
session1 = SessionModel(
|
||||
token=duplicate_token,
|
||||
account=self.primary_user,
|
||||
actual_account=self.actual_user
|
||||
)
|
||||
session1.renew(3600)
|
||||
session1.save()
|
||||
|
||||
# Попытка создать вторую сессию с тем же токеном должна намертво упасть в БД
|
||||
session2 = SessionModel(
|
||||
token=duplicate_token,
|
||||
account=self.primary_user,
|
||||
actual_account=self.actual_user
|
||||
)
|
||||
# Попытка создать новую сессию с токеном Петра должна упасть
|
||||
duplicate_session = SessionModel()
|
||||
duplicate_session.token = SessionTestData.P_PETROV_SESSION_TOKEN
|
||||
duplicate_session.account_id = AccountTestData.I_IVANOV_ID
|
||||
duplicate_session.actual_account_id = AccountTestData.I_IVANOV_ID
|
||||
duplicate_session.expire()
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
session2.renew(3600)
|
||||
session2.save()
|
||||
duplicate_session.save()
|
||||
|
||||
Reference in New Issue
Block a user