Files
core/tests/test_getSession_cookies.py
Untone 1b48675b92
Some checks failed
Deploy on push / deploy (push) Failing after 2m22s
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`

### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода

### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки

### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации

### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00

277 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Тест для проверки работы getSession с cookies
Проверяет:
1. getSession работает без токена в заголовке, но с валидным cookie
2. getSession возвращает данные пользователя при валидном cookie
3. getSession возвращает ошибку при невалидном cookie
4. getSession работает с токеном в заголовке
"""
import pytest
from unittest.mock import patch, MagicMock
from graphql import GraphQLResolveInfo
from resolvers.auth import get_session
from auth.tokens.storage import TokenStorage as TokenManager
from orm.author import Author
class MockRequest:
"""Мок для Request объекта"""
def __init__(self, headers=None, cookies=None):
self.headers = headers or {}
self.cookies = cookies or {}
class MockContext:
"""Мок для GraphQL контекста"""
def __init__(self, request=None):
self.request = request
def get(self, key, default=None):
"""Мокаем метод get для совместимости с DRY функциями"""
if key == "request":
return self.request
return default
class MockGraphQLResolveInfo:
"""Мок для GraphQLResolveInfo"""
def __init__(self, context):
self.context = context
@pytest.fixture
def mock_author():
"""Мок для объекта Author"""
author = MagicMock(spec=Author)
author.id = 123
author.email = "test@example.com"
author.name = "Test User"
author.slug = "test-user"
author.username = "testuser"
# Мокаем метод dict()
author.dict.return_value = {
"id": 123,
"email": "test@example.com",
"name": "Test User",
"slug": "test-user",
"username": "testuser"
}
return author
@pytest.fixture
def mock_payload():
"""Мок для payload токена"""
payload = MagicMock()
payload.user_id = "123"
return payload
@pytest.mark.asyncio
async def test_getSession_with_valid_cookie(mock_author, mock_payload):
"""Тест getSession с валидным cookie"""
# Мокаем request с cookie
request = MockRequest(
headers={},
cookies={"session_token": "valid_token_123"}
)
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token, \
patch('resolvers.auth.get_user_data_by_token') as mock_get_user_data:
mock_get_token.return_value = "valid_token_123"
mock_get_user_data.return_value = (True, mock_author.dict(), None)
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is True
assert result["token"] == "valid_token_123"
assert result["author"]["id"] == 123
assert result["author"]["email"] == "test@example.com"
assert result["error"] is None
# Проверяем вызовы DRY функций
mock_get_token.assert_called_once_with(info)
mock_get_user_data.assert_called_once_with("valid_token_123")
@pytest.mark.asyncio
async def test_getSession_with_authorization_header(mock_author, mock_payload):
"""Тест getSession с заголовком Authorization"""
# Мокаем request с заголовком Authorization
request = MockRequest(
headers={"authorization": "Bearer bearer_token_456"},
cookies={}
)
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token, \
patch('resolvers.auth.get_user_data_by_token') as mock_get_user_data:
mock_get_token.return_value = "bearer_token_456"
mock_get_user_data.return_value = (True, mock_author.dict(), None)
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is True
assert result["token"] == "bearer_token_456"
assert result["author"]["id"] == 123
assert result["error"] is None
# Проверяем вызовы DRY функций
mock_get_token.assert_called_once_with(info)
mock_get_user_data.assert_called_once_with("bearer_token_456")
@pytest.mark.asyncio
async def test_getSession_with_invalid_token(mock_author):
"""Тест getSession с невалидным токеном"""
# Мокаем request с невалидным cookie
request = MockRequest(
headers={},
cookies={"session_token": "invalid_token"}
)
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token, \
patch('resolvers.auth.get_user_data_by_token') as mock_get_user_data:
mock_get_token.return_value = "invalid_token"
mock_get_user_data.return_value = (False, None, "Сессия не найдена")
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is False
assert result["token"] is None
assert result["author"] is None
assert result["error"] == "Сессия не найдена"
@pytest.mark.asyncio
async def test_getSession_without_token():
"""Тест getSession без токена"""
# Мокаем request без токена
request = MockRequest(headers={}, cookies={})
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token:
mock_get_token.return_value = None
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is False
assert result["token"] is None
assert result["author"] is None
assert result["error"] == "Сессия не найдена"
@pytest.mark.asyncio
async def test_getSession_without_request():
"""Тест getSession без request в контексте"""
# Мокаем контекст без request
context = MockContext(request=None)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token:
mock_get_token.return_value = None
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is False
assert result["token"] is None
assert result["author"] is None
assert result["error"] == "Сессия не найдена"
@pytest.mark.asyncio
async def test_getSession_user_not_found(mock_payload):
"""Тест getSession когда пользователь не найден в БД"""
# Мокаем request с валидным cookie
request = MockRequest(
headers={},
cookies={"session_token": "valid_token_123"}
)
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token, \
patch('resolvers.auth.get_user_data_by_token') as mock_get_user_data:
mock_get_token.return_value = "valid_token_123"
mock_get_user_data.return_value = (False, None, f"Пользователь с ID 123 не найден в БД")
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is False
assert result["token"] is None
assert result["author"] is None
assert result["error"] == "Пользователь с ID 123 не найден в БД"
@pytest.mark.asyncio
async def test_getSession_payload_without_user_id():
"""Тест getSession когда payload не содержит user_id"""
# Мокаем request с валидным cookie
request = MockRequest(
headers={},
cookies={"session_token": "valid_token_123"}
)
context = MockContext(request)
info = MockGraphQLResolveInfo(context)
# Мокаем DRY функции из auth/utils.py
with patch('resolvers.auth.get_auth_token_from_context') as mock_get_token, \
patch('resolvers.auth.get_user_data_by_token') as mock_get_user_data:
mock_get_token.return_value = "valid_token_123"
mock_get_user_data.return_value = (False, None, "Токен не содержит user_id")
result = await get_session(None, info)
# Проверяем результат
assert result["success"] is False
assert result["token"] is None
assert result["author"] is None
assert result["error"] == "Токен не содержит user_id"
if __name__ == "__main__":
pytest.main([__file__, "-v"])