Подготовка новых моделей тестов

This commit is contained in:
2026-07-17 13:23:17 +03:00
parent b68f17f069
commit 0c1c2bd61c
65 changed files with 337 additions and 5147 deletions

View File

@@ -1,10 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет описания тестов для служебных классов и утилит
"""

View File

@@ -1,12 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет описания тестов классов авторизации
"""
from .expiring_token_authentication_tests import ExpiringTokenAuthenticationTests

View File

@@ -1,173 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса ExpiringTokenAuthenticationTests
"""
import datetime
from django.utils import timezone
from django.utils import translation
from rest_framework.parsers import JSONParser
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory
from net.xeaf.rack.core.tests import CoreTestCase
from net.xeaf.rack.management.userstory import AccountTestData
from net.xeaf.rack.management.userstory import SessionTestData
from net.xeaf.rack.models import AccountModel
from net.xeaf.rack.models import SessionModel
from net.xeaf.rack.utils.auth import ExpiringTokenAuthentication
class ExpiringTokenAuthenticationTests(CoreTestCase):
"""
Тесты для ExpiringTokenAuthentication
"""
def setUp(self) -> None:
"""
Подготовка к тестированию
"""
super().setUp()
self.auth = ExpiringTokenAuthentication()
self.factory = APIRequestFactory()
self.old_language = translation.get_language()
def tearDown(self) -> None:
"""
Восстанавливаем язык глобального контекста после тестов
"""
super().tearDown()
translation.activate(self.old_language)
@classmethod
def _wrap_to_drf_request(cls, wsgi_request) -> Request:
"""
Вспомогательный метод для честного оборачивания Django WSGIRequest в DRF Request
"""
return Request(wsgi_request, parsers=[JSONParser()])
def test_authenticate_adjusts_language_via_query_param(self) -> None:
"""
Должен переключить глобальную локаль Django, используя механизмы LocaleManager
"""
# 1. Создаем нативный запрос Django
wsgi_req = self.factory.get("/", {"x-ui-language": "en"})
# 2. Оборачиваем его в честный DRF Request, у которого ЕСТЬ query_params
drf_request = self._wrap_to_drf_request(wsgi_req)
self.auth.authenticate(drf_request)
# Проверяем, что LocaleManager отработал "вживую" и активировал перевод
self.assertEqual(translation.get_language(), "en")
self.assertEqual(getattr(drf_request, "LANGUAGE_CODE", None), "EN")
def test_authenticate_extracts_token_from_query_params_and_logs_in(self) -> None:
"""
Должен успешно авторизовать Ивана Иванова, если его токен передан в GET параметре
"""
wsgi_req = self.factory.get("/", {ExpiringTokenAuthentication.AUTH_TOKEN_PARAM: SessionTestData.I_IVANOV_SESSION_TOKEN})
drf_request = self._wrap_to_drf_request(wsgi_req)
result = self.auth.authenticate(drf_request)
# Линтер-безопасная проверка контейнера
self.assertIsInstance(result, tuple)
assert result is not None
account, session = result
self.assertEqual(account.id, AccountTestData.I_IVANOV_ID)
self.assertEqual(session.token, SessionTestData.I_IVANOV_SESSION_TOKEN)
def test_authenticate_fallback_to_super_headers_if_no_query_param(self) -> None:
"""
Должен отработать через стандартный заголовок 'Authorization: Token ...' родительского класса
"""
wsgi_req = self.factory.get("/", HTTP_AUTHORIZATION=f"Token {SessionTestData.P_PETROV_SESSION_TOKEN}")
drf_request = self._wrap_to_drf_request(wsgi_req)
result = self.auth.authenticate(drf_request)
self.assertIsInstance(result, tuple)
assert result is not None
account, session = result
self.assertEqual(account.id, AccountTestData.P_PETROV_ID)
def test_authenticate_credentials_returns_none_tuple_if_token_not_found(self) -> None:
"""
Должен мягко вернуть (None, None), если токена вообще нет в базе сессий
"""
result = self.auth.authenticate_credentials("completely_fake_token")
self.assertEqual(result, (None, None))
def test_authenticate_credentials_returns_none_tuple_if_session_is_expired(self) -> None:
"""
Должен вернуть (None, None), если сессия Петра Петрова просрочена
"""
session = SessionModel.objects.get(token=SessionTestData.P_PETROV_SESSION_TOKEN)
session.expires_at = timezone.now() - datetime.timedelta(seconds=1)
session.save()
result = self.auth.authenticate_credentials(SessionTestData.P_PETROV_SESSION_TOKEN)
self.assertEqual(result, (None, None))
def test_authenticate_credentials_returns_none_tuple_if_account_is_not_active(self) -> None:
"""
Должен вернуть (None, None), если AccountManager заблокировал вход (is_active = False)
"""
account = AccountModel.objects.get(id=AccountTestData.I_IVANOV_ID)
account.is_active = False
account.save()
result = self.auth.authenticate_credentials(SessionTestData.I_IVANOV_SESSION_TOKEN)
self.assertEqual(result, (None, None))
def test_authenticate_credentials_returns_none_tuple_if_account_is_deleted(self) -> None:
"""
Должен вернуть (None, None), если пользователь помечен как удаленный
"""
account = AccountModel.objects.get(id=AccountTestData.I_IVANOV_ID)
account.soft_delete(save=True)
result = self.auth.authenticate_credentials(SessionTestData.I_IVANOV_SESSION_TOKEN)
self.assertEqual(result, (None, None))
def test_authenticate_credentials_renews_session_timestamp_on_success(self) -> None:
"""
Должен обновить время жизни сессии в БД и вернуть валидные объекты при успешном входе
"""
session_before = SessionModel.objects.get(token=SessionTestData.I_IVANOV_SESSION_TOKEN)
result = self.auth.authenticate_credentials(SessionTestData.I_IVANOV_SESSION_TOKEN)
self.assertIsInstance(result, tuple)
assert result is not None
account, session = result
self.assertIsInstance(account, AccountModel)
self.assertEqual(account.id, AccountTestData.I_IVANOV_ID)
self.assertIsInstance(session, SessionModel)
session.refresh_from_db()
self.assertGreater(session.expires_at, session_before.expires_at)

View File

@@ -1,99 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса DateUtilsTest
"""
import datetime
from unittest.mock import patch
from django.test import SimpleTestCase
from django.utils import timezone
from net.xeaf.rack.utils import DateUtils
class DateUtilsTestCase(SimpleTestCase):
"""
Классы для DateUtils.
"""
def test_now_returns_frozen_current_time(self) -> None:
"""
Должен вернуть точно зафиксированное текущее время через mock.patch
"""
fixed_time = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=timezone.get_current_timezone())
with patch("django.utils.timezone.now", return_value=fixed_time):
result = DateUtils.now()
self.assertEqual(result, fixed_time)
def test_now_returns_timezone_aware_datetime(self) -> None:
"""
Должен возвращать объект datetime с информацией о часовом поясе (is_aware)
"""
result = DateUtils.now()
self.assertTrue(timezone.is_aware(result))
def test_shift_time_uses_now_if_date_time_is_none(self) -> None:
"""
Должен автоматически взять текущее время (now), если date_time передан как None
"""
fixed_now = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=datetime.timezone.utc)
expected_time = datetime.datetime(2026, 7, 17, 12, 0, 0, tzinfo=datetime.timezone.utc)
with patch("django.utils.timezone.now", return_value=fixed_now):
result = DateUtils.shift_time(date_time=None, days=3)
self.assertEqual(result, expected_time)
def test_shift_time_uses_now_by_omission(self) -> None:
"""
Должен автоматически взять текущее время (now), если параметр date_time вообще опущен
"""
fixed_now = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=datetime.timezone.utc)
expected_time = datetime.datetime(2026, 7, 14, 14, 0, 0, tzinfo=datetime.timezone.utc)
with patch("django.utils.timezone.now", return_value=fixed_now):
result = DateUtils.shift_time(hours=2)
self.assertEqual(result, expected_time)
def test_shift_time_adds_days_correctly_with_explicit_date(self) -> None:
"""
Должен корректно сдвигать время вперед, если передан явный объект datetime
"""
base_time = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=datetime.timezone.utc)
expected_time = datetime.datetime(2026, 7, 19, 12, 0, 0, tzinfo=datetime.timezone.utc)
result = DateUtils.shift_time(base_time, days=5)
self.assertEqual(result, expected_time)
def test_shift_time_subtracts_hours_correctly(self) -> None:
"""
Должен корректно сдвигать время назад при передаче отрицательных значений
"""
base_time = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=datetime.timezone.utc)
expected_time = datetime.datetime(2026, 7, 14, 9, 0, 0, tzinfo=datetime.timezone.utc)
result = DateUtils.shift_time(base_time, hours=-3)
self.assertEqual(result, expected_time)
def test_shift_time_complex_modification(self) -> None:
"""
Должен атомарно применять комбинированный сдвиг по всем осям времени
"""
base_time = datetime.datetime(2026, 7, 14, 12, 0, 0, tzinfo=datetime.timezone.utc)
expected_time = datetime.datetime(2026, 7, 15, 14, 30, 15, tzinfo=datetime.timezone.utc)
result = DateUtils.shift_time(base_time, days=1, hours=2, minutes=30, seconds=15)
self.assertEqual(result, expected_time)

View File

@@ -1,16 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет описания тестов для классов исключений
"""
from .bad_request_exception_tests import BadRequestExceptionTests
from .critical_exception_tests import CriticalExceptionTests
from .file_not_found_exception_tests import FileNotFoundExceptionTests
from .forbidden_exception_tests import ForbiddenExceptionTests
from .not_found_exception_tests import NotFoundExceptionTests
from .unauthorized_exception_tests import UnauthorizedExceptionTests

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса BadRequestExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import BadRequestException
class BadRequestExceptionTests(SimpleTestCase):
"""
Тесты для BadRequestException
"""
def test_init_with_defaults(self):
"""
Проверяет инициализацию по умолчанию (без параметров)
"""
exc = BadRequestException()
self.assertEqual(exc.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(exc.detail, "rack.errors.http.bad_request")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_false(self):
"""
Гарантирует, что ошибка возвращает False для чистого HTTP статуса
"""
exc = BadRequestException()
self.assertFalse(exc.is_pure_status())

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса CriticalExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import CriticalException
class CriticalExceptionTests(SimpleTestCase):
"""
Тесты для CriticalException
"""
def test_init_with_defaults(self):
"""
Проверяет дефолтные параметры: статус и сообщение об ошибке
"""
exc = CriticalException()
self.assertEqual(exc.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
self.assertEqual(exc.detail, "rack.errors.http.internal_server_error")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_true(self):
"""
Гарантирует, что ошибка возвращает True для чистого HTTP статуса
"""
exc = CriticalException()
self.assertTrue(exc.is_pure_status())

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса FileNotFoundExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import FileNotFoundException
class FileNotFoundExceptionTests(SimpleTestCase):
"""
Тесты для FileNotFoundException
"""
def test_init_with_defaults(self):
"""
Проверяет дефолтные параметры: статус и сообщение об ошибке
"""
exc = FileNotFoundException()
self.assertEqual(exc.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(exc.detail, "rack.errors.http.not_found")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_true(self):
"""
Гарантирует, что ошибка возвращает True для чистого HTTP статуса
"""
exc = FileNotFoundException()
self.assertTrue(exc.is_pure_status())

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса ForbiddenExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import ForbiddenException
class ForbiddenExceptionTests(SimpleTestCase):
"""
Тесты для BadRequestException
"""
def test_init_with_defaults(self):
"""
Проверяет инициализацию по умолчанию (без параметров)
"""
exc = ForbiddenException()
self.assertEqual(exc.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(exc.detail, "rack.errors.http.forbidden")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_false(self):
"""
Гарантирует, что ошибка возвращает False для чистого HTTP статуса
"""
exc = ForbiddenException()
self.assertFalse(exc.is_pure_status())

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса NotFoundExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import NotFoundException
class NotFoundExceptionTests(SimpleTestCase):
"""
Тесты для NotFoundException
"""
def test_init_with_defaults(self):
"""
Проверяет дефолтные параметры: статус и сообщение об ошибке
"""
exc = NotFoundException()
self.assertEqual(exc.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(exc.detail, "rack.errors.http.not_found")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_false(self):
"""
Гарантирует, что ошибка возвращает False для чистого HTTP статуса
"""
exc = NotFoundException()
self.assertFalse(exc.is_pure_status())

View File

@@ -1,40 +0,0 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса UnauthorizedExceptionTests
"""
from django.test import SimpleTestCase
from rest_framework import status
from net.xeaf.rack.utils.exceptions import UnauthorizedException
class UnauthorizedExceptionTests(SimpleTestCase):
"""
Тесты для UnauthorizedException
"""
def test_init_with_defaults(self):
"""
Проверяет дефолтные параметры: статус и сообщение об ошибке
"""
exc = UnauthorizedException()
self.assertEqual(exc.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(exc.detail, "rack.errors.http.unauthorized")
self.assertIsNone(exc.meta)
def test_is_pure_status_returns_false(self):
"""
Гарантирует, что ошибка возвращает False для чистого HTTP статуса
"""
exc = UnauthorizedException()
self.assertFalse(exc.is_pure_status())