From cc57591e66fa910f290cb20ff9ccbf34f50f6e63 Mon Sep 17 00:00:00 2001 From: "Nick V. Anokhin" Date: Sat, 18 Jul 2026 11:50:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=BD=D1=81=D0=BF=D0=B5=D0=BA=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- net/xeaf/rack/core/testing/core_test_case.py | 260 ------------------ .../core/testing/core_user_story_test_case.py | 12 +- .../rack/management/commands/rack_coverage.py | 4 +- .../management/commands/rack_test_database.py | 4 +- .../rack/models/mixins/expired_at_mixin.py | 1 + .../user_story}/__init__.py | 2 +- .../user_story}/account_test_data.py | 0 .../user_story}/session_test_data.py | 0 net/xeaf/rack/userstory/__init__.py | 0 .../auth/expiring_token_authentication.py | 4 +- net/xeaf/rack/utils/management/__init__.py | 12 + .../management/coverage_structure_visitor.py} | 0 .../management/coverage_test_file_visitor.py} | 0 13 files changed, 27 insertions(+), 272 deletions(-) delete mode 100644 net/xeaf/rack/core/testing/core_test_case.py rename net/xeaf/rack/{management/userstory => models/user_story}/__init__.py (74%) rename net/xeaf/rack/{management/userstory => models/user_story}/account_test_data.py (100%) rename net/xeaf/rack/{management/userstory => models/user_story}/session_test_data.py (100%) create mode 100644 net/xeaf/rack/userstory/__init__.py create mode 100644 net/xeaf/rack/utils/management/__init__.py rename net/xeaf/rack/{management/commands/_coverage_structure_visitor.py => utils/management/coverage_structure_visitor.py} (100%) rename net/xeaf/rack/{management/commands/_coverage_test_file_visitor.py => utils/management/coverage_test_file_visitor.py} (100%) diff --git a/net/xeaf/rack/core/testing/core_test_case.py b/net/xeaf/rack/core/testing/core_test_case.py deleted file mode 100644 index c3060dd..0000000 --- a/net/xeaf/rack/core/testing/core_test_case.py +++ /dev/null @@ -1,260 +0,0 @@ -# DRF Rack -# Библиотека классов расширений для Django REST Framework -# -# Автор: Николай В. Анохин -# Все права защищены. Лицензия: MIT - -""" -Описание класса CoreTestCase -""" - -import json -from typing import Any - -from django.core.management import call_command -from django.test import TestCase -from rest_framework.parsers import JSONParser -from rest_framework.request import Request -from rest_framework.test import APIRequestFactory - -from net.xeaf.rack.enums import HttpMethod -from net.xeaf.rack.management.userstory import AccountTestData -from net.xeaf.rack.management.userstory import SessionTestData -from net.xeaf.rack.managers import SessionManager -from net.xeaf.rack.models import AccountModel -from net.xeaf.rack.models import SessionModel - - -class CoreTestCase(TestCase): - """ - Реализует инициализацию тестовой базы данных перед каждым тестом - """ - - factory: APIRequestFactory - """ Фабрика запросов """ - - def setUp(self): - """ - Подготовка к тестированию - """ - - call_command("rack_test_database") - self.factory = APIRequestFactory() - - def create_request(self, - method: HttpMethod, - path: str, - headers: dict[str, str] | None = None, - data: dict[str, Any] | None = None, - query_params: dict[str, str] | None = None, - auth: SessionModel | None = None) -> Request: - """ - Создает объект запроса для тестирования - - :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) - :param path: URL путь - :param headers: Заголовки запроса - :param data: Данные для тела запроса - :param query_params: Параметры запроса - :param auth: Объект аутентификации (сессия) - - :return: DRF Request объект - """ - - # Уточняем значения параметров - headers = self.__prepare_headers(headers or {}) - query_params = query_params or {} - data = data or {} - - # Создаем внутренний объект запроса - match method: - case HttpMethod.GET: - wsgi_request = self.factory.get(path, data=query_params, **headers) - case HttpMethod.POST: - wsgi_request = self.factory.post(path, data=data, format='json', **headers) - case HttpMethod.PUT: - wsgi_request = self.factory.put(path, data=data, format='json', **headers) - case HttpMethod.PATCH: - wsgi_request = self.factory.patch(path, data=data, format='json', **headers) - case HttpMethod.DELETE: - wsgi_request = self.factory.delete(path, **headers) - case _: - wsgi_request = self.factory.generic( - method, - path, - content_type='application/json', - data=json.dumps(data) if data else '' - ) - - # Преобразуем к объекту запроса DRF - drf_request = Request(wsgi_request, parsers=[JSONParser()]) - - if method != HttpMethod.GET: - self.__assign_query_params(drf_request, query_params) - self.__assign_auth(drf_request, auth) - - return drf_request - - def create_authenticated_request(self, - method: HttpMethod, - path: str, - headers: dict[str, str] | None = None, - data: dict[str, Any] | None = None, - query_params: dict[str, str] | None = None, - token: str | None = None) -> Request: - """ - Создает объект авторизованного запроса для тестирования - - :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) - :param path: URL путь - :param headers: Заголовки запроса - :param data: Данные для тела запроса - :param query_params: Параметры запроса - :param token: Токен сессии авторизации - - :return: DRF Request объект - """ - - # Получаем сессию - if token is None: - session = None - else: - session = SessionManager.find_by_token(token) - - # Возвращаем объект запроса - return self.create_request(method=method, path=path, headers=headers, data=data, query_params=query_params, auth=session) - - @classmethod - def __prepare_headers(cls, headers: dict[str, str]) -> dict[str, str]: - """ - Подготавливаем объект заголовков для передачи в фабрику - - :param headers: Заголовки запроса - - :return: Объект заголовков для фабрики - """ - - wsgi_headers = {} - for key, value in headers.items(): - if key.startswith('HTTP_'): - wsgi_headers[key] = value - else: - norm_key = key.upper().replace('-', '_') - if norm_key in ('CONTENT_TYPE', 'CONTENT_LENGTH'): - wsgi_headers[norm_key.lower()] = value - else: - wsgi_headers[f'HTTP_{norm_key}'] = value - - return wsgi_headers - - @classmethod - def __assign_query_params(cls, request: Request, query_params: dict[str, Any]) -> None: - """ - Присваивает параметры запроса в DRF Request объект - - :param request: DRF Request объект - :param query_params: Параметры запроса - """ - - # noinspection PyTypeChecker - request.query_params._mutable = True - for key, value in query_params.items(): - request.query_params[key] = value - request.query_params._mutable = False - - @classmethod - def __assign_auth(cls, request: Request, auth: SessionModel | None) -> None: - """ - Присваивает аутентификацию в DRF Request объект - - :param request: Объект запроса - :param auth: Объект сессии пользователя - """ - - if auth: - request.auth = auth - if hasattr(auth, 'account'): - request.user = auth.account - - @classmethod - def get_i_ivanov_account(cls) -> AccountModel: - """ - Возвращает аккаунт Ивана И. Иванова - - :return: Аккаунт Ивана И. Иванова - """ - - return AccountTestData.get_i_ivanov_account() - - @classmethod - def get_i_ivanov_session(cls) -> SessionModel: - """ - Возвращает сессию Ивана И. Иванова - - :return: Сессия Ивана И. Иванова - """ - - return SessionTestData.get_i_ivanov_session() - - def create_i_ivanov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: - """ - Создает запрос от имени Ивана И. Иванова - - :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) - :param path: URL путь - :param headers: Заголовки запроса - :param data: Данные для тела запроса - :param query_params: Параметры запроса - - :return: Объект запроса с аутентификацией Ивана И. Иванова - """ - - return self.create_authenticated_request( - method=method, - path=path, - headers=headers, - data=data, - query_params=query_params, - token=SessionTestData.I_IVANOV_SESSION_TOKEN, - ) - - @classmethod - def get_p_petrov_account(cls) -> AccountModel: - """ - Возвращает аккаунт Петра П. Петрова - - :return: Аккаунт Петра П. Петрова - """ - return AccountTestData.get_p_petrov_account() - - @classmethod - def get_p_petrov_session(cls) -> SessionModel: - """ - Возвращает сессию Петра П. Петрова - - :return: Сессия Петра П. Петрова - """ - - return SessionTestData.get_p_petrov_session() - - def create_p_petrov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: - """ - Создает запрос от имени Петра П. Петрова - - :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) - :param path: URL путь - :param headers: Заголовки запроса - :param data: Данные для тела запроса - :param query_params: Параметры запроса - - :return: Объект запроса с аутентификацией Петра П. Петрова - """ - - return self.create_authenticated_request( - method=method, - path=path, - headers=headers, - data=data, - query_params=query_params, - token=SessionTestData.P_PETROV_SESSION_TOKEN - ) diff --git a/net/xeaf/rack/core/testing/core_user_story_test_case.py b/net/xeaf/rack/core/testing/core_user_story_test_case.py index 9c1f6fe..83ac565 100644 --- a/net/xeaf/rack/core/testing/core_user_story_test_case.py +++ b/net/xeaf/rack/core/testing/core_user_story_test_case.py @@ -16,7 +16,7 @@ from rest_framework.request import Request from rest_framework.test import APIRequestFactory from net.xeaf.rack.enums import HttpMethod -from net.xeaf.rack.management.userstory import SessionTestData +from net.xeaf.rack.models.user_story import SessionTestData from .request_test_case_mixin import RequestTestCaseMixin @@ -33,9 +33,10 @@ class CoreUserStoryTestCase(TestCase, RequestTestCaseMixin): call_command("rack_test_database") self.factory = APIRequestFactory() - def create_i_ivanov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: + def create_ivanov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, + data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: """ - Создает запрос от имени Ивана И. Иванова + Создает запрос от имени Ивана Иванова :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) :param path: URL путь @@ -55,9 +56,10 @@ class CoreUserStoryTestCase(TestCase, RequestTestCaseMixin): token=SessionTestData.IVANOV_SESSION_TOKEN, ) - def create_p_petrov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: + def create_petrov_request(self, method: HttpMethod, path: str, headers: dict[str, str] | None = None, + data: dict[str, Any] | None = None, query_params: dict[str, Any] | None = None) -> Request: """ - Создает запрос от имени Петра П. Петрова + Создает запрос от имени Петра Петрова :param method: HTTP метод (GET, POST, PUT, PATCH, DELETE) :param path: URL путь diff --git a/net/xeaf/rack/management/commands/rack_coverage.py b/net/xeaf/rack/management/commands/rack_coverage.py index 358420f..4ba78fc 100644 --- a/net/xeaf/rack/management/commands/rack_coverage.py +++ b/net/xeaf/rack/management/commands/rack_coverage.py @@ -21,8 +21,8 @@ from django.conf import settings from django.core.management import CommandError from net.xeaf.rack.core import CoreCommand -from ._coverage_structure_visitor import CoverageStructureVisitor -from ._coverage_test_file_visitor import CoverageTestFileVisitor +from net.xeaf.rack.utils.management import CoverageStructureVisitor +from net.xeaf.rack.utils.management import CoverageTestFileVisitor class Command(CoreCommand): diff --git a/net/xeaf/rack/management/commands/rack_test_database.py b/net/xeaf/rack/management/commands/rack_test_database.py index b53a2a7..9f2fb10 100644 --- a/net/xeaf/rack/management/commands/rack_test_database.py +++ b/net/xeaf/rack/management/commands/rack_test_database.py @@ -12,8 +12,8 @@ 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 +from net.xeaf.rack.models.user_story import AccountTestData +from net.xeaf.rack.models.user_story import SessionTestData class Command(CoreCommand): diff --git a/net/xeaf/rack/models/mixins/expired_at_mixin.py b/net/xeaf/rack/models/mixins/expired_at_mixin.py index de3456f..56ea338 100644 --- a/net/xeaf/rack/models/mixins/expired_at_mixin.py +++ b/net/xeaf/rack/models/mixins/expired_at_mixin.py @@ -7,6 +7,7 @@ """ Описание класса ExpiredAtMixin """ + from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ diff --git a/net/xeaf/rack/management/userstory/__init__.py b/net/xeaf/rack/models/user_story/__init__.py similarity index 74% rename from net/xeaf/rack/management/userstory/__init__.py rename to net/xeaf/rack/models/user_story/__init__.py index d5f29af..5b92118 100644 --- a/net/xeaf/rack/management/userstory/__init__.py +++ b/net/xeaf/rack/models/user_story/__init__.py @@ -5,7 +5,7 @@ # Все права защищены. Лицензия: MIT """ -Пакет классов создания тестовой БД +Пакет описания классов инициализации моделей тестовых данных """ from .account_test_data import AccountTestData diff --git a/net/xeaf/rack/management/userstory/account_test_data.py b/net/xeaf/rack/models/user_story/account_test_data.py similarity index 100% rename from net/xeaf/rack/management/userstory/account_test_data.py rename to net/xeaf/rack/models/user_story/account_test_data.py diff --git a/net/xeaf/rack/management/userstory/session_test_data.py b/net/xeaf/rack/models/user_story/session_test_data.py similarity index 100% rename from net/xeaf/rack/management/userstory/session_test_data.py rename to net/xeaf/rack/models/user_story/session_test_data.py diff --git a/net/xeaf/rack/userstory/__init__.py b/net/xeaf/rack/userstory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/net/xeaf/rack/utils/auth/expiring_token_authentication.py b/net/xeaf/rack/utils/auth/expiring_token_authentication.py index a17ef28..c73dc7d 100644 --- a/net/xeaf/rack/utils/auth/expiring_token_authentication.py +++ b/net/xeaf/rack/utils/auth/expiring_token_authentication.py @@ -28,7 +28,7 @@ class ExpiringTokenAuthentication(TokenAuthentication): AUTH_TOKEN_PARAM = "x-auth-token" """ Идентификатор параметра токена """ - def authenticate(self, request: Request) -> tuple[AccountModel | None, CoreSessionModel | None] | tuple[Any, Any] | None: + def authenticate(self, request: Request) -> tuple[AccountModel | None, SessionModel | None] | tuple[Any, Any] | None: """ Метод авторизации @@ -43,7 +43,7 @@ class ExpiringTokenAuthentication(TokenAuthentication): return super().authenticate(request) - def authenticate_credentials(self, key: str) -> tuple[AccountModel | None, CoreSessionModel | None]: + def authenticate_credentials(self, key: str) -> tuple[AccountModel | None, SessionModel | None]: """ Проверка Токена авторизации diff --git a/net/xeaf/rack/utils/management/__init__.py b/net/xeaf/rack/utils/management/__init__.py new file mode 100644 index 0000000..a0b7e96 --- /dev/null +++ b/net/xeaf/rack/utils/management/__init__.py @@ -0,0 +1,12 @@ +# DRF Rack +# Библиотека классов расширений для Django REST Framework +# +# Автор: Николай В. Анохин +# Все права защищены. Лицензия: MIT + +""" +Пакет служебных классов для создания команд управления +""" + +from .coverage_structure_visitor import CoverageStructureVisitor +from .coverage_test_file_visitor import CoverageTestFileVisitor diff --git a/net/xeaf/rack/management/commands/_coverage_structure_visitor.py b/net/xeaf/rack/utils/management/coverage_structure_visitor.py similarity index 100% rename from net/xeaf/rack/management/commands/_coverage_structure_visitor.py rename to net/xeaf/rack/utils/management/coverage_structure_visitor.py diff --git a/net/xeaf/rack/management/commands/_coverage_test_file_visitor.py b/net/xeaf/rack/utils/management/coverage_test_file_visitor.py similarity index 100% rename from net/xeaf/rack/management/commands/_coverage_test_file_visitor.py rename to net/xeaf/rack/utils/management/coverage_test_file_visitor.py