From 3fba091cbc7cde2cadf15ee4ec6aff07b5eb1a29 Mon Sep 17 00:00:00 2001 From: "Nick V. Anokhin" Date: Sat, 11 Jul 2026 18:34:54 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20CoreException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- net/xeaf/rack/core/__init__.py | 1 + net/xeaf/rack/core/core_exception.py | 59 ++++++++ net/xeaf/rack/tests/core/__init__.py | 2 +- .../rack/tests/core/core_exception_tests.py | 133 ++++++++++++++++++ .../tests/managers/crypto_manager_tests.py | 2 +- 5 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 net/xeaf/rack/core/core_exception.py create mode 100644 net/xeaf/rack/tests/core/core_exception_tests.py diff --git a/net/xeaf/rack/core/__init__.py b/net/xeaf/rack/core/__init__.py index cb614a1..42e6677 100644 --- a/net/xeaf/rack/core/__init__.py +++ b/net/xeaf/rack/core/__init__.py @@ -9,4 +9,5 @@ """ from .core_enum import CoreEnum +from .core_exception import CoreException from .core_test_case import CoreTestCase diff --git a/net/xeaf/rack/core/core_exception.py b/net/xeaf/rack/core/core_exception.py new file mode 100644 index 0000000..c24e714 --- /dev/null +++ b/net/xeaf/rack/core/core_exception.py @@ -0,0 +1,59 @@ +# DRF Rack +# Библиотека классов расширений для Django REST Framework +# +# Автор: Николай В. Анохин +# Все права защищены. Лицензия: MIT + +""" +Описание класса CoreException +""" + +from typing import Any + + +class CoreException(Exception): + """ + Реализует базовый функционал всех классов исключений проекта + """ + + status_code: int + """ Код состояния ответа API """ + + detail: str | dict | list | None = None + """ Информация об ошибке """ + + meta: dict | None = None + """ Информация для обработки ошибки """ + + def __init__(self, status_code: int, detail: str | dict | list | None = None, meta: dict | None = None, **kwargs: Any): + """ + Инициализация + + :param status_code: Код состояния ответа API + :param detail: Информация об ошибке + :param meta: Информация для обработки ошибки + :param kwargs: Дополнительные параметры + """ + + self.status_code = status_code + self.detail = detail + self._process_meta(meta, **kwargs) + super().__init__() + + def _process_meta(self, meta: dict | None, **kwargs: Any) -> None: + """ + Разбирает переданные параметры + + :param meta: Метаданные + :param kwargs: Дополнительные аргументы + """ + + # Метаданные + if meta is not None: + self.meta = meta + elif kwargs: + self.meta = {} + + # Дополнительные параметры + if kwargs and self.meta is not None: + self.meta.update(kwargs) diff --git a/net/xeaf/rack/tests/core/__init__.py b/net/xeaf/rack/tests/core/__init__.py index 5a6a1f4..29ccbb3 100644 --- a/net/xeaf/rack/tests/core/__init__.py +++ b/net/xeaf/rack/tests/core/__init__.py @@ -9,4 +9,4 @@ """ from .core_enum_tests import CoreEnumTests - +from .core_exception_tests import CoreExceptionTests diff --git a/net/xeaf/rack/tests/core/core_exception_tests.py b/net/xeaf/rack/tests/core/core_exception_tests.py new file mode 100644 index 0000000..466c275 --- /dev/null +++ b/net/xeaf/rack/tests/core/core_exception_tests.py @@ -0,0 +1,133 @@ +# DRF Rack +# Библиотека классов расширений для Django REST Framework +# +# Автор: Николай В. Анохин +# Все права защищены. Лицензия: MIT + +""" +Описание класса CoreExceptionTests +""" + +from django.test import SimpleTestCase +from rest_framework import status + +from net.xeaf.rack.core import CoreException + + +class CoreExceptionTests(SimpleTestCase): + """ + Тесты для класса CoreException + """ + + def setUp(self): + """ + Заполняем параметры для теста + """ + + self.default_status = status.HTTP_400_BAD_REQUEST + self.default_detail = "Тестовая ошибка" + self.exc_instance = CoreException(status_code=self.default_status) + + def test_init_with_basic_params(self): + """ + Проверяет базовую инициализацию без метаданных + """ + + exc = CoreException(status_code=self.default_status, detail=self.default_detail) + + self.assertEqual(exc.status_code, self.default_status) + self.assertEqual(exc.detail, self.default_detail) + self.assertIsNone(exc.meta) + + def test_init_with_meta_only(self): + """ + Проверяет инициализацию, когда передан только словарь meta + """ + + meta_data = {"action": "REDIRECT", "url": "/login/"} + + exc = CoreException(status_code=self.default_status, meta=meta_data) + + self.assertEqual(exc.status_code, self.default_status) + self.assertEqual(exc.meta, meta_data) + self.assertIsInstance(exc.meta, dict) + self.assertEqual(exc.meta["action"], "REDIRECT") + + def test_init_with_kwargs_only(self): + """ + Проверяет инициализацию, когда meta=None, но переданы именованные аргументы (kwargs) + """ + + exc = CoreException(status_code=self.default_status, foo="bar", code=123) + + self.assertEqual(exc.status_code, self.default_status) + self.assertIsNotNone(exc.meta) + self.assertIsInstance(exc.meta, dict) + self.assertEqual(exc.meta["foo"], "bar") + self.assertEqual(exc.meta["code"], 123) + + def test_process_meta_with_none(self): + """ + Если meta=None и kwargs пусты, self.meta должен остаться None + """ + + self.exc_instance.meta = None # Сбрасываем состояние перед проверкой + + self.exc_instance._process_meta(meta=None) + + self.assertIsNone(self.exc_instance.meta) + + def test_process_meta_with_dict(self): + """ + Если передан только словарь meta, он должен записаться в self.meta + """ + + self.exc_instance.meta = None + test_meta = {"source": "auth_service"} + + self.exc_instance._process_meta(meta=test_meta) + + self.assertEqual(self.exc_instance.meta, test_meta) + + def test_process_meta_with_kwargs_only(self): + """ + Если meta=None, но есть kwargs, должен создаться словарь с этими kwargs + """ + + self.exc_instance.meta = None + + self.exc_instance._process_meta(meta=None, request_id="req_123", user_id=99) + + self.assertIsNotNone(self.exc_instance.meta) + self.assertIsInstance(self.exc_instance.meta, dict) + self.assertEqual(self.exc_instance.meta["request_id"], "req_123") + self.assertEqual(self.exc_instance.meta["user_id"], 99) + + def test_process_meta_combined(self): + """ + Если переданы и meta, и kwargs, они должны успешно объединиться (update) + """ + + self.exc_instance.meta = None + initial_meta = {"version": "v1"} + + self.exc_instance._process_meta(meta=initial_meta, debug=True, env="prod") + + self.assertIsNotNone(self.exc_instance.meta) + self.assertIsInstance(self.exc_instance.meta, dict) + self.assertEqual(self.exc_instance.meta["version"], "v1") + self.assertEqual(self.exc_instance.meta["debug"], True) + self.assertEqual(self.exc_instance.meta["env"], "prod") + + def test_process_meta_with_empty_dict_and_kwargs(self): + """ + Критический случай: meta пришел пустым словарем, но kwargs заполнены + """ + + self.exc_instance.meta = None + + self.exc_instance._process_meta(meta={}, trace_id="xyz") + + self.assertIsNotNone(self.exc_instance.meta) + self.assertIsInstance(self.exc_instance.meta, dict) + self.assertEqual(self.exc_instance.meta["trace_id"], "xyz") diff --git a/net/xeaf/rack/tests/managers/crypto_manager_tests.py b/net/xeaf/rack/tests/managers/crypto_manager_tests.py index beb50c6..70f803e 100644 --- a/net/xeaf/rack/tests/managers/crypto_manager_tests.py +++ b/net/xeaf/rack/tests/managers/crypto_manager_tests.py @@ -14,7 +14,7 @@ from net.xeaf.rack.managers import CryptoManager class CryptoManagerTests(CoreTestCase): """ - Тесты для класса Crypto + Тесты для класса CryptoManager """ def test_gen_token(self):