Добавлены тесты для CoreResponse
This commit is contained in:
@@ -41,7 +41,7 @@ class CoreResponse(Response):
|
|||||||
result["detail"] = data
|
result["detail"] = data
|
||||||
|
|
||||||
# Набор дополнительных данных
|
# Набор дополнительных данных
|
||||||
if meta:
|
if meta is not None:
|
||||||
result["meta"] = meta
|
result["meta"] = meta
|
||||||
|
|
||||||
# Формируем ответ
|
# Формируем ответ
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ from .core_enum_choices_tests import CoreEnumChoicesTests
|
|||||||
from .core_enum_from_string_tests import CoreEnumFromStringTests
|
from .core_enum_from_string_tests import CoreEnumFromStringTests
|
||||||
from .core_enum_keys_tests import CoreEnumKeysTests
|
from .core_enum_keys_tests import CoreEnumKeysTests
|
||||||
from .core_enum_values_tests import CoreEnumValuesTests
|
from .core_enum_values_tests import CoreEnumValuesTests
|
||||||
|
from .core_response_tests import CoreResponseTests
|
||||||
|
|||||||
307
net/xeaf/rack/tests/core/core_response_tests.py
Normal file
307
net/xeaf/rack/tests/core/core_response_tests.py
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreResponseTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreResponse
|
||||||
|
from net.xeaf.rack.core.testing import CoreSimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class CoreResponseTests(CoreSimpleTestCase):
|
||||||
|
"""
|
||||||
|
Тестовый класс для метода CoreResponse.__init__()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_init_with_success_status_and_data(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с успешным статусом и данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = {"key": "value", "list": [1, 2, 3]}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertIn("timestamp", response.data)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
self.assertNotIn("meta", response.data)
|
||||||
|
|
||||||
|
def test_init_with_success_status_and_string_data(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с успешным статусом и строковыми данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_201_CREATED
|
||||||
|
external_status = status.HTTP_201_CREATED
|
||||||
|
data = "Operation completed successfully"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
|
||||||
|
def test_init_with_success_status_and_bool_data(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с успешным статусом и булевыми данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = True
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
|
||||||
|
def test_init_with_success_status_and_none_data(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с успешным статусом и None в качестве данных
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_204_NO_CONTENT
|
||||||
|
external_status = status.HTTP_204_NO_CONTENT
|
||||||
|
data = None
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
|
||||||
|
def test_init_with_error_status_and_detail(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с ошибочным статусом и деталями ошибки
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_400_BAD_REQUEST
|
||||||
|
external_status = status.HTTP_400_BAD_REQUEST
|
||||||
|
data = "Invalid request parameters"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["detail"], data)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
|
||||||
|
def test_init_with_error_status_and_dict_detail(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с ошибочным статусом и деталями в виде словаря
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||||
|
external_status = status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||||
|
data = {"field": "email", "error": "Invalid email format"}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["detail"], data)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
|
||||||
|
def test_init_with_server_error_status_and_detail(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с серверной ошибкой и деталями
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
external_status = status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
data = "Internal server error occurred"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["detail"], data)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
|
||||||
|
def test_init_with_meta_data(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с дополнительными мета-данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = {"id": 123}
|
||||||
|
meta = {"page": 1, "total": 100, "per_page": 10}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data, meta)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertEqual(response.data["meta"], meta)
|
||||||
|
|
||||||
|
def test_init_with_error_status_and_meta(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с ошибочным статусом и мета-данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_404_NOT_FOUND
|
||||||
|
external_status = status.HTTP_404_NOT_FOUND
|
||||||
|
data = "Resource not found"
|
||||||
|
meta = {"resource_type": "user", "requested_id": 999}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data, meta)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["detail"], data)
|
||||||
|
self.assertEqual(response.data["meta"], meta)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
|
||||||
|
def test_init_with_empty_meta(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с пустыми мета-данными
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = {"id": 123}
|
||||||
|
meta = {}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data, meta)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertEqual(response.data["meta"], {})
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
|
||||||
|
def test_init_timestamp_is_integer(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что timestamp является целым числом
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = "test"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertIn("timestamp", response.data)
|
||||||
|
self.assertIsInstance(response.data["timestamp"], int)
|
||||||
|
self.assertGreater(response.data["timestamp"], 0)
|
||||||
|
|
||||||
|
def test_init_with_different_external_status(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с отличающимися внутренним и внешним статусами
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_201_CREATED
|
||||||
|
data = {"created": True}
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertNotEqual(response.status_code, internal_status)
|
||||||
|
|
||||||
|
def test_init_with_boundary_status_values(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет инициализацию ответа с граничными значениями статусов
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange - тестируем границу между успехом и ошибкой (400)
|
||||||
|
internal_status = 399
|
||||||
|
external_status = 399
|
||||||
|
data = "Redirect"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert - на уровне 399 это все еще считается успешным (менее 400)
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["data"], data)
|
||||||
|
self.assertNotIn("detail", response.data)
|
||||||
|
|
||||||
|
# Arrange - тестируем границу ошибок (400)
|
||||||
|
internal_status = status.HTTP_400_BAD_REQUEST
|
||||||
|
external_status = status.HTTP_400_BAD_REQUEST
|
||||||
|
data = "Bad Request"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert - на уровне 400 это считается ошибкой
|
||||||
|
self.assertEqual(response.status_code, external_status)
|
||||||
|
self.assertEqual(response.data["status"], internal_status)
|
||||||
|
self.assertEqual(response.data["detail"], data)
|
||||||
|
self.assertNotIn("data", response.data)
|
||||||
|
|
||||||
|
def test_init_inherits_from_drf_response(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что CoreResponse наследуется от DRF Response
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
internal_status = status.HTTP_200_OK
|
||||||
|
external_status = status.HTTP_200_OK
|
||||||
|
data = "test"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
response = CoreResponse(internal_status, external_status, data)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertIsInstance(response, Response)
|
||||||
|
self.assertTrue(hasattr(response, "data"))
|
||||||
|
self.assertTrue(hasattr(response, "status_code"))
|
||||||
|
self.assertTrue(hasattr(response, "render"))
|
||||||
@@ -22,13 +22,14 @@
|
|||||||
Правила для файла тестов:
|
Правила для файла тестов:
|
||||||
1. Изоляция: Один метод = Один тестовый класс = Один отдельный файл.
|
1. Изоляция: Один метод = Один тестовый класс = Один отдельный файл.
|
||||||
2. Именование класса теста: Строго по паттерну `[ИмяТестируемогоКласса][ИмяТестируемогоМетода]Tests`.
|
2. Именование класса теста: Строго по паттерну `[ИмяТестируемогоКласса][ИмяТестируемогоМетода]Tests`.
|
||||||
|
- СТРОГОЕ ИСКЛЮЧЕНИЕ: Если тестируется конструктор `__init__()`, то имя метода НЕ должно включаться в название класса. Класс должен называться просто `[ИмяТестируемогоКласса]Tests`. Пример: Для `DemoClass.__init__()` тестовый класс называется `DemoClassTests`.
|
||||||
3. Зеркалирование структуры пакетов: Тесты находятся в пакете `net.xeaf.rack.tests`. Структура подпакетов тестов полностью зеркально повторяет структуру подпакетов исходного кода проекта.
|
3. Зеркалирование структуры пакетов: Тесты находятся в пакете `net.xeaf.rack.tests`. Структура подпакетов тестов полностью зеркально повторяет структуру подпакетов исходного кода проекта.
|
||||||
4. Выбор базового класса (Предка):
|
4. Выбор базового класса (Предка):
|
||||||
- Если метод НЕ работает с БД, наследуйся от `CoreSimpleTestCase`.
|
- Если метод НЕ работает с БД, наследуйся от `CoreSimpleTestCase`.
|
||||||
- Если метод работает с БД, наследуйся от `CoreUserStoryTestCase`.
|
- Если метод работает с БД, наследуйся от `CoreUserStoryTestCase`.
|
||||||
- Оба класса импортируются из пакета `net.xeaf.rack.core.testing`.
|
- Оба класса импортируются из пакета `net.xeaf.rack.core.testing`.
|
||||||
5. Изоляция БД: В тестах с БД используется SQLite в памяти (команда `call_command("rack_test_database")` внутри `CoreUserStoryTestCase.setUp()` автоматически сбрасывает и наполняет БД). База изолирована, данные можно изменять и удалять безболезненно.
|
5. Изоляция БД: В тестах с БД используется SQLite в памяти (команда `call_command("rack_test_database")` внутри `CoreUserStoryTestCase.setUp()` автоматически сбрасывает и наполняет БД). База изолирована, данные можно изменять и удалять безболезненно.
|
||||||
6. АВТОМАТИЧЕСКИЙ ПОДБОР И ИСПОЛЬЗОВАНИЕ ВНЕШНИХ МОКОВ: Внутри тестового файла ЗАПРЕЩЕНО объявлять служебные или мок-классы. Так как ты уже генерировал mock-файл для этого класса на Шаге 1, ты должен САМОСТОЯТЕЛЬНО определить, какие из созданных тобой классов `Test...` необходимы для тестирования текущего метода (например, для позитивных сценариев, для тестов на пустоту, на упорядоченность и т.д.). Сформируй имя файла моков как `._[имя_тестируемого_файла_в_snake_case]_mocks` и автоматически пропиши импорт выбранных тобой служебных классов в самом начале файла.
|
6. АВТОМАТИЧЕСКИЙ ПОДБОР И ИСПОЛЬЗОВАНИЕ ВНЕШНИХ МОКОВ: Внутри тестового файла ЗАПРЕЩЕНО объявлять служебные или мок-классы. Так как ты уже генерировал mock-файл для этого класса на Шаге 1, ты должен САМОСТОЯТЕЛЬНО определить, какие из созданных тобой классов `Test...` необходимы для тестирования текущего метода. Сформируй имя файла моков как `._[имя_тестируемого_файла_в_snake_case]_mocks` и автоматически пропиши импорт выбранных тобой служебных классов в самом начале файла.
|
||||||
|
|
||||||
### ТРЕБОВАНИЯ К ЯЗЫКУ, ОФОРМЛЕНИЮ И СТИЛЮ КОДА (ДЛЯ ВСЕХ РЕЖИМОВ)
|
### ТРЕБОВАНИЯ К ЯЗЫКУ, ОФОРМЛЕНИЮ И СТИЛЮ КОДА (ДЛЯ ВСЕХ РЕЖИМОВ)
|
||||||
- Все комментарии и docstrings пишутся строго на русском языке.
|
- Все комментарии и docstrings пишутся строго на русском языке.
|
||||||
@@ -39,12 +40,13 @@
|
|||||||
Тестовый класс для DemoClass.demo_method()
|
Тестовый класс для DemoClass.demo_method()
|
||||||
"""
|
"""
|
||||||
```
|
```
|
||||||
|
Для конструктора паттерн аналогичен: `Тестовый класс для DemoClass.__init__()`.
|
||||||
- Оформление тестовых методов: Докстринг внутри каждого тестового метода должен быть многострочным, оформленным с пустыми строками до и после текста.
|
- Оформление тестовых методов: Докстринг внутри каждого тестового метода должен быть многострочным, оформленным с пустыми строками до и после текста.
|
||||||
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты (включая автоматический импорт только нужных для этого метода классов `Test...` из локального файла моков) должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
||||||
- Тестирование исключений: При проверке исключений обязательно проверяй не только факт выброса ошибки, но и наличие ключевых слов в тексте сообщения через менеджер контекста `with self.assertRaises(ValueError) as context:`.
|
- Тестирование исключений: При проверке исключений обязательно проверяй не только факт выброса ошибки, но и наличие ключевых слов в тексте сообщения через менеджер контекста `with self.assertRaises(ValueError) as context:`.
|
||||||
- Формат ответа: Возвращай ТОЛЬКО чистый код на Python внутри одного блока разметки ```python. Без вводных фраз и пояснений после кода.
|
- Формат ответа: Возвращай ТОЛЬКО чистый код на Python внутри одного блока разметки ```python. Без вводных фраз и пояснений после кода.
|
||||||
|
|
||||||
### АЛГОРИТМ ТВОЕЙ РАБОТЫ
|
### АЛГОРИТМ ТВОЕЙ РАБОТЫ
|
||||||
Пользователь пришлет тебе команду.
|
Пользователь пришлет тебе команду.
|
||||||
- Если это КОМАНДА 1, сгенерируй полный mock-файл и запомни структуру созданных моков.
|
- Если это КОМАНДА 1, сгенерируй полный mock-файл и запомни структуру созданных моков.
|
||||||
- Если это КОМАНДА 2, проанализируй целевой метод, вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс по методологии AAA.
|
- Если это КОМАНДА 2, проанализируй целевой метод (с учетом исключения для `__init__`), вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс по методологии AAA.
|
||||||
|
|||||||
Reference in New Issue
Block a user