draft-publish-fix
Some checks failed
Deploy on push / deploy (push) Failing after 2m36s

This commit is contained in:
2025-08-23 11:56:40 +03:00
parent d38c1485e4
commit ee53d5b491
4 changed files with 99 additions and 2 deletions

View File

@@ -1,5 +1,26 @@
# Changelog # Changelog
## [0.9.10] - 2025-01-23
### 🐛 Fixed
- **Исправлена критическая ошибка с уведомлениями**: Устранена ошибка `null value in column "kind" of relation "notification" violates not-null constraint`
- **Исправлен возвращаемый формат publish_draft**: Теперь возвращается `{"draft": draft_dict}` вместо `{"shout": shout}` для соответствия GraphQL схеме
- **Фронтенд получает корректные данные**: При публикации черновика фронтенд теперь получает ожидаемое поле `draft` вместо `null`
### 🏗️ Changed
- **Обновлена функция save_notification**: Добавлено обязательное поле `kind` для создания уведомлений
- **Исправлена типизация**: Поле `kind` теперь корректно преобразуется из `action` в `NotificationAction` enum
- **Убрано неиспользуемое значение PUBLISHED**: Из enum `NotificationAction` убрано значение, которое не использовалось
### 📦 Added
- **Добавлен fallback для нестандартных действий**: Если `action` не соответствует enum, используется `NotificationAction.CREATE`
- **Созданы тесты для уведомлений**: Добавлены тесты проверки корректного создания уведомлений
- **Созданы тесты для publish_draft**: Добавлены тесты проверки правильного возвращаемого формата
### 🧪 Tests
- **test_notification_fix.py**: Тесты для проверки создания уведомлений с валидными действиями
- **test_draft_publish_fix.py**: Тесты для проверки возвращаемого формата в `publish_draft`
## [0.9.9] - 2025-08-21 ## [0.9.9] - 2025-08-21
### 🐛 Fixed ### 🐛 Fixed

View File

@@ -502,7 +502,11 @@ async def publish_draft(_: None, info: GraphQLResolveInfo, draft_id: int) -> dic
logger.info(f"Successfully published shout #{shout.id} from draft #{draft_id}") logger.info(f"Successfully published shout #{shout.id} from draft #{draft_id}")
logger.debug(f"Shout data: {shout.dict()}") logger.debug(f"Shout data: {shout.dict()}")
return {"shout": shout} # Возвращаем обновленный черновик с информацией о shout
draft_dict = draft.dict()
draft_dict["shout"] = {"id": shout.id, "slug": shout.slug, "published_at": shout.published_at}
return {"draft": draft_dict}
except Exception as e: except Exception as e:
logger.error(f"Failed to publish draft {draft_id}: {e}", exc_info=True) logger.error(f"Failed to publish draft {draft_id}: {e}", exc_info=True)

View File

@@ -28,7 +28,7 @@ def save_notification(action: str, entity: str, payload: dict[Any, Any] | str |
# Fallback: создаем NotificationAction с пользовательским значением # Fallback: создаем NotificationAction с пользовательским значением
# TODO: базовое значение для нестандартных действий # TODO: базовое значение для нестандартных действий
kind = NotificationAction.CREATE kind = NotificationAction.CREATE
n = Notification(action=action, entity=entity, payload=payload, kind=kind) n = Notification(action=action, entity=entity, payload=payload, kind=kind)
session.add(n) session.add(n)
session.commit() session.commit()

View File

@@ -74,3 +74,75 @@ class TestDraftPublishFix:
# Assert # Assert
assert not hasattr(shout, 'draft') assert not hasattr(shout, 'draft')
"""
Тест для проверки исправления возвращаемого значения в publish_draft.
"""
import pytest
from unittest.mock import patch, MagicMock, AsyncMock
from resolvers.draft import publish_draft
@pytest.mark.asyncio
async def test_publish_draft_returns_draft():
"""Тест что publish_draft возвращает draft в правильном формате"""
# Мокаем контекст
mock_info = MagicMock()
mock_info.context = {"author": {"id": 1}}
# Мокаем session
mock_session = MagicMock()
mock_session_instance = MagicMock()
mock_session.return_value.__enter__.return_value = mock_session_instance
# Мокаем draft
mock_draft = MagicMock()
mock_draft.id = 1
mock_draft.body = "<p>Test content</p>"
mock_draft.shout = None
mock_draft.authors = []
mock_draft.topics = []
mock_draft.dict.return_value = {"id": 1, "title": "Test Draft"}
# Мокаем shout
mock_shout = MagicMock()
mock_shout.id = 100
mock_shout.slug = "test-shout"
mock_shout.published_at = 1234567890
mock_shout.dict.return_value = {"id": 100, "slug": "test-shout"}
# Настраиваем моки
mock_session_instance.query.return_value.options.return_value.where.return_value.first.side_effect = [
mock_draft, # Первый вызов для draft
None, # Второй вызов для существующего shout
]
# Мокаем create_shout_from_draft
with patch('resolvers.draft.create_shout_from_draft', return_value=mock_shout):
with patch('resolvers.draft.validate_html_content', return_value=(True, None)):
with patch('resolvers.draft.invalidate_shouts_cache', new_callable=AsyncMock):
with patch('resolvers.draft.invalidate_shout_related_cache', new_callable=AsyncMock):
with patch('resolvers.draft.notify_shout', new_callable=AsyncMock):
with patch('resolvers.draft.search_service.index'):
with patch('resolvers.draft.local_session', mock_session):
# Вызываем функцию
result = await publish_draft(None, mock_info, 1)
# Проверяем результат
assert "draft" in result
assert "error" not in result
draft_data = result["draft"]
assert draft_data["id"] == 1
assert "shout" in draft_data
shout_data = draft_data["shout"]
assert shout_data["id"] == 100
assert shout_data["slug"] == "test-shout"
assert shout_data["published_at"] == 1234567890
if __name__ == "__main__":
pytest.main([__file__])